forked from casdoor/casdoor
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16f427d4d7 | ||
|
|
6ad8f9fa0b | ||
|
|
b93a29fbc6 |
5
.gitattributes
vendored
5
.gitattributes
vendored
@@ -1,5 +0,0 @@
|
||||
*.go linguist-detectable=true
|
||||
*.js linguist-detectable=false
|
||||
# Declare files that will always have LF line endings on checkout.
|
||||
# Git will always convert line endings to LF on checkout. You should use this for files that must keep LF endings, even on Windows.
|
||||
*.sh text eol=lf
|
||||
@@ -1,31 +0,0 @@
|
||||
name: Build & Push Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- custom
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Login to Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: gromlab.ru
|
||||
username: ${{ secrets.CR_USER }}
|
||||
password: ${{ secrets.CR_TOKEN }}
|
||||
|
||||
- name: Build and Push
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
target: STANDARD
|
||||
push: true
|
||||
tags: |
|
||||
gromlab.ru/gromov/casdoor:latest
|
||||
gromlab.ru/gromov/casdoor:${{ github.sha }}
|
||||
315
.github/workflows/build.yml
vendored
315
.github/workflows/build.yml
vendored
@@ -1,330 +1,67 @@
|
||||
name: Build
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.25.8"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
go-tests:
|
||||
name: Running Go tests
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:5.7
|
||||
env:
|
||||
MYSQL_DATABASE: casdoor
|
||||
MYSQL_ROOT_PASSWORD: 123456
|
||||
ports:
|
||||
- 3306:3306
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache-dependency-path: ./go.mod
|
||||
- name: Tests
|
||||
run: |
|
||||
go test -v $(go list ./...) -tags skipCi
|
||||
working-directory: ./
|
||||
|
||||
frontend:
|
||||
name: Front-end
|
||||
runs-on: ubuntu-latest
|
||||
needs: [go-tests]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "yarn"
|
||||
cache-dependency-path: ./web/yarn.lock
|
||||
node-version: '14.17.0'
|
||||
- run: yarn install && CI=false yarn run build
|
||||
working-directory: ./web
|
||||
- name: Upload build artifacts
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: frontend-build-${{ github.run_id }}
|
||||
path: ./web/build
|
||||
|
||||
backend:
|
||||
name: Back-end
|
||||
runs-on: ubuntu-latest
|
||||
needs: [go-tests]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache-dependency-path: ./go.mod
|
||||
go-version: '^1.16.5'
|
||||
- run: go version
|
||||
- name: Build
|
||||
run: |
|
||||
go build -race -ldflags "-extldflags '-static'"
|
||||
working-directory: ./
|
||||
|
||||
linter:
|
||||
name: Go-Linter
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
needs: [go-tests]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: false
|
||||
|
||||
- name: Sync vendor tree
|
||||
run: go mod vendor
|
||||
|
||||
# CI and local `make lint` both use the repo's gofumpt-only golangci-lint config.
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v9.2.0
|
||||
with:
|
||||
version: v2.11.4
|
||||
args: --config .golangci.yml ./...
|
||||
|
||||
e2e:
|
||||
name: e2e-test
|
||||
runs-on: ubuntu-latest
|
||||
needs: [go-tests]
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:5.7
|
||||
env:
|
||||
MYSQL_DATABASE: casdoor
|
||||
MYSQL_ROOT_PASSWORD: 123456
|
||||
ports:
|
||||
- 3306:3306
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache-dependency-path: ./go.mod
|
||||
- name: start backend
|
||||
run: nohup go run ./main.go > /tmp/backend.log 2>&1 &
|
||||
working-directory: ./
|
||||
- name: Wait for backend to be ready
|
||||
run: |
|
||||
echo "Waiting for backend server to start on port 8000..."
|
||||
for i in {1..60}; do
|
||||
if curl -s http://localhost:8000 > /dev/null 2>&1; then
|
||||
echo "Backend is ready!"
|
||||
break
|
||||
fi
|
||||
if [ $i -eq 60 ]; then
|
||||
echo "Backend failed to start within 60 seconds"
|
||||
echo "Backend logs:"
|
||||
cat /tmp/backend.log || echo "No backend logs available"
|
||||
exit 1
|
||||
fi
|
||||
echo "Waiting... ($i/60)"
|
||||
sleep 1
|
||||
done
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "yarn"
|
||||
cache-dependency-path: ./web/yarn.lock
|
||||
- run: yarn install
|
||||
working-directory: ./web
|
||||
- uses: cypress-io/github-action@v5
|
||||
with:
|
||||
browser: chrome
|
||||
start: yarn start
|
||||
wait-on: "http://localhost:7001"
|
||||
wait-on-timeout: 210
|
||||
working-directory: ./web
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: cypress-screenshots
|
||||
path: ./web/cypress/screenshots
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: cypress-videos
|
||||
path: ./web/cypress/videos
|
||||
|
||||
tag-release:
|
||||
name: Create Tag
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push'
|
||||
needs: [frontend, backend, linter, e2e]
|
||||
outputs:
|
||||
new-release-published: ${{ steps.semantic.outputs.new_release_published }}
|
||||
new-release-version: ${{ steps.semantic.outputs.new_release_version }}
|
||||
if: github.repository == 'casbin/casdoor' && github.event_name == 'push'
|
||||
needs: [ frontend, backend ]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Create Tag with Semantic Release
|
||||
id: semantic
|
||||
uses: cycjimmy/semantic-release-action@v4
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
github-release:
|
||||
name: GitHub Release
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && needs.tag-release.outputs.new-release-published == 'true'
|
||||
needs: [tag-release]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-go@v4
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache-dependency-path: ./go.mod
|
||||
|
||||
- name: Free disk space
|
||||
uses: jlumbroso/free-disk-space@v1.3.1
|
||||
with:
|
||||
tool-cache: false
|
||||
android: true
|
||||
dotnet: true
|
||||
haskell: true
|
||||
large-packages: true
|
||||
swap-storage: true
|
||||
|
||||
- name: Download frontend build artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: frontend-build-${{ github.run_id }}
|
||||
path: ./web/build
|
||||
|
||||
- name: Prepare Go caches
|
||||
run: |
|
||||
echo "GOMODCACHE=$RUNNER_TEMP/gomod" >> $GITHUB_ENV
|
||||
echo "GOCACHE=$RUNNER_TEMP/gocache" >> $GITHUB_ENV
|
||||
go clean -cache -modcache -testcache -fuzzcache
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: "~> v2"
|
||||
args: release --clean
|
||||
node-version: 12
|
||||
- name: Release
|
||||
run: yarn global add semantic-release@17.4.4 && semantic-release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }}
|
||||
|
||||
docker-release:
|
||||
name: Docker Release
|
||||
publish:
|
||||
name: Publish
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && needs.tag-release.outputs.new-release-published == 'true'
|
||||
needs: [tag-release]
|
||||
if: github.repository == 'casbin/casdoor' && github.event_name == 'push'
|
||||
needs: release
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: -1
|
||||
|
||||
- name: Fetch Previous version
|
||||
id: get-previous-tag
|
||||
uses: actions-ecosystem/action-get-latest-tag@v1.6.0
|
||||
|
||||
- name: Decide Should_Push Or Not
|
||||
id: should_push
|
||||
run: |
|
||||
old_version=${{steps.get-previous-tag.outputs.tag}}
|
||||
new_version=${{ needs.tag-release.outputs.new-release-version }}
|
||||
|
||||
old_array=(${old_version//\./ })
|
||||
new_array=(${new_version//\./ })
|
||||
|
||||
if [ ${old_array[0]} != ${new_array[0]} ]
|
||||
then
|
||||
echo ::set-output name=push::'true'
|
||||
elif [ ${old_array[1]} != ${new_array[1]} ]
|
||||
then
|
||||
echo ::set-output name=push::'true'
|
||||
else
|
||||
echo ::set-output name=push::'false'
|
||||
fi
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v2
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
- name: Push to Docker Hub
|
||||
uses: docker/build-push-action@v3
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
target: STANDARD
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: casbin/casdoor:${{ needs.tag-release.outputs.new-release-version }},casbin/casdoor:latest
|
||||
|
||||
- name: Push All In One Version to Docker Hub
|
||||
uses: docker/build-push-action@v3
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||
with:
|
||||
context: .
|
||||
target: ALLINONE
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: casbin/casdoor-all-in-one:${{ needs.tag-release.outputs.new-release-version }},casbin/casdoor-all-in-one:latest
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
if: steps.should_push.outputs.push=='true'
|
||||
with:
|
||||
repository: casdoor/casdoor-helm
|
||||
ref: "master"
|
||||
token: ${{ secrets.GH_BOT_TOKEN }}
|
||||
|
||||
- name: Update Helm Chart
|
||||
if: steps.should_push.outputs.push=='true'
|
||||
run: |
|
||||
# Set the appVersion and version of the chart to the current tag
|
||||
sed -i "s/appVersion: .*/appVersion: ${{ needs.tag-release.outputs.new-release-version }}/g" ./charts/casdoor/Chart.yaml
|
||||
sed -i "s/version: .*/version: ${{ needs.tag-release.outputs.new-release-version }}/g" ./charts/casdoor/Chart.yaml
|
||||
|
||||
REGISTRY=oci://registry-1.docker.io/casbin
|
||||
cd charts/casdoor
|
||||
helm package .
|
||||
PKG_NAME=$(ls *.tgz)
|
||||
helm repo index . --url $REGISTRY --merge index.yaml
|
||||
helm push $PKG_NAME $REGISTRY
|
||||
rm $PKG_NAME
|
||||
|
||||
# Commit and push the changes back to the repository
|
||||
git config --global user.name "casbin-bot"
|
||||
git config --global user.email "bot@casbin.org"
|
||||
git add Chart.yaml index.yaml
|
||||
git commit -m "chore(helm): bump helm charts appVersion to ${{ needs.tag-release.outputs.new-release-version }}"
|
||||
git tag ${{ needs.tag-release.outputs.new-release-version }}
|
||||
git push origin HEAD:master --follow-tags
|
||||
tags: casbin/casdoor:latest
|
||||
|
||||
27
.github/workflows/sync.yml
vendored
27
.github/workflows/sync.yml
vendored
@@ -7,14 +7,14 @@ on:
|
||||
jobs:
|
||||
synchronize-with-crowdin:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push'
|
||||
if: github.repository == 'casbin/casdoor' && github.event_name == 'push'
|
||||
steps:
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: crowdin action
|
||||
uses: crowdin/github-action@1.4.8
|
||||
uses: crowdin/github-action@1.2.0
|
||||
with:
|
||||
upload_translations: true
|
||||
|
||||
@@ -32,25 +32,4 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_PROJECT_ID: '463556'
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
|
||||
- name: crowdin backend action
|
||||
uses: crowdin/github-action@1.4.8
|
||||
with:
|
||||
upload_translations: true
|
||||
|
||||
download_translations: true
|
||||
push_translations: true
|
||||
commit_message: 'refactor: New Crowdin Backend translations by Github Action'
|
||||
|
||||
localization_branch_name: l10n_crowdin_action
|
||||
create_pull_request: true
|
||||
pull_request_title: 'refactor: New Crowdin Backend translations'
|
||||
|
||||
crowdin_branch_name: l10n_branch
|
||||
config: './crowdin.yml'
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_PROJECT_ID: '463556'
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
13
.gitignore
vendored
13
.gitignore
vendored
@@ -5,7 +5,6 @@
|
||||
*.so
|
||||
*.dylib
|
||||
*.swp
|
||||
server_*
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
@@ -14,13 +13,10 @@ server_*
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
vendor/
|
||||
bin/
|
||||
# vendor/
|
||||
|
||||
.idea/
|
||||
*.iml
|
||||
.vscode/settings.json
|
||||
.claude
|
||||
|
||||
tmp/
|
||||
tmpFiles/
|
||||
@@ -29,10 +25,3 @@ logs/
|
||||
files/
|
||||
lastupdate.tmp
|
||||
commentsRouter*.go
|
||||
|
||||
# ignore build result
|
||||
casdoor
|
||||
server
|
||||
|
||||
# include helm-chart
|
||||
!manifests/casdoor
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
version: "2"
|
||||
run:
|
||||
relative-path-mode: gomod
|
||||
modules-download-mode: vendor
|
||||
linters:
|
||||
default: none
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
formatters:
|
||||
enable:
|
||||
- gofumpt
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
@@ -1,55 +0,0 @@
|
||||
# This is an example .goreleaser.yml file with some sensible defaults.
|
||||
# Make sure to check the documentation at https://goreleaser.com
|
||||
|
||||
# The lines below are called `modelines`. See `:help modeline`
|
||||
# Feel free to remove those if you don't want/need to use them.
|
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
||||
# vim: set ts=2 sw=2 tw=0 fo=cnqoj
|
||||
|
||||
version: 2
|
||||
|
||||
before:
|
||||
hooks:
|
||||
# You may remove this if you don't use go modules.
|
||||
- go mod tidy
|
||||
# you may remove this if you don't need go generate
|
||||
#- go generate ./...
|
||||
- go test -v -run TestGetVersionInfo ./util/system_test.go ./util/system.go ./util/variable.go
|
||||
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
|
||||
archives:
|
||||
- format: tar.gz
|
||||
# this name template makes the OS and Arch compatible with the results of `uname`.
|
||||
name_template: >-
|
||||
{{ .ProjectName }}_
|
||||
{{- title .Os }}_
|
||||
{{- if eq .Arch "amd64" }}x86_64
|
||||
{{- else if eq .Arch "386" }}i386
|
||||
{{- else }}{{ .Arch }}{{ end }}
|
||||
{{- if .Arm }}v{{ .Arm }}{{ end }}
|
||||
# use zip for windows archives
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
files:
|
||||
- src: 'web/build'
|
||||
dst: './web/build'
|
||||
- src: 'conf/app.conf'
|
||||
dst: './conf/app.conf'
|
||||
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- "^docs:"
|
||||
- "^test:"
|
||||
15
.vscode/launch.json
vendored
15
.vscode/launch.json
vendored
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${workspaceFolder}",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"debugAdapter": "dlv-dap",
|
||||
"args": ["--createDatabase=true"]
|
||||
}
|
||||
]
|
||||
}
|
||||
77
Dockerfile
77
Dockerfile
@@ -1,70 +1,19 @@
|
||||
FROM --platform=$BUILDPLATFORM node:20.20.1 AS FRONT
|
||||
WORKDIR /web
|
||||
|
||||
# Copy only dependency files first for better caching
|
||||
COPY ./web/package.json ./web/yarn.lock ./
|
||||
RUN yarn install --frozen-lockfile --network-timeout 1000000
|
||||
|
||||
# Copy source files and build
|
||||
COPY ./web .
|
||||
RUN NODE_OPTIONS="--max-old-space-size=4096" yarn run build
|
||||
|
||||
FROM --platform=$BUILDPLATFORM golang:1.25.8 AS BACK
|
||||
FROM golang:1.16 AS BACK
|
||||
WORKDIR /go/src/casdoor
|
||||
|
||||
# Copy only go.mod and go.sum first for dependency caching
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
# Copy source files
|
||||
COPY . .
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOPROXY=https://goproxy.cn,direct go build -ldflags="-w -s" -o server . \
|
||||
&& apt update && apt install wait-for-it && chmod +x /usr/bin/wait-for-it
|
||||
|
||||
RUN ./build.sh
|
||||
FROM node:14.17.4 AS FRONT
|
||||
WORKDIR /web
|
||||
COPY ./web .
|
||||
RUN npm install && npm run build
|
||||
|
||||
FROM alpine:latest AS STANDARD
|
||||
FROM alpine:latest
|
||||
LABEL MAINTAINER="https://casdoor.org/"
|
||||
ARG USER=casdoor
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
ENV BUILDX_ARCH="${TARGETOS:-linux}_${TARGETARCH:-amd64}"
|
||||
|
||||
RUN sed -i 's/https/http/' /etc/apk/repositories
|
||||
RUN apk add --update sudo
|
||||
RUN apk add tzdata
|
||||
RUN apk add curl
|
||||
RUN apk add ca-certificates && update-ca-certificates
|
||||
|
||||
RUN adduser -D $USER -u 1000 \
|
||||
&& echo "$USER ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/$USER \
|
||||
&& chmod 0440 /etc/sudoers.d/$USER \
|
||||
&& mkdir logs \
|
||||
&& chown -R $USER:$USER logs
|
||||
|
||||
USER 1000
|
||||
WORKDIR /
|
||||
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/server_${BUILDX_ARCH} ./server
|
||||
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/swagger ./swagger
|
||||
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/conf/app.conf ./conf/app.conf
|
||||
COPY --from=FRONT --chown=$USER:$USER /web/build ./web/build
|
||||
|
||||
ENTRYPOINT ["/server"]
|
||||
|
||||
|
||||
FROM debian:latest AS ALLINONE
|
||||
LABEL MAINTAINER="https://casdoor.org/"
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
ENV BUILDX_ARCH="${TARGETOS:-linux}_${TARGETARCH:-amd64}"
|
||||
|
||||
RUN apt update
|
||||
RUN apt install -y ca-certificates lsof && update-ca-certificates
|
||||
|
||||
WORKDIR /
|
||||
COPY --from=BACK /go/src/casdoor/server_${BUILDX_ARCH} ./server
|
||||
COPY --from=BACK /go/src/casdoor/swagger ./swagger
|
||||
COPY --from=BACK /go/src/casdoor/docker-entrypoint.sh /docker-entrypoint.sh
|
||||
COPY --from=BACK /go/src/casdoor/conf/app.conf ./conf/app.conf
|
||||
COPY --from=FRONT /web/build ./web/build
|
||||
|
||||
ENTRYPOINT ["/bin/bash"]
|
||||
CMD ["/docker-entrypoint.sh"]
|
||||
COPY --from=BACK /go/src/casdoor/ ./
|
||||
COPY --from=BACK /usr/bin/wait-for-it ./
|
||||
RUN mkdir -p web/build && apk add --no-cache bash coreutils
|
||||
COPY --from=FRONT /web/build /web/build
|
||||
CMD ./wait-for-it db:3306 -- ./server
|
||||
116
Makefile
116
Makefile
@@ -1,116 +0,0 @@
|
||||
|
||||
# Image URL to use all building/pushing image targets
|
||||
REGISTRY ?= casbin
|
||||
IMG ?= casdoor
|
||||
IMG_TAG ?=$(shell git --no-pager log -1 --format="%ad" --date=format:"%Y%m%d")-$(shell git describe --tags --always --dirty --abbrev=6)
|
||||
NAMESPACE ?= casdoor
|
||||
APP ?= casdoor
|
||||
HOST ?= test.com
|
||||
|
||||
|
||||
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
|
||||
ifeq (,$(shell go env GOBIN))
|
||||
GOBIN=$(shell go env GOPATH)/bin
|
||||
else
|
||||
GOBIN=$(shell go env GOBIN)
|
||||
endif
|
||||
|
||||
# Setting SHELL to bash allows bash commands to be executed by recipes.
|
||||
# This is a requirement for 'setup-envtest.sh' in the test target.
|
||||
# Options are set to exit when a recipe line exits non-zero or a piped command fails.
|
||||
SHELL = /usr/bin/env bash -o pipefail
|
||||
.SHELLFLAGS = -ec
|
||||
|
||||
.PHONY: all
|
||||
all: docker-build docker-push deploy
|
||||
|
||||
##@ General
|
||||
|
||||
# The help target prints out all targets with their descriptions organized
|
||||
# beneath their categories. The categories are represented by '##@' and the
|
||||
# target descriptions by '##'. The awk commands is responsible for reading the
|
||||
# entire set of makefiles included in this invocation, looking for lines of the
|
||||
# file as xyz: ## something, and then pretty-format the target and help. Then,
|
||||
# if there's a line with ##@ something, that gets pretty-printed as a category.
|
||||
# More info on the usage of ANSI control characters for terminal formatting:
|
||||
# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters
|
||||
# More info on the awk command:
|
||||
# http://linuxcommand.org/lc3_adv_awk.php
|
||||
|
||||
.PHONY: help
|
||||
help: ## Display this help.
|
||||
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
|
||||
|
||||
##@ Development
|
||||
|
||||
.PHONY: fmt
|
||||
fmt: ## Run go fmt against code.
|
||||
go fmt ./...
|
||||
|
||||
.PHONY: vet
|
||||
vet: ## Run go vet against code.
|
||||
go vet ./...
|
||||
|
||||
.PHONY: ut
|
||||
ut: ## UT test
|
||||
go test -v -cover -coverprofile=coverage.out ./...
|
||||
go tool cover -func=coverage.out
|
||||
|
||||
##@ Build
|
||||
|
||||
.PHONY: backend
|
||||
backend: fmt vet ## Build backend binary.
|
||||
go build -o bin/manager main.go
|
||||
|
||||
.PHONY: backend-vendor
|
||||
backend-vendor: vendor fmt vet ## Build backend binary with vendor.
|
||||
go build -mod=vendor -o bin/manager main.go
|
||||
|
||||
.PHONY: frontend
|
||||
frontend: ## Build backend binary.
|
||||
cd web/ && yarn && yarn run build && cd -
|
||||
|
||||
.PHONY: vendor
|
||||
vendor: ## Update vendor.
|
||||
go mod vendor
|
||||
|
||||
.PHONY: run
|
||||
run: fmt vet ## Run backend in local
|
||||
go run ./main.go
|
||||
|
||||
.PHONY: docker-build
|
||||
docker-build: ## Build docker image with the manager.
|
||||
docker build -t ${REGISTRY}/${IMG}:${IMG_TAG} .
|
||||
|
||||
.PHONY: docker-push
|
||||
docker-push: ## Push docker image with the manager.
|
||||
docker push ${REGISTRY}/${IMG}:${IMG_TAG}
|
||||
|
||||
deps: ## Run dependencies for local development
|
||||
docker compose up -d db
|
||||
|
||||
lint-install: ## Install golangci-lint
|
||||
@# Keep the local golangci-lint version aligned with CI. Both local and CI lint run the gofumpt-only ruleset from .golangci.yml.
|
||||
GOTOOLCHAIN=go1.25.8 go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.4
|
||||
|
||||
lint: vendor ## Run golangci-lint
|
||||
@echo "---lint---"
|
||||
golangci-lint run ./...
|
||||
|
||||
##@ Deployment
|
||||
|
||||
.PHONY: deploy
|
||||
deploy: ## Deploy controller to the K8s cluster specified in ~/.kube/config.
|
||||
helm upgrade --install ${APP} manifests/casdoor --create-namespace --set ingress.enabled=true \
|
||||
--set "ingress.hosts[0].host=${HOST},ingress.hosts[0].paths[0].path=/,ingress.hosts[0].paths[0].pathType=ImplementationSpecific" \
|
||||
--set image.tag=${IMG_TAG} --set image.repository=${REGISTRY} --set image.name=${IMG} --version ${IMG_TAG} -n ${NAMESPACE}
|
||||
|
||||
.PHONY: dry-run
|
||||
dry-run: ## Dry run for helm install
|
||||
helm upgrade --install ${APP} manifests/casdoor --set ingress.enabled=true \
|
||||
--set "ingress.hosts[0].host=${HOST},ingress.hosts[0].paths[0].path=/,ingress.hosts[0].paths[0].pathType=ImplementationSpecific" \
|
||||
--set image.tag=${IMG_TAG} --set image.repository=${REGISTRY} --set image.name=${IMG} --version ${IMG_TAG} -n ${NAMESPACE} --dry-run
|
||||
|
||||
.PHONY: undeploy
|
||||
undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
|
||||
helm delete ${APP} -n ${NAMESPACE}
|
||||
258
README.md
258
README.md
@@ -1,88 +1,170 @@
|
||||
<h1 align="center" style="border-bottom: none;">📦⚡️ Casdoor</h1>
|
||||
<h3 align="center">An open-source AI-first Identity and Access Management (IAM) /AI MCP gateway and auth server with web UI supporting MCP, A2A, OAuth 2.1, OIDC, SAML, CAS, LDAP, SCIM, WebAuthn, TOTP, MFA, Face ID, Google Workspace, Azure AD</h3>
|
||||
<p align="center">
|
||||
<a href="#badge">
|
||||
<img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
|
||||
</a>
|
||||
<a href="https://hub.docker.com/r/casbin/casdoor">
|
||||
<img alt="docker pull casbin/casdoor" src="https://img.shields.io/docker/pulls/casbin/casdoor.svg">
|
||||
</a>
|
||||
<a href="https://github.com/casdoor/casdoor/actions/workflows/build.yml">
|
||||
<img alt="GitHub Workflow Status (branch)" src="https://github.com/casdoor/casdoor/workflows/Build/badge.svg?style=flat-square">
|
||||
</a>
|
||||
<a href="https://github.com/casdoor/casdoor/releases/latest">
|
||||
<img alt="GitHub Release" src="https://img.shields.io/github/v/release/casdoor/casdoor.svg">
|
||||
</a>
|
||||
<a href="https://hub.docker.com/r/casbin/casdoor">
|
||||
<img alt="Docker Image Version (latest semver)" src="https://img.shields.io/badge/Docker%20Hub-latest-brightgreen">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://goreportcard.com/report/github.com/casdoor/casdoor">
|
||||
<img alt="Go Report Card" src="https://goreportcard.com/badge/github.com/casdoor/casdoor?style=flat-square">
|
||||
</a>
|
||||
<a href="https://github.com/casdoor/casdoor/blob/master/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/casdoor/casdoor?style=flat-square" alt="license">
|
||||
</a>
|
||||
<a href="https://github.com/casdoor/casdoor/issues">
|
||||
<img alt="GitHub issues" src="https://img.shields.io/github/issues/casdoor/casdoor?style=flat-square">
|
||||
</a>
|
||||
<a href="#">
|
||||
<img alt="GitHub stars" src="https://img.shields.io/github/stars/casdoor/casdoor?style=flat-square">
|
||||
</a>
|
||||
<a href="https://github.com/casdoor/casdoor/network">
|
||||
<img alt="GitHub forks" src="https://img.shields.io/github/forks/casdoor/casdoor?style=flat-square">
|
||||
</a>
|
||||
<a href="https://crowdin.com/project/casdoor-site">
|
||||
<img alt="Crowdin" src="https://badges.crowdin.net/casdoor-site/localized.svg">
|
||||
</a>
|
||||
<a href="https://discord.gg/5rPsrAzK7S">
|
||||
<img alt="Discord" src="https://img.shields.io/discord/1022748306096537660?style=flat-square&logo=discord&label=discord&color=5865F2">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## Online demo
|
||||
|
||||
- Read-only site: https://door.casdoor.com (any modification operation will fail)
|
||||
- Writable site: https://demo.casdoor.com (original data will be restored for every 5 minutes)
|
||||
|
||||
## Documentation
|
||||
|
||||
https://casdoor.org
|
||||
|
||||
## Install
|
||||
|
||||
- By source code: https://casdoor.org/docs/basic/server-installation
|
||||
- By Docker: https://casdoor.org/docs/basic/try-with-docker
|
||||
- By Kubernetes Helm: https://casdoor.org/docs/basic/try-with-helm
|
||||
|
||||
## How to connect to Casdoor?
|
||||
|
||||
https://casdoor.org/docs/how-to-connect/overview
|
||||
|
||||
## Casdoor Public API
|
||||
|
||||
- Docs: https://casdoor.org/docs/basic/public-api
|
||||
- Swagger: https://door.casdoor.com/swagger
|
||||
|
||||
## Integrations
|
||||
|
||||
https://casdoor.org/docs/category/integrations
|
||||
|
||||
## How to contact?
|
||||
|
||||
- Discord: https://discord.gg/5rPsrAzK7S
|
||||
- Contact: https://casdoor.org/help
|
||||
|
||||
## Contribute
|
||||
|
||||
For casdoor, if you have any questions, you can give Issues, or you can also directly start Pull Requests(but we recommend giving issues first to communicate with the community).
|
||||
|
||||
### I18n translation
|
||||
|
||||
If you are contributing to casdoor, please note that we use [Crowdin](https://crowdin.com/project/casdoor-site) as translating platform and i18next as translating tool. When you add some words using i18next in the `web/` directory, please remember to add what you have added to the `web/src/locales/en/data.json` file.
|
||||
|
||||
## License
|
||||
|
||||
[Apache-2.0](https://github.com/casdoor/casdoor/blob/master/LICENSE)
|
||||
<h1 align="center" style="border-bottom: none;">📦⚡️ Casdoor</h1>
|
||||
<h3 align="center">A UI-first centralized authentication / Single-Sign-On (SSO) platform based on OAuth 2.0 / OIDC.</h3>
|
||||
<p align="center">
|
||||
<a href="#badge">
|
||||
<img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
|
||||
</a>
|
||||
<a href="https://hub.docker.com/r/casbin/casdoor">
|
||||
<img alt="docker pull casbin/casdoor" src="https://img.shields.io/docker/pulls/casbin/casdoor.svg">
|
||||
</a>
|
||||
<a href="https://github.com/casbin/casdoor/actions/workflows/build.yml">
|
||||
<img alt="GitHub Workflow Status (branch)" src="https://github.com/casbin/jcasbin/workflows/build/badge.svg?style=flat-square">
|
||||
</a>
|
||||
<a href="https://github.com/casbin/casdoor/releases/latest">
|
||||
<img alt="GitHub Release" src="https://img.shields.io/github/v/release/casbin/casdoor.svg">
|
||||
</a>
|
||||
<a href="https://hub.docker.com/repository/docker/casbin/casdoor">
|
||||
<img alt="Docker Image Version (latest semver)" src="https://img.shields.io/badge/Docker%20Hub-latest-brightgreen">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://goreportcard.com/report/github.com/casbin/casdoor">
|
||||
<img alt="Go Report Card" src="https://goreportcard.com/badge/github.com/casbin/casdoor?style=flat-square">
|
||||
</a>
|
||||
<a href="https://github.com/casbin/casdoor/blob/master/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/casbin/casdoor?style=flat-square" alt="license">
|
||||
</a>
|
||||
<a href="https://github.com/casbin/casdoor/issues">
|
||||
<img alt="GitHub issues" src="https://img.shields.io/github/issues/casbin/casdoor?style=flat-square">
|
||||
</a>
|
||||
<a href="#">
|
||||
<img alt="GitHub stars" src="https://img.shields.io/github/stars/casbin/casdoor?style=flat-square">
|
||||
</a>
|
||||
<a href="https://github.com/casbin/casdoor/network">
|
||||
<img alt="GitHub forks" src="https://img.shields.io/github/forks/casbin/casdoor?style=flat-square">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## Online demo
|
||||
|
||||
Deployed site: https://door.casbin.com/
|
||||
|
||||
## Quick Start
|
||||
|
||||
Run your own casdoor program in a few minutes:smiley:
|
||||
|
||||
### Download
|
||||
|
||||
There are two methods, get code via go subcommand `get`:
|
||||
|
||||
```shell
|
||||
go get github.com/casbin/casdoor
|
||||
```
|
||||
|
||||
or `git`:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/casbin/casdoor
|
||||
```
|
||||
|
||||
Finally, change directory:
|
||||
|
||||
```bash
|
||||
cd casdoor/
|
||||
```
|
||||
|
||||
We provide two start up methods for all kinds of users.
|
||||
|
||||
### Manual
|
||||
|
||||
#### Simple configuration
|
||||
|
||||
Edit `conf/app.conf`, modify `dataSourceName` to correct database info, which follows this format:
|
||||
|
||||
```bash
|
||||
username:password@tcp(database_ip:database_port)/
|
||||
```
|
||||
|
||||
#### Run
|
||||
|
||||
Casdoor provides two run modes, the difference is binary size and user prompt.
|
||||
|
||||
##### Dev Mode
|
||||
|
||||
Edit `conf/app.conf`, set `runmode=dev`. Firstly build front-end files:
|
||||
|
||||
```bash
|
||||
cd web/ && npm install && npm run start
|
||||
```
|
||||
*❗ A word of caution ❗: the `npm` commands above need a recommended system RAM of at least 4GB. It has a potential failure during building the files if your RAM is not sufficient.*
|
||||
|
||||
Then build back-end binary file, change directory to root(Relative to casdoor):
|
||||
|
||||
```bash
|
||||
go run main.go
|
||||
```
|
||||
|
||||
That's it! Try to visit http://127.0.0.1:7001/. :small_airplane:
|
||||
**But make sure you always request the backend port 8000 when you are using SDKs.**
|
||||
|
||||
##### Production Mode
|
||||
|
||||
Edit `conf/app.conf`, set `runmode=prod`. Firstly build front-end files:
|
||||
|
||||
```bash
|
||||
cd web/ && npm install && npm run build
|
||||
```
|
||||
|
||||
Then build back-end binary file, change directory to root(Relative to casdoor):
|
||||
|
||||
```bash
|
||||
go build main.go && sudo ./main
|
||||
```
|
||||
|
||||
> Notice, you should visit back-end port, default 8000. Now try to visit **http://SERVER_IP:8000/**
|
||||
|
||||
### Docker
|
||||
|
||||
This method requires [docker](https://docs.docker.com/get-docker/) and [docker-compose](https://docs.docker.com/compose/install/) to be installed first.
|
||||
|
||||
#### Simple configuration
|
||||
|
||||
Edit `conf/app.conf`, modify `dataSourceName` to the fixed content:
|
||||
|
||||
```bash
|
||||
dataSourceName = root:123@tcp(db:3306)/
|
||||
```
|
||||
|
||||
> If you need to modify `conf/app.conf`, you need to re-run `docker-compose up`.
|
||||
|
||||
#### Run
|
||||
|
||||
```bash
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
That's it! Try to visit http://localhost:8000/. :small_airplane:
|
||||
|
||||
### Docker Hub
|
||||
|
||||
This method requires [docker](https://docs.docker.com/get-docker/) and [docker-compose](https://docs.docker.com/compose/install/) to be installed first.
|
||||
|
||||
```bash
|
||||
docker pull casbin/casdoor
|
||||
```
|
||||
|
||||
## Detailed documentation
|
||||
|
||||
We also provide a complete [document](https://casdoor.org/) as a reference.
|
||||
|
||||
## Other examples
|
||||
|
||||
These all use casdoor as a centralized authentication platform.
|
||||
|
||||
- [Casnode](https://github.com/casbin/casnode): Next-generation forum software based on React + Golang.
|
||||
- [Casbin-OA](https://github.com/casbin/casbin-oa): A full-featured OA(Office Assistant) system.
|
||||
- ......
|
||||
|
||||
## Contribute
|
||||
|
||||
For casdoor, if you have any questions, you can give Issues, and you can also directly Pull Requests(but we recommend give issues first to communicate with the community).
|
||||
|
||||
### I18n notice
|
||||
|
||||
If you are contributing to casdoor, please note that we use [Crowdin](https://crowdin.com/project/casdoor-web) as translating platform and i18next as translating tool. When you add some words using i18next in the ```web/``` directory, please remember to add what you have added to the ```web/src/locales/en/data.json``` file.
|
||||
|
||||
## License
|
||||
|
||||
[Apache-2.0](https://github.com/casbin/casdoor/blob/master/LICENSE)
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
We are grateful for security researchers and users reporting a vulnerability to us first. To ensure that your request is handled in a timely manner and we can keep users safe, please follow the below guidelines.
|
||||
|
||||
- **Please do not report security vulnerabilities directly on GitHub.**
|
||||
|
||||
- To report a vulnerability, please email [admin@casdoor.org](admin@casdoor.org).
|
||||
207
authz/authz.go
207
authz/authz.go
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -15,123 +15,92 @@
|
||||
package authz
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casbin/casbin/v2"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/casbin/casbin/v2/model"
|
||||
xormadapter "github.com/casbin/xorm-adapter/v2"
|
||||
stringadapter "github.com/qiangmzsx/string-adapter/v2"
|
||||
)
|
||||
|
||||
var Enforcer *casbin.Enforcer
|
||||
|
||||
func InitApi() {
|
||||
e, err := object.GetInitializedEnforcer(util.GetId("built-in", "api-enforcer-built-in"))
|
||||
func InitAuthz() {
|
||||
var err error
|
||||
|
||||
a, err := xormadapter.NewAdapter(beego.AppConfig.String("driverName"), beego.AppConfig.String("dataSourceName")+beego.AppConfig.String("dbName"), true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
modelText := `
|
||||
[request_definition]
|
||||
r = subOwner, subName, method, urlPath, objOwner, objName
|
||||
|
||||
[policy_definition]
|
||||
p = subOwner, subName, method, urlPath, objOwner, objName
|
||||
|
||||
[role_definition]
|
||||
g = _, _
|
||||
|
||||
[policy_effect]
|
||||
e = some(where (p.eft == allow))
|
||||
|
||||
[matchers]
|
||||
m = (r.subOwner == p.subOwner || p.subOwner == "*") && \
|
||||
(r.subName == p.subName || p.subName == "*" || r.subName != "anonymous" && p.subName == "!anonymous") && \
|
||||
(r.method == p.method || p.method == "*") && \
|
||||
(r.urlPath == p.urlPath || p.urlPath == "*") && \
|
||||
(r.objOwner == p.objOwner || p.objOwner == "*") && \
|
||||
(r.objName == p.objName || p.objName == "*") || \
|
||||
(r.urlPath == "/api/update-user" && r.subOwner == r.objOwner && r.subName == r.objName)
|
||||
`
|
||||
|
||||
m, err := model.NewModelFromString(modelText)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
Enforcer, err = casbin.NewEnforcer(m, a)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
Enforcer = e.Enforcer
|
||||
Enforcer.ClearPolicy()
|
||||
|
||||
// if len(Enforcer.GetPolicy()) == 0 {
|
||||
//if len(Enforcer.GetPolicy()) == 0 {
|
||||
if true {
|
||||
ruleText := `
|
||||
p, built-in, *, *, *, *, *
|
||||
p, app, *, *, *, *, *
|
||||
p, *, *, POST, /api/signup, *, *
|
||||
p, *, *, GET, /api/get-email-and-phone, *, *
|
||||
p, *, *, POST, /api/get-email-and-phone, *, *
|
||||
p, *, *, POST, /api/login, *, *
|
||||
p, *, *, GET, /api/get-app-login, *, *
|
||||
p, *, *, POST, /api/logout, *, *
|
||||
p, *, *, GET, /api/logout, *, *
|
||||
p, *, *, POST, /api/sso-logout, *, *
|
||||
p, *, *, GET, /api/sso-logout, *, *
|
||||
p, *, *, POST, /api/callback, *, *
|
||||
p, *, *, POST, /api/device-auth, *, *
|
||||
p, *, *, GET, /api/get-account, *, *
|
||||
p, *, *, GET, /api/userinfo, *, *
|
||||
p, *, *, GET, /api/user, *, *
|
||||
p, *, *, GET, /api/health, *, *
|
||||
p, *, *, *, /api/webhook, *, *
|
||||
p, *, *, GET, /api/get-qrcode, *, *
|
||||
p, *, *, GET, /api/get-webhook-event, *, *
|
||||
p, *, *, GET, /api/get-captcha-status, *, *
|
||||
p, *, *, *, /api/login/oauth, *, *
|
||||
p, *, *, POST, /api/oauth/register, *, *
|
||||
p, *, *, POST, /api/login/oauth/access_token, *, *
|
||||
p, *, *, GET, /api/get-application, *, *
|
||||
p, *, *, GET, /api/get-organization-applications, *, *
|
||||
p, *, *, GET, /api/get-users, *, *
|
||||
p, *, *, GET, /api/get-user, *, *
|
||||
p, *, *, GET, /api/get-organizations, *, *
|
||||
p, *, *, GET, /api/get-user-application, *, *
|
||||
p, *, *, POST, /api/upload-users, *, *
|
||||
p, *, *, GET, /api/get-default-providers, *, *
|
||||
p, *, *, GET, /api/get-resources, *, *
|
||||
p, *, *, GET, /api/get-records, *, *
|
||||
p, *, *, GET, /api/get-product, *, *
|
||||
p, *, *, GET, /api/get-products, *, *
|
||||
p, *, *, POST, /api/buy-product, *, *
|
||||
p, *, *, GET, /api/get-order, *, *
|
||||
p, *, *, GET, /api/get-orders, *, *
|
||||
p, *, *, GET, /api/get-user-orders, *, *
|
||||
p, *, *, GET, /api/get-payment, *, *
|
||||
p, *, *, POST, /api/update-payment, *, *
|
||||
p, *, *, POST, /api/invoice-payment, *, *
|
||||
p, *, *, POST, /api/notify-payment, *, *
|
||||
p, *, *, POST, /api/place-order, *, *
|
||||
p, *, *, POST, /api/cancel-order, *, *
|
||||
p, *, *, POST, /api/pay-order, *, *
|
||||
p, *, *, POST, /api/upload-avatar, *, *
|
||||
p, *, *, POST, /api/unlink, *, *
|
||||
p, *, *, POST, /api/set-password, *, *
|
||||
p, *, *, POST, /api/send-verification-code, *, *
|
||||
p, *, *, GET, /api/get-captcha, *, *
|
||||
p, *, *, POST, /api/verify-captcha, *, *
|
||||
p, *, *, POST, /api/verify-code, *, *
|
||||
p, *, *, POST, /api/v1/traces, *, *
|
||||
p, *, *, POST, /api/v1/metrics, *, *
|
||||
p, *, *, POST, /api/v1/logs, *, *
|
||||
p, *, *, GET, /api/get-human-check, *, *
|
||||
p, *, *, POST, /api/reset-email-or-phone, *, *
|
||||
p, *, *, POST, /api/upload-resource, *, *
|
||||
p, *, *, GET, /.well-known/openid-configuration, *, *
|
||||
p, *, *, GET, /.well-known/oauth-authorization-server, *, *
|
||||
p, *, *, GET, /.well-known/oauth-protected-resource, *, *
|
||||
p, *, *, GET, /.well-known/webfinger, *, *
|
||||
p, *, *, *, /.well-known/jwks, *, *
|
||||
p, *, *, GET, /.well-known/:application/openid-configuration, *, *
|
||||
p, *, *, GET, /.well-known/:application/oauth-authorization-server, *, *
|
||||
p, *, *, GET, /.well-known/:application/oauth-protected-resource, *, *
|
||||
p, *, *, GET, /.well-known/:application/webfinger, *, *
|
||||
p, *, *, *, /.well-known/:application/jwks, *, *
|
||||
p, *, *, GET, /api/get-saml-login, *, *
|
||||
p, *, *, POST, /api/acs, *, *
|
||||
p, *, *, GET, /api/saml/metadata, *, *
|
||||
p, *, *, *, /api/saml/redirect, *, *
|
||||
p, *, *, *, /cas, *, *
|
||||
p, *, *, *, /scim, *, *
|
||||
p, *, *, *, /api/webauthn, *, *
|
||||
p, *, *, GET, /api/get-release, *, *
|
||||
p, *, *, GET, /api/get-default-application, *, *
|
||||
p, *, *, GET, /api/get-prometheus-info, *, *
|
||||
p, *, *, *, /api/metrics, *, *
|
||||
p, *, *, GET, /api/get-pricing, *, *
|
||||
p, *, *, GET, /api/get-plan, *, *
|
||||
p, *, *, GET, /api/get-subscription, *, *
|
||||
p, *, *, GET, /api/get-transactions, *, *
|
||||
p, *, *, GET, /api/get-transaction, *, *
|
||||
p, *, *, GET, /api/get-provider, *, *
|
||||
p, *, *, GET, /api/get-organization-names, *, *
|
||||
p, *, *, GET, /api/get-all-objects, *, *
|
||||
p, *, *, GET, /api/get-all-actions, *, *
|
||||
p, *, *, GET, /api/get-all-roles, *, *
|
||||
p, *, *, GET, /api/run-casbin-command, *, *
|
||||
p, *, *, POST, /api/refresh-engines, *, *
|
||||
p, *, *, GET, /api/get-invitation-info, *, *
|
||||
p, *, *, GET, /api/faceid-signin-begin, *, *
|
||||
p, *, *, GET, /api/kerberos-login, *, *
|
||||
p, *, *, POST, /api/paypal, *, *
|
||||
p, *, *, GET, /api/success-pay, *, *
|
||||
p, *, *, GET, /api/get-application-clientId, *, *
|
||||
`
|
||||
|
||||
sa := stringadapter.NewAdapter(ruleText)
|
||||
// load all rules from string adapter to enforcer's memory
|
||||
err = sa.LoadPolicy(Enforcer.GetModel())
|
||||
err := sa.LoadPolicy(Enforcer.GetModel())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -146,79 +115,11 @@ p, *, *, GET, /api/kerberos-login, *, *
|
||||
}
|
||||
}
|
||||
|
||||
func IsAllowed(subOwner string, subName string, method string, urlPath string, objOwner string, objName string, extraInfo map[string]interface{}) bool {
|
||||
if urlPath == "/api/mcp" {
|
||||
if detailPath, ok := extraInfo["detailPathUrl"].(string); ok {
|
||||
if detailPath == "initialize" || detailPath == "notifications/initialized" || detailPath == "ping" || detailPath == "tools/list" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if conf.IsDemoMode() {
|
||||
if !isAllowedInDemoMode(subOwner, subName, method, urlPath, objOwner, objName) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
user, err := object.GetUser(util.GetId(subOwner, subName))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if subOwner == "app" {
|
||||
return true
|
||||
}
|
||||
|
||||
if user != nil {
|
||||
if user.IsDeleted {
|
||||
return false
|
||||
}
|
||||
|
||||
if user.IsGlobalAdmin() {
|
||||
return true
|
||||
}
|
||||
|
||||
if user.IsAdmin && subOwner == objOwner {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func IsAllowed(subOwner string, subName string, method string, urlPath string, objOwner string, objName string) bool {
|
||||
res, err := Enforcer.Enforce(subOwner, subName, method, urlPath, objOwner, objName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if !res {
|
||||
res, err = object.CheckApiPermission(util.GetId(subOwner, subName), objOwner, urlPath, method)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func isAllowedInDemoMode(subOwner string, subName string, method string, urlPath string, objOwner string, objName string) bool {
|
||||
if method == "POST" {
|
||||
if strings.HasPrefix(urlPath, "/api/login") || urlPath == "/api/logout" || urlPath == "/api/sso-logout" || urlPath == "/api/signup" || urlPath == "/api/callback" || urlPath == "/api/send-verification-code" || urlPath == "/api/send-email" || urlPath == "/api/verify-captcha" || urlPath == "/api/verify-code" || urlPath == "/api/check-user-password" || strings.HasPrefix(urlPath, "/api/mfa/") || urlPath == "/api/webhook" || urlPath == "/api/get-qrcode" || urlPath == "/api/refresh-engines" {
|
||||
return true
|
||||
} else if urlPath == "/api/update-user" {
|
||||
// Allow ordinary users to update their own information
|
||||
if (subOwner == objOwner && subName == objName || subOwner == "app") && !(subOwner == "built-in" && subName == "admin") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
} else if urlPath == "/api/upload-resource" || urlPath == "/api/add-transaction" {
|
||||
if subOwner == "app" && subName == "app-casibase" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// If method equals GET
|
||||
return true
|
||||
}
|
||||
|
||||
13
build.sh
13
build.sh
@@ -1,13 +0,0 @@
|
||||
#!/bin/bash
|
||||
#try to connect to google to determine whether user need to use proxy
|
||||
curl www.google.com -o /dev/null --connect-timeout 5 2> /dev/null
|
||||
if [ $? == 0 ]
|
||||
then
|
||||
echo "Successfully connected to Google, no need to use Go proxy"
|
||||
else
|
||||
echo "Google is blocked, Go proxy is enabled: GOPROXY=https://goproxy.cn,direct"
|
||||
export GOPROXY="https://goproxy.cn,direct"
|
||||
fi
|
||||
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o server_linux_amd64 .
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-w -s" -o server_linux_arm64 .
|
||||
@@ -1,135 +0,0 @@
|
||||
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package captcha
|
||||
|
||||
import (
|
||||
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||
openapiutil "github.com/alibabacloud-go/openapi-util/service"
|
||||
teaUtil "github.com/alibabacloud-go/tea-utils/v2/service"
|
||||
"github.com/alibabacloud-go/tea/tea"
|
||||
)
|
||||
|
||||
const AliyunCaptchaVerifyUrl = "captcha.cn-shanghai.aliyuncs.com"
|
||||
|
||||
type VerifyCaptchaRequest struct {
|
||||
CaptchaVerifyParam *string `json:"CaptchaVerifyParam,omitempty" xml:"CaptchaVerifyParam,omitempty"`
|
||||
SceneId *string `json:"SceneId,omitempty" xml:"SceneId,omitempty"`
|
||||
}
|
||||
|
||||
type VerifyCaptchaResponseBodyResult struct {
|
||||
VerifyResult *bool `json:"VerifyResult,omitempty" xml:"VerifyResult,omitempty"`
|
||||
}
|
||||
|
||||
type VerifyCaptchaResponseBody struct {
|
||||
Code *string `json:"Code,omitempty" xml:"Code,omitempty"`
|
||||
Message *string `json:"Message,omitempty" xml:"Message,omitempty"`
|
||||
// Id of the request
|
||||
RequestId *string `json:"RequestId,omitempty" xml:"RequestId,omitempty"`
|
||||
Result *VerifyCaptchaResponseBodyResult `json:"Result,omitempty" xml:"Result,omitempty" type:"Struct"`
|
||||
Success *bool `json:"Success,omitempty" xml:"Success,omitempty"`
|
||||
}
|
||||
|
||||
type VerifyIntelligentCaptchaResponseBodyResult struct {
|
||||
VerifyCode *string `json:"VerifyCode,omitempty" xml:"VerifyCode,omitempty"`
|
||||
VerifyResult *bool `json:"VerifyResult,omitempty" xml:"VerifyResult,omitempty"`
|
||||
}
|
||||
|
||||
type VerifyIntelligentCaptchaResponseBody struct {
|
||||
Code *string `json:"Code,omitempty" xml:"Code,omitempty"`
|
||||
Message *string `json:"Message,omitempty" xml:"Message,omitempty"`
|
||||
// Id of the request
|
||||
RequestId *string `json:"RequestId,omitempty" xml:"RequestId,omitempty"`
|
||||
Result *VerifyIntelligentCaptchaResponseBodyResult `json:"Result,omitempty" xml:"Result,omitempty" type:"Struct"`
|
||||
Success *bool `json:"Success,omitempty" xml:"Success,omitempty"`
|
||||
}
|
||||
|
||||
type VerifyIntelligentCaptchaResponse struct {
|
||||
Headers map[string]*string `json:"headers,omitempty" xml:"headers,omitempty" require:"true"`
|
||||
StatusCode *int32 `json:"statusCode,omitempty" xml:"statusCode,omitempty" require:"true"`
|
||||
Body *VerifyIntelligentCaptchaResponseBody `json:"body,omitempty" xml:"body,omitempty" require:"true"`
|
||||
}
|
||||
type AliyunCaptchaProvider struct{}
|
||||
|
||||
func NewAliyunCaptchaProvider() *AliyunCaptchaProvider {
|
||||
captcha := &AliyunCaptchaProvider{}
|
||||
return captcha
|
||||
}
|
||||
|
||||
func (captcha *AliyunCaptchaProvider) VerifyCaptcha(token, clientId, clientSecret, clientId2 string) (bool, error) {
|
||||
config := &openapi.Config{}
|
||||
|
||||
config.Endpoint = tea.String(AliyunCaptchaVerifyUrl)
|
||||
config.ConnectTimeout = tea.Int(5000)
|
||||
config.ReadTimeout = tea.Int(5000)
|
||||
config.AccessKeyId = tea.String(clientId)
|
||||
config.AccessKeySecret = tea.String(clientSecret)
|
||||
|
||||
client := new(openapi.Client)
|
||||
err := client.Init(config)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
request := VerifyCaptchaRequest{CaptchaVerifyParam: tea.String(token), SceneId: tea.String(clientId2)}
|
||||
|
||||
err = teaUtil.ValidateModel(&request)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
runtime := &teaUtil.RuntimeOptions{}
|
||||
|
||||
body := map[string]interface{}{}
|
||||
if !tea.BoolValue(teaUtil.IsUnset(request.CaptchaVerifyParam)) {
|
||||
body["CaptchaVerifyParam"] = request.CaptchaVerifyParam
|
||||
}
|
||||
|
||||
if !tea.BoolValue(teaUtil.IsUnset(request.SceneId)) {
|
||||
body["SceneId"] = request.SceneId
|
||||
}
|
||||
|
||||
req := &openapi.OpenApiRequest{
|
||||
Body: openapiutil.ParseToMap(body),
|
||||
}
|
||||
params := &openapi.Params{
|
||||
Action: tea.String("VerifyIntelligentCaptcha"),
|
||||
Version: tea.String("2023-03-05"),
|
||||
Protocol: tea.String("HTTPS"),
|
||||
Pathname: tea.String("/"),
|
||||
Method: tea.String("POST"),
|
||||
AuthType: tea.String("AK"),
|
||||
Style: tea.String("RPC"),
|
||||
ReqBodyType: tea.String("formData"),
|
||||
BodyType: tea.String("json"),
|
||||
}
|
||||
|
||||
res := &VerifyIntelligentCaptchaResponse{}
|
||||
|
||||
resBody, err := client.CallApi(params, req, runtime)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
err = tea.Convert(resBody, &res)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if res.Body.Result.VerifyResult != nil && *res.Body.Result.VerifyResult {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package captcha
|
||||
|
||||
import "github.com/casdoor/casdoor/object"
|
||||
|
||||
type DefaultCaptchaProvider struct{}
|
||||
|
||||
func NewDefaultCaptchaProvider() *DefaultCaptchaProvider {
|
||||
captcha := &DefaultCaptchaProvider{}
|
||||
return captcha
|
||||
}
|
||||
|
||||
func (captcha *DefaultCaptchaProvider) VerifyCaptcha(token, clientId, clientSecret, clientId2 string) (bool, error) {
|
||||
return object.VerifyCaptcha(clientSecret, token), nil
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package captcha
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
const GEETESTCaptchaVerifyUrl = "http://gcaptcha4.geetest.com/validate"
|
||||
|
||||
type GEETESTCaptchaProvider struct{}
|
||||
|
||||
func NewGEETESTCaptchaProvider() *GEETESTCaptchaProvider {
|
||||
captcha := &GEETESTCaptchaProvider{}
|
||||
return captcha
|
||||
}
|
||||
|
||||
func (captcha *GEETESTCaptchaProvider) VerifyCaptcha(token, clientId, clientSecret, clientId2 string) (bool, error) {
|
||||
pathData, err := url.ParseQuery(token)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
signToken := util.GetHmacSha256(clientSecret, pathData["lot_number"][0])
|
||||
|
||||
formData := make(url.Values)
|
||||
formData["lot_number"] = []string{pathData["lot_number"][0]}
|
||||
formData["captcha_output"] = []string{pathData["captcha_output"][0]}
|
||||
formData["pass_token"] = []string{pathData["pass_token"][0]}
|
||||
formData["gen_time"] = []string{pathData["gen_time"][0]}
|
||||
formData["sign_token"] = []string{signToken}
|
||||
captchaId := pathData["captcha_id"][0]
|
||||
|
||||
cli := http.Client{Timeout: time.Second * 5}
|
||||
resp, err := cli.PostForm(fmt.Sprintf("%s?captcha_id=%s", GEETESTCaptchaVerifyUrl, captchaId), formData)
|
||||
if err != nil || resp.StatusCode != 200 {
|
||||
return false, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
type captchaResponse struct {
|
||||
Result string `json:"result"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
captchaResp := &captchaResponse{}
|
||||
err = json.Unmarshal(body, captchaResp)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if captchaResp.Result == "success" {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, errors.New(captchaResp.Reason)
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package captcha
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const HCaptchaVerifyUrl = "https://hcaptcha.com/siteverify"
|
||||
|
||||
type HCaptchaProvider struct{}
|
||||
|
||||
func NewHCaptchaProvider() *HCaptchaProvider {
|
||||
captcha := &HCaptchaProvider{}
|
||||
return captcha
|
||||
}
|
||||
|
||||
func (captcha *HCaptchaProvider) VerifyCaptcha(token, clientId, clientSecret, clientId2 string) (bool, error) {
|
||||
reqData := url.Values{
|
||||
"secret": {clientSecret},
|
||||
"response": {token},
|
||||
}
|
||||
resp, err := http.PostForm(HCaptchaVerifyUrl, reqData)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
type captchaResponse struct {
|
||||
Success bool `json:"success"`
|
||||
ErrorCodes []string `json:"error-codes"`
|
||||
}
|
||||
captchaResp := &captchaResponse{}
|
||||
err = json.Unmarshal(body, captchaResp)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if len(captchaResp.ErrorCodes) > 0 {
|
||||
return false, errors.New(strings.Join(captchaResp.ErrorCodes, ","))
|
||||
}
|
||||
|
||||
return captchaResp.Success, nil
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package captcha
|
||||
|
||||
import "fmt"
|
||||
|
||||
type CaptchaProvider interface {
|
||||
VerifyCaptcha(token, clientId, clientSecret, clientId2 string) (bool, error)
|
||||
}
|
||||
|
||||
func GetCaptchaProvider(captchaType string) CaptchaProvider {
|
||||
switch captchaType {
|
||||
case "Default":
|
||||
return NewDefaultCaptchaProvider()
|
||||
case "reCAPTCHA":
|
||||
return NewReCaptchaProvider()
|
||||
case "reCAPTCHA v2":
|
||||
return NewReCaptchaProvider()
|
||||
case "reCAPTCHA v3":
|
||||
return NewReCaptchaProvider()
|
||||
case "Aliyun Captcha":
|
||||
return NewAliyunCaptchaProvider()
|
||||
case "hCaptcha":
|
||||
return NewHCaptchaProvider()
|
||||
case "GEETEST":
|
||||
return NewGEETESTCaptchaProvider()
|
||||
case "Cloudflare Turnstile":
|
||||
return NewCloudflareTurnstileProvider()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func VerifyCaptchaByCaptchaType(captchaType, token, clientId, clientSecret, clientId2 string) (bool, error) {
|
||||
provider := GetCaptchaProvider(captchaType)
|
||||
if provider == nil {
|
||||
return false, fmt.Errorf("invalid captcha provider: %s", captchaType)
|
||||
}
|
||||
|
||||
return provider.VerifyCaptcha(token, clientId, clientSecret, clientId2)
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package captcha
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const ReCaptchaVerifyUrl = "https://recaptcha.net/recaptcha/api/siteverify"
|
||||
|
||||
type ReCaptchaProvider struct{}
|
||||
|
||||
func NewReCaptchaProvider() *ReCaptchaProvider {
|
||||
captcha := &ReCaptchaProvider{}
|
||||
return captcha
|
||||
}
|
||||
|
||||
func (captcha *ReCaptchaProvider) VerifyCaptcha(token, clientId, clientSecret, clientId2 string) (bool, error) {
|
||||
reqData := url.Values{
|
||||
"secret": {clientSecret},
|
||||
"response": {token},
|
||||
}
|
||||
resp, err := http.PostForm(ReCaptchaVerifyUrl, reqData)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
type captchaResponse struct {
|
||||
Success bool `json:"success"`
|
||||
ErrorCodes []string `json:"error-codes"`
|
||||
}
|
||||
captchaResp := &captchaResponse{}
|
||||
err = json.Unmarshal(body, captchaResp)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if len(captchaResp.ErrorCodes) > 0 {
|
||||
return false, errors.New(strings.Join(captchaResp.ErrorCodes, ","))
|
||||
}
|
||||
|
||||
return captchaResp.Success, nil
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package captcha
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const CloudflareTurnstileVerifyUrl = "https://challenges.cloudflare.com/turnstile/v0/siteverify"
|
||||
|
||||
type CloudflareTurnstileProvider struct{}
|
||||
|
||||
func NewCloudflareTurnstileProvider() *CloudflareTurnstileProvider {
|
||||
captcha := &CloudflareTurnstileProvider{}
|
||||
return captcha
|
||||
}
|
||||
|
||||
func (captcha *CloudflareTurnstileProvider) VerifyCaptcha(token, clientId, clientSecret, clientId2 string) (bool, error) {
|
||||
reqData := url.Values{
|
||||
"secret": {clientSecret},
|
||||
"response": {token},
|
||||
}
|
||||
resp, err := http.PostForm(CloudflareTurnstileVerifyUrl, reqData)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
type captchaResponse struct {
|
||||
Success bool `json:"success"`
|
||||
ErrorCodes []string `json:"error-codes"`
|
||||
}
|
||||
captchaResp := &captchaResponse{}
|
||||
err = json.Unmarshal(body, captchaResp)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if len(captchaResp.ErrorCodes) > 0 {
|
||||
return false, errors.New(strings.Join(captchaResp.ErrorCodes, ","))
|
||||
}
|
||||
|
||||
return captchaResp.Success, nil
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package certificate
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
|
||||
"github.com/casbin/lego/v4/acme"
|
||||
"github.com/casbin/lego/v4/certcrypto"
|
||||
"github.com/casbin/lego/v4/lego"
|
||||
"github.com/casbin/lego/v4/registration"
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
)
|
||||
|
||||
type Account struct {
|
||||
Email string
|
||||
Registration *registration.Resource
|
||||
Key crypto.PrivateKey
|
||||
}
|
||||
|
||||
/** Implementation of the registration.User interface **/
|
||||
|
||||
// GetEmail returns the email address for the account.
|
||||
func (a *Account) GetEmail() string {
|
||||
return a.Email
|
||||
}
|
||||
|
||||
// GetPrivateKey returns the private RSA account key.
|
||||
func (a *Account) GetPrivateKey() crypto.PrivateKey {
|
||||
return a.Key
|
||||
}
|
||||
|
||||
// GetRegistration returns the server registration.
|
||||
func (a *Account) GetRegistration() *registration.Resource {
|
||||
return a.Registration
|
||||
}
|
||||
|
||||
func getLegoClientAndAccount(email string, privateKey string, devMode bool) (*lego.Client, *Account, error) {
|
||||
key, err := decodeEccKey(privateKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
account := &Account{
|
||||
Email: email,
|
||||
Key: key,
|
||||
}
|
||||
|
||||
config := lego.NewConfig(account)
|
||||
if devMode {
|
||||
config.CADirURL = lego.LEDirectoryStaging
|
||||
} else {
|
||||
config.CADirURL = lego.LEDirectoryProduction
|
||||
}
|
||||
|
||||
config.Certificate.KeyType = certcrypto.RSA2048
|
||||
config.HTTPClient = proxy.ProxyHttpClient
|
||||
|
||||
client, err := lego.NewClient(config)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return client, account, err
|
||||
}
|
||||
|
||||
// GetAcmeClient Incoming an email ,a privatekey and a Boolean value that controls the opening of the test environment
|
||||
// When this function is started for the first time, it will initialize the account-related configuration,
|
||||
// After initializing the configuration, It will try to obtain an account based on the private key,
|
||||
// if it fails, it will create an account based on the private key.
|
||||
// This account will be used during the running of the program
|
||||
func GetAcmeClient(email string, privateKey string, devMode bool) (*lego.Client, error) {
|
||||
// Create a user. New accounts need an email and private key to start.
|
||||
client, account, err := getLegoClientAndAccount(email, privateKey, devMode)
|
||||
|
||||
// try to obtain an account based on the private key
|
||||
account.Registration, err = client.Registration.ResolveAccountByKey()
|
||||
if err != nil {
|
||||
acmeError, ok := err.(*acme.ProblemDetails)
|
||||
if !ok {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if acmeError.Type != "urn:ietf:params:acme:error:accountDoesNotExist" {
|
||||
return nil, acmeError
|
||||
}
|
||||
|
||||
// Failed to get account, so create an account based on the private key.
|
||||
account.Registration, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package certificate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/casbin/lego/v4/certificate"
|
||||
"github.com/casbin/lego/v4/challenge/dns01"
|
||||
"github.com/casbin/lego/v4/cmd"
|
||||
"github.com/casbin/lego/v4/lego"
|
||||
"github.com/casbin/lego/v4/providers/dns/alidns"
|
||||
"github.com/casbin/lego/v4/providers/dns/godaddy"
|
||||
)
|
||||
|
||||
type AliConf struct {
|
||||
Domains []string // The domain names for which you want to apply for a certificate
|
||||
AccessKey string // Aliyun account's AccessKey, if this is not empty, Secret is required.
|
||||
Secret string
|
||||
RAMRole string // Use Ramrole to control aliyun account
|
||||
SecurityToken string // Optional
|
||||
Path string // The path to store cert file
|
||||
Timeout int // Maximum waiting time for certificate application, in minutes
|
||||
}
|
||||
|
||||
type GodaddyConf struct {
|
||||
Domains []string // The domain names for which you want to apply for a certificate
|
||||
APIKey string // GoDaddy account's API Key
|
||||
APISecret string
|
||||
Path string // The path to store cert file
|
||||
Timeout int // Maximum waiting time for certificate application, in minutes
|
||||
}
|
||||
|
||||
// getCert Verify domain ownership, then obtain a certificate, and finally store it locally.
|
||||
// Need to pass in an AliConf struct, some parameters are required, other parameters can be left blank
|
||||
func getAliCert(client *lego.Client, conf AliConf) (string, string, error) {
|
||||
if conf.Timeout <= 0 {
|
||||
conf.Timeout = 3
|
||||
}
|
||||
|
||||
config := alidns.NewDefaultConfig()
|
||||
config.PropagationTimeout = time.Duration(conf.Timeout) * time.Minute
|
||||
config.APIKey = conf.AccessKey
|
||||
config.SecretKey = conf.Secret
|
||||
config.RAMRole = conf.RAMRole
|
||||
config.SecurityToken = conf.SecurityToken
|
||||
|
||||
dnsProvider, err := alidns.NewDNSProvider(config)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
// Choose a local DNS service provider to increase the authentication speed
|
||||
servers := []string{"223.5.5.5:53"}
|
||||
err = client.Challenge.SetDNS01Provider(dnsProvider, dns01.CondOption(len(servers) > 0, dns01.AddRecursiveNameservers(dns01.ParseNameservers(servers))), dns01.DisableCompletePropagationRequirement())
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
// Obtain the certificate
|
||||
request := certificate.ObtainRequest{
|
||||
Domains: conf.Domains,
|
||||
Bundle: true,
|
||||
}
|
||||
cert, err := client.Certificate.Obtain(request)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return string(cert.Certificate), string(cert.PrivateKey), nil
|
||||
}
|
||||
|
||||
func getGoDaddyCert(client *lego.Client, conf GodaddyConf) (string, string, error) {
|
||||
if conf.Timeout <= 0 {
|
||||
conf.Timeout = 3
|
||||
}
|
||||
|
||||
config := godaddy.NewDefaultConfig()
|
||||
config.PropagationTimeout = time.Duration(conf.Timeout) * time.Minute
|
||||
config.PollingInterval = time.Duration(conf.Timeout) * time.Minute / 9
|
||||
config.APIKey = conf.APIKey
|
||||
config.APISecret = conf.APISecret
|
||||
|
||||
dnsProvider, err := godaddy.NewDNSProvider(config)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
// Choose a local DNS service provider to increase the authentication speed
|
||||
servers := []string{"223.5.5.5:53"}
|
||||
err = client.Challenge.SetDNS01Provider(dnsProvider, dns01.CondOption(len(servers) > 0, dns01.AddRecursiveNameservers(dns01.ParseNameservers(servers))), dns01.DisableCompletePropagationRequirement())
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
// Obtain the certificate
|
||||
request := certificate.ObtainRequest{
|
||||
Domains: conf.Domains,
|
||||
Bundle: true,
|
||||
}
|
||||
cert, err := client.Certificate.Obtain(request)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return string(cert.Certificate), string(cert.PrivateKey), nil
|
||||
}
|
||||
|
||||
func ObtainCertificateAli(client *lego.Client, domain string, accessKey string, accessSecret string) (string, string, error) {
|
||||
conf := AliConf{
|
||||
Domains: []string{fmt.Sprintf("*.%s", domain), domain},
|
||||
AccessKey: accessKey,
|
||||
Secret: accessSecret,
|
||||
RAMRole: "",
|
||||
SecurityToken: "",
|
||||
Path: "",
|
||||
Timeout: 3,
|
||||
}
|
||||
return getAliCert(client, conf)
|
||||
}
|
||||
|
||||
func ObtainCertificateGoDaddy(client *lego.Client, domain string, accessKey string, accessSecret string) (string, string, error) {
|
||||
conf := GodaddyConf{
|
||||
Domains: []string{fmt.Sprintf("*.%s", domain), domain},
|
||||
APIKey: accessKey,
|
||||
APISecret: accessSecret,
|
||||
Path: "",
|
||||
Timeout: 3,
|
||||
}
|
||||
return getGoDaddyCert(client, conf)
|
||||
}
|
||||
|
||||
func SaveCert(path, filename string, cert *certificate.Resource) {
|
||||
// Store the certificate file locally
|
||||
certsStorage := cmd.NewCertificatesStorageLib(path, filename, true)
|
||||
certsStorage.CreateRootFolder()
|
||||
certsStorage.SaveResource(cert)
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package certificate
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// generateEccKey generates a public and private key pair.(NIST P-256)
|
||||
func generateEccKey() (*ecdsa.PrivateKey, error) {
|
||||
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
}
|
||||
|
||||
// encodeEccKey Return the input private key object as string type private key
|
||||
func encodeEccKey(privateKey *ecdsa.PrivateKey) (string, error) {
|
||||
x509Encoded, err := x509.MarshalECPrivateKey(privateKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
pemEncoded := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: x509Encoded})
|
||||
return string(pemEncoded), nil
|
||||
}
|
||||
|
||||
// decodeEccKey Return the entered private key string as a private key object that can be used
|
||||
func decodeEccKey(pemEncoded string) (*ecdsa.PrivateKey, error) {
|
||||
block, _ := pem.Decode([]byte(pemEncoded))
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("invalid PEM-encoded EC private key")
|
||||
}
|
||||
x509Encoded := block.Bytes
|
||||
privateKey, err := x509.ParseECPrivateKey(x509Encoded)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return privateKey, nil
|
||||
}
|
||||
@@ -1,37 +1,14 @@
|
||||
appname = casdoor
|
||||
httpport = 8000
|
||||
runmode = dev
|
||||
copyrequestbody = true
|
||||
driverName = postgres
|
||||
dataSourceName = "user=casdoor password=casdoor_dev host=localhost port=5434 sslmode=disable dbname=casdoor"
|
||||
dbName = casdoor
|
||||
tableNamePrefix =
|
||||
showSql = false
|
||||
redisEndpoint =
|
||||
defaultStorageProvider =
|
||||
isCloudIntranet = false
|
||||
authState = "casdoor"
|
||||
socks5Proxy = ""
|
||||
verificationCodeTimeout = 10
|
||||
initScore = 0
|
||||
logPostOnly = true
|
||||
isUsernameLowered = false
|
||||
origin = "http://localhost:8000"
|
||||
originFrontend = "http://localhost:7001"
|
||||
staticBaseUrl = "https://cdn.casbin.org"
|
||||
isDemoMode = false
|
||||
batchSize = 100
|
||||
showGithubCorner = false
|
||||
forceLanguage = ""
|
||||
defaultLanguage = "ru"
|
||||
enableErrorMask = false
|
||||
enableGzip = true
|
||||
ldapServerPort = 389
|
||||
ldapsServerPort = 636
|
||||
radiusServerPort = 1812
|
||||
radiusDefaultOrganization = "built-in"
|
||||
radiusSecret = "secret"
|
||||
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
|
||||
logConfig = {"adapter":"file", "filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}
|
||||
initDataNewOnly = false
|
||||
initDataFile = "./init_data.json"
|
||||
appname = casdoor
|
||||
httpport = 8000
|
||||
runmode = dev
|
||||
SessionOn = true
|
||||
copyrequestbody = true
|
||||
driverName = mysql
|
||||
dataSourceName = root:123@tcp(localhost:3306)/
|
||||
dbName = casdoor
|
||||
redisEndpoint =
|
||||
defaultStorageProvider =
|
||||
authState = "casdoor"
|
||||
httpProxy = "127.0.0.1:10808"
|
||||
verificationCodeTimeout = 10
|
||||
initScore = 2000
|
||||
@@ -1,43 +0,0 @@
|
||||
appname = casdoor
|
||||
httpport = 8000
|
||||
runmode = dev
|
||||
copyrequestbody = true
|
||||
driverName = mysql
|
||||
dataSourceName = root:123456@tcp(localhost:3306)/
|
||||
dbName = casdoor
|
||||
tableNamePrefix =
|
||||
showSql = false
|
||||
redisEndpoint =
|
||||
defaultStorageProvider =
|
||||
isCloudIntranet = false
|
||||
authState = "casdoor"
|
||||
socks5Proxy = "127.0.0.1:10808"
|
||||
verificationCodeTimeout = 10
|
||||
initScore = 0
|
||||
logPostOnly = true
|
||||
isUsernameLowered = false
|
||||
origin =
|
||||
originFrontend =
|
||||
staticBaseUrl = "https://cdn.casbin.org"
|
||||
isDemoMode = false
|
||||
batchSize = 100
|
||||
showGithubCorner = false
|
||||
forceLanguage = ""
|
||||
defaultLanguage = "en"
|
||||
aiAssistantUrl = "https://ai.casbin.com"
|
||||
defaultApplication = "app-built-in"
|
||||
maxItemsForFlatMenu = 7
|
||||
enableErrorMask = false
|
||||
enableGzip = true
|
||||
inactiveTimeoutMinutes =
|
||||
ldapServerPort = 389
|
||||
ldapsCertId = ""
|
||||
ldapsServerPort = 636
|
||||
radiusServerPort = 1812
|
||||
radiusDefaultOrganization = "built-in"
|
||||
radiusSecret = "secret"
|
||||
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
|
||||
logConfig = {"adapter":"file", "filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}
|
||||
initDataNewOnly = false
|
||||
initDataFile = "./init_data.json"
|
||||
frontendBaseDir = "../cc_0"
|
||||
@@ -1,37 +0,0 @@
|
||||
appname = casdoor
|
||||
httpport = 8000
|
||||
runmode = dev
|
||||
copyrequestbody = true
|
||||
driverName = postgres
|
||||
dataSourceName = "user=casdoor password=casdoor_dev host=localhost port=5434 sslmode=disable dbname=casdoor"
|
||||
dbName = casdoor
|
||||
tableNamePrefix =
|
||||
showSql = false
|
||||
redisEndpoint =
|
||||
defaultStorageProvider =
|
||||
isCloudIntranet = false
|
||||
authState = "casdoor"
|
||||
socks5Proxy = ""
|
||||
verificationCodeTimeout = 10
|
||||
initScore = 0
|
||||
logPostOnly = true
|
||||
isUsernameLowered = false
|
||||
origin = "http://localhost:8000"
|
||||
originFrontend = "http://localhost:7001"
|
||||
staticBaseUrl = "https://cdn.casbin.org"
|
||||
isDemoMode = false
|
||||
batchSize = 100
|
||||
showGithubCorner = false
|
||||
forceLanguage = ""
|
||||
defaultLanguage = "ru"
|
||||
enableErrorMask = false
|
||||
enableGzip = true
|
||||
ldapServerPort = 389
|
||||
ldapsServerPort = 636
|
||||
radiusServerPort = 1812
|
||||
radiusDefaultOrganization = "built-in"
|
||||
radiusSecret = "secret"
|
||||
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
|
||||
logConfig = {"adapter":"file", "filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}
|
||||
initDataNewOnly = false
|
||||
initDataFile = "./init_data.json"
|
||||
121
conf/conf.go
121
conf/conf.go
@@ -1,121 +0,0 @@
|
||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package conf
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/beego/beego/v2/server/web"
|
||||
)
|
||||
|
||||
//go:embed waf.conf
|
||||
var WafConf string
|
||||
|
||||
func init() {
|
||||
// this array contains the beego configuration items that may be modified via env
|
||||
presetConfigItems := []string{"httpport", "appname"}
|
||||
for _, key := range presetConfigItems {
|
||||
if value, ok := os.LookupEnv(key); ok {
|
||||
err := web.AppConfig.Set(key, value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetConfigString(key string) string {
|
||||
if value, ok := os.LookupEnv(key); ok {
|
||||
return value
|
||||
}
|
||||
|
||||
res, _ := web.AppConfig.String(key)
|
||||
if res == "" {
|
||||
if key == "staticBaseUrl" {
|
||||
res = "https://cdn.casbin.org"
|
||||
} else if key == "logConfig" {
|
||||
appname, _ := web.AppConfig.String("appname")
|
||||
res = fmt.Sprintf("{\"filename\": \"logs/%s.log\", \"maxdays\":99999, \"perm\":\"0770\"}", appname)
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func GetConfigBool(key string) bool {
|
||||
value := GetConfigString(key)
|
||||
if value == "true" {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func GetConfigInt64(key string) (int64, error) {
|
||||
value := GetConfigString(key)
|
||||
num, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("GetConfigInt64(%s) error, %s", key, err.Error())
|
||||
}
|
||||
|
||||
return num, nil
|
||||
}
|
||||
|
||||
func GetConfigDataSourceName() string {
|
||||
dataSourceName := GetConfigString("dataSourceName")
|
||||
return ReplaceDataSourceNameByDocker(dataSourceName)
|
||||
}
|
||||
|
||||
func ReplaceDataSourceNameByDocker(dataSourceName string) string {
|
||||
runningInDocker := os.Getenv("RUNNING_IN_DOCKER")
|
||||
if runningInDocker == "true" {
|
||||
// https://stackoverflow.com/questions/48546124/what-is-linux-equivalent-of-host-docker-internal
|
||||
if runtime.GOOS == "linux" {
|
||||
dataSourceName = strings.ReplaceAll(dataSourceName, "localhost", "172.17.0.1")
|
||||
} else {
|
||||
dataSourceName = strings.ReplaceAll(dataSourceName, "localhost", "host.docker.internal")
|
||||
}
|
||||
}
|
||||
return dataSourceName
|
||||
}
|
||||
|
||||
func GetLanguage(language string) string {
|
||||
if language == "" || language == "*" {
|
||||
return "en"
|
||||
}
|
||||
|
||||
if len(language) != 2 || language == "nu" {
|
||||
return "en"
|
||||
} else {
|
||||
return language
|
||||
}
|
||||
}
|
||||
|
||||
func IsDemoMode() bool {
|
||||
return strings.ToLower(GetConfigString("isDemoMode")) == "true"
|
||||
}
|
||||
|
||||
func GetConfigBatchSize() int {
|
||||
res, err := strconv.Atoi(GetConfigString("batchSize"))
|
||||
if err != nil {
|
||||
res = 100
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package conf
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/beego/beego/v2/server/web"
|
||||
)
|
||||
|
||||
type Quota struct {
|
||||
Organization int `json:"organization"`
|
||||
User int `json:"user"`
|
||||
Application int `json:"application"`
|
||||
Provider int `json:"provider"`
|
||||
}
|
||||
|
||||
var quota = &Quota{-1, -1, -1, -1}
|
||||
|
||||
func init() {
|
||||
initQuota()
|
||||
}
|
||||
|
||||
func initQuota() {
|
||||
res, _ := web.AppConfig.String("quota")
|
||||
if res != "" {
|
||||
err := json.Unmarshal([]byte(res), quota)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetConfigQuota() *Quota {
|
||||
return quota
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package conf
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/beego/beego/v2/server/web"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetConfString(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
description string
|
||||
input string
|
||||
expected interface{}
|
||||
}{
|
||||
{"Should be return casbin", "appname", "casbin"},
|
||||
{"Should be return 8000", "httpport", "8000"},
|
||||
{"Should be return value", "key", "value"},
|
||||
}
|
||||
|
||||
// do some set up job
|
||||
|
||||
os.Setenv("appname", "casbin")
|
||||
os.Setenv("key", "value")
|
||||
|
||||
err := web.LoadAppConfig("ini", "app.conf")
|
||||
assert.Nil(t, err)
|
||||
|
||||
for _, scenery := range scenarios {
|
||||
t.Run(scenery.description, func(t *testing.T) {
|
||||
actual := GetConfigString(scenery.input)
|
||||
assert.Equal(t, scenery.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetConfInt(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
description string
|
||||
input string
|
||||
expected interface{}
|
||||
}{
|
||||
{"Should be return 8000", "httpport", 8001},
|
||||
{"Should be return 8000", "verificationCodeTimeout", 10},
|
||||
}
|
||||
|
||||
// do some set up job
|
||||
os.Setenv("httpport", "8001")
|
||||
|
||||
err := web.LoadAppConfig("ini", "app.conf")
|
||||
assert.Nil(t, err)
|
||||
|
||||
for _, scenery := range scenarios {
|
||||
t.Run(scenery.description, func(t *testing.T) {
|
||||
actual, err := GetConfigInt64(scenery.input)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, scenery.expected, int(actual))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetConfBool(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
description string
|
||||
input string
|
||||
expected interface{}
|
||||
}{
|
||||
{"Should be return false", "copyrequestbody", true},
|
||||
}
|
||||
|
||||
err := web.LoadAppConfig("ini", "app.conf")
|
||||
assert.Nil(t, err)
|
||||
for _, scenery := range scenarios {
|
||||
t.Run(scenery.description, func(t *testing.T) {
|
||||
actual := GetConfigBool(scenery.input)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, scenery.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetConfigQuota(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
description string
|
||||
expected *Quota
|
||||
}{
|
||||
{"default", &Quota{-1, -1, -1, -1}},
|
||||
}
|
||||
|
||||
err := web.LoadAppConfig("ini", "app.conf")
|
||||
assert.Nil(t, err)
|
||||
for _, scenery := range scenarios {
|
||||
quota := GetConfigQuota()
|
||||
assert.Equal(t, scenery.expected, quota)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetConfigLogs(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
description string
|
||||
expected string
|
||||
}{
|
||||
{"Default log config", `{"adapter":"file", "filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}`},
|
||||
}
|
||||
|
||||
err := web.LoadAppConfig("ini", "app.conf")
|
||||
assert.Nil(t, err)
|
||||
for _, scenery := range scenarios {
|
||||
quota := GetConfigString("logConfig")
|
||||
assert.Equal(t, scenery.expected, quota)
|
||||
}
|
||||
}
|
||||
246
conf/waf.conf
246
conf/waf.conf
@@ -1,246 +0,0 @@
|
||||
# -- Rule engine initialization ----------------------------------------------
|
||||
|
||||
# Enable Coraza, attaching it to every transaction. Use detection
|
||||
# only to start with, because that minimises the chances of post-installation
|
||||
# disruption.
|
||||
#
|
||||
SecRuleEngine DetectionOnly
|
||||
|
||||
|
||||
# -- Request body handling ---------------------------------------------------
|
||||
|
||||
# Allow Coraza to access request bodies. If you don't, Coraza
|
||||
# won't be able to see any POST parameters, which opens a large security
|
||||
# hole for attackers to exploit.
|
||||
#
|
||||
SecRequestBodyAccess On
|
||||
|
||||
# Enable XML request body parser.
|
||||
# Initiate XML Processor in case of xml content-type
|
||||
#
|
||||
SecRule REQUEST_HEADERS:Content-Type "^(?:application(?:/soap\+|/)|text/)xml" \
|
||||
"id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML"
|
||||
|
||||
# Enable JSON request body parser.
|
||||
# Initiate JSON Processor in case of JSON content-type; change accordingly
|
||||
# if your application does not use 'application/json'
|
||||
#
|
||||
SecRule REQUEST_HEADERS:Content-Type "^application/json" \
|
||||
"id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON"
|
||||
|
||||
# Sample rule to enable JSON request body parser for more subtypes.
|
||||
# Uncomment or adapt this rule if you want to engage the JSON
|
||||
# Processor for "+json" subtypes
|
||||
#
|
||||
#SecRule REQUEST_HEADERS:Content-Type "^application/[a-z0-9.-]+[+]json" \
|
||||
# "id:'200006',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON"
|
||||
|
||||
# Maximum request body size we will accept for buffering. If you support
|
||||
# file uploads then the value given on the first line has to be as large
|
||||
# as the largest file you are willing to accept. The second value refers
|
||||
# to the size of data, with files excluded. You want to keep that value as
|
||||
# low as practical.
|
||||
#
|
||||
SecRequestBodyLimit 13107200
|
||||
|
||||
SecRequestBodyInMemoryLimit 131072
|
||||
|
||||
# SecRequestBodyNoFilesLimit is currently not supported by Coraza
|
||||
# SecRequestBodyNoFilesLimit 131072
|
||||
|
||||
# What to do if the request body size is above our configured limit.
|
||||
# Keep in mind that this setting will automatically be set to ProcessPartial
|
||||
# when SecRuleEngine is set to DetectionOnly mode in order to minimize
|
||||
# disruptions when initially deploying Coraza.
|
||||
#
|
||||
SecRequestBodyLimitAction Reject
|
||||
|
||||
# Verify that we've correctly processed the request body.
|
||||
# As a rule of thumb, when failing to process a request body
|
||||
# you should reject the request (when deployed in blocking mode)
|
||||
# or log a high-severity alert (when deployed in detection-only mode).
|
||||
#
|
||||
SecRule REQBODY_ERROR "!@eq 0" \
|
||||
"id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2"
|
||||
|
||||
# By default be strict with what we accept in the multipart/form-data
|
||||
# request body. If the rule below proves to be too strict for your
|
||||
# environment consider changing it to detection-only. You are encouraged
|
||||
# _not_ to remove it altogether.
|
||||
#
|
||||
SecRule MULTIPART_STRICT_ERROR "!@eq 0" \
|
||||
"id:'200003',phase:2,t:none,log,deny,status:400, \
|
||||
msg:'Multipart request body failed strict validation: \
|
||||
PE %{REQBODY_PROCESSOR_ERROR}, \
|
||||
BQ %{MULTIPART_BOUNDARY_QUOTED}, \
|
||||
BW %{MULTIPART_BOUNDARY_WHITESPACE}, \
|
||||
DB %{MULTIPART_DATA_BEFORE}, \
|
||||
DA %{MULTIPART_DATA_AFTER}, \
|
||||
HF %{MULTIPART_HEADER_FOLDING}, \
|
||||
LF %{MULTIPART_LF_LINE}, \
|
||||
SM %{MULTIPART_MISSING_SEMICOLON}, \
|
||||
IQ %{MULTIPART_INVALID_QUOTING}, \
|
||||
IP %{MULTIPART_INVALID_PART}, \
|
||||
IH %{MULTIPART_INVALID_HEADER_FOLDING}, \
|
||||
FL %{MULTIPART_FILE_LIMIT_EXCEEDED}'"
|
||||
|
||||
# Did we see anything that might be a boundary?
|
||||
#
|
||||
# Here is a short description about the Coraza Multipart parser: the
|
||||
# parser returns with value 0, if all "boundary-like" line matches with
|
||||
# the boundary string which given in MIME header. In any other cases it returns
|
||||
# with different value, eg. 1 or 2.
|
||||
#
|
||||
# The RFC 1341 descript the multipart content-type and its syntax must contains
|
||||
# only three mandatory lines (above the content):
|
||||
# * Content-Type: multipart/mixed; boundary=BOUNDARY_STRING
|
||||
# * --BOUNDARY_STRING
|
||||
# * --BOUNDARY_STRING--
|
||||
#
|
||||
# First line indicates, that this is a multipart content, second shows that
|
||||
# here starts a part of the multipart content, third shows the end of content.
|
||||
#
|
||||
# If there are any other lines, which starts with "--", then it should be
|
||||
# another boundary id - or not.
|
||||
#
|
||||
# After 3.0.3, there are two kinds of types of boundary errors: strict and permissive.
|
||||
#
|
||||
# If multipart content contains the three necessary lines with correct order, but
|
||||
# there are one or more lines with "--", then parser returns with value 2 (non-zero).
|
||||
#
|
||||
# If some of the necessary lines (usually the start or end) misses, or the order
|
||||
# is wrong, then parser returns with value 1 (also a non-zero).
|
||||
#
|
||||
# You can choose, which one is what you need. The example below contains the
|
||||
# 'strict' mode, which means if there are any lines with start of "--", then
|
||||
# Coraza blocked the content. But the next, commented example contains
|
||||
# the 'permissive' mode, then you check only if the necessary lines exists in
|
||||
# correct order. Whit this, you can enable to upload PEM files (eg "----BEGIN.."),
|
||||
# or other text files, which contains eg. HTTP headers.
|
||||
#
|
||||
# The difference is only the operator - in strict mode (first) the content blocked
|
||||
# in case of any non-zero value. In permissive mode (second, commented) the
|
||||
# content blocked only if the value is explicit 1. If it 0 or 2, the content will
|
||||
# allowed.
|
||||
#
|
||||
|
||||
#
|
||||
# See #1747 and #1924 for further information on the possible values for
|
||||
# MULTIPART_UNMATCHED_BOUNDARY.
|
||||
#
|
||||
SecRule MULTIPART_UNMATCHED_BOUNDARY "@eq 1" \
|
||||
"id:'200004',phase:2,t:none,log,deny,msg:'Multipart parser detected a possible unmatched boundary.'"
|
||||
|
||||
# Some internal errors will set flags in TX and we will need to look for these.
|
||||
# All of these are prefixed with "MSC_". The following flags currently exist:
|
||||
#
|
||||
# COR_PCRE_LIMITS_EXCEEDED: PCRE match limits were exceeded.
|
||||
#
|
||||
SecRule TX:/^COR_/ "!@streq 0" \
|
||||
"id:'200005',phase:2,t:none,deny,msg:'Coraza internal error flagged: %{MATCHED_VAR_NAME}'"
|
||||
|
||||
|
||||
# -- Response body handling --------------------------------------------------
|
||||
|
||||
# Allow Coraza to access response bodies.
|
||||
# You should have this directive enabled in order to identify errors
|
||||
# and data leakage issues.
|
||||
#
|
||||
# Do keep in mind that enabling this directive does increases both
|
||||
# memory consumption and response latency.
|
||||
#
|
||||
SecResponseBodyAccess On
|
||||
|
||||
# Which response MIME types do you want to inspect? You should adjust the
|
||||
# configuration below to catch documents but avoid static files
|
||||
# (e.g., images and archives).
|
||||
#
|
||||
SecResponseBodyMimeType text/plain text/html text/xml
|
||||
|
||||
# Buffer response bodies of up to 512 KB in length.
|
||||
SecResponseBodyLimit 524288
|
||||
|
||||
# What happens when we encounter a response body larger than the configured
|
||||
# limit? By default, we process what we have and let the rest through.
|
||||
# That's somewhat less secure, but does not break any legitimate pages.
|
||||
#
|
||||
SecResponseBodyLimitAction ProcessPartial
|
||||
|
||||
|
||||
# -- Filesystem configuration ------------------------------------------------
|
||||
|
||||
# The location where Coraza will keep its persistent data. This default setting
|
||||
# is chosen due to all systems have /tmp available however, it
|
||||
# too should be updated to a place that other users can't access.
|
||||
#
|
||||
SecDataDir /tmp/
|
||||
|
||||
|
||||
# -- File uploads handling configuration -------------------------------------
|
||||
|
||||
# The location where Coraza stores intercepted uploaded files. This
|
||||
# location must be private to Coraza. You don't want other users on
|
||||
# the server to access the files, do you?
|
||||
#
|
||||
#SecUploadDir /opt/coraza/var/upload/
|
||||
|
||||
# By default, only keep the files that were determined to be unusual
|
||||
# in some way (by an external inspection script). For this to work you
|
||||
# will also need at least one file inspection rule.
|
||||
#
|
||||
#SecUploadKeepFiles RelevantOnly
|
||||
|
||||
# Uploaded files are by default created with permissions that do not allow
|
||||
# any other user to access them. You may need to relax that if you want to
|
||||
# interface Coraza to an external program (e.g., an anti-virus).
|
||||
#
|
||||
#SecUploadFileMode 0600
|
||||
|
||||
|
||||
# -- Debug log configuration -------------------------------------------------
|
||||
|
||||
# Default debug log path
|
||||
# Debug levels:
|
||||
# 0: No logging (least verbose)
|
||||
# 1: Error
|
||||
# 2: Warn
|
||||
# 3: Info
|
||||
# 4-8: Debug
|
||||
# 9: Trace (most verbose)
|
||||
# Most logging has not been implemented because it will be replaced with
|
||||
# advanced rule profiling options
|
||||
#SecDebugLog /opt/coraza/var/log/debug.log
|
||||
#SecDebugLogLevel 3
|
||||
|
||||
|
||||
# -- Audit log configuration -------------------------------------------------
|
||||
|
||||
# Log the transactions that are marked by a rule, as well as those that
|
||||
# trigger a server error (determined by a 5xx or 4xx, excluding 404,
|
||||
# level response status codes).
|
||||
#
|
||||
SecAuditEngine RelevantOnly
|
||||
SecAuditLogRelevantStatus "^(?:(5|4)(0|1)[0-9])$"
|
||||
|
||||
# Log everything we know about a transaction.
|
||||
SecAuditLogParts ABIJDEFHZ
|
||||
|
||||
# Use a single file for logging. This is much easier to look at, but
|
||||
# assumes that you will use the audit log only occasionally.
|
||||
#
|
||||
SecAuditLogType Serial
|
||||
|
||||
|
||||
# -- Miscellaneous -----------------------------------------------------------
|
||||
|
||||
# Use the most commonly used application/x-www-form-urlencoded parameter
|
||||
# separator. There's probably only one application somewhere that uses
|
||||
# something else so don't expect to change this value.
|
||||
#
|
||||
SecArgumentSeparator &
|
||||
|
||||
# Settle on version 0 (zero) cookies, as that is what most applications
|
||||
# use. Using an incorrect cookie version may open your installation to
|
||||
# evasion attacks (against the rules that examine named cookies).
|
||||
#
|
||||
SecCookieFormat 0
|
||||
@@ -1,49 +0,0 @@
|
||||
// Copyright 2026 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package conf
|
||||
|
||||
type WebConfig struct {
|
||||
ShowGithubCorner bool `json:"showGithubCorner"`
|
||||
ForceLanguage string `json:"forceLanguage"`
|
||||
DefaultLanguage string `json:"defaultLanguage"`
|
||||
IsDemoMode bool `json:"isDemoMode"`
|
||||
StaticBaseUrl string `json:"staticBaseUrl"`
|
||||
AiAssistantUrl string `json:"aiAssistantUrl"`
|
||||
DefaultApplication string `json:"defaultApplication"`
|
||||
MaxItemsForFlatMenu int64 `json:"maxItemsForFlatMenu"`
|
||||
}
|
||||
|
||||
func GetWebConfig() *WebConfig {
|
||||
config := &WebConfig{}
|
||||
|
||||
config.ShowGithubCorner = GetConfigBool("showGithubCorner")
|
||||
config.ForceLanguage = GetLanguage(GetConfigString("forceLanguage"))
|
||||
config.DefaultLanguage = GetLanguage(GetConfigString("defaultLanguage"))
|
||||
config.IsDemoMode = IsDemoMode()
|
||||
config.StaticBaseUrl = GetConfigString("staticBaseUrl")
|
||||
config.AiAssistantUrl = GetConfigString("aiAssistantUrl")
|
||||
config.DefaultApplication = GetConfigString("defaultApplication")
|
||||
if config.DefaultApplication == "" {
|
||||
config.DefaultApplication = "app-built-in"
|
||||
}
|
||||
|
||||
maxItemsForFlatMenu, err := GetConfigInt64("maxItemsForFlatMenu")
|
||||
if err != nil {
|
||||
maxItemsForFlatMenu = 7
|
||||
}
|
||||
config.MaxItemsForFlatMenu = maxItemsForFlatMenu
|
||||
|
||||
return config
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -15,66 +15,62 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"strconv"
|
||||
|
||||
"github.com/beego/beego/v2/core/logs"
|
||||
"github.com/casdoor/casdoor/form"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/original"
|
||||
"github.com/casbin/casdoor/util"
|
||||
)
|
||||
|
||||
const (
|
||||
ResponseTypeLogin = "login"
|
||||
ResponseTypeCode = "code"
|
||||
ResponseTypeToken = "token"
|
||||
ResponseTypeIdToken = "id_token"
|
||||
ResponseTypeSaml = "saml"
|
||||
ResponseTypeCas = "cas"
|
||||
ResponseTypeDevice = "device"
|
||||
ResponseTypeLogin = "login"
|
||||
ResponseTypeCode = "code"
|
||||
)
|
||||
|
||||
type RequestForm struct {
|
||||
Type string `json:"type"`
|
||||
|
||||
Organization string `json:"organization"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Affiliation string `json:"affiliation"`
|
||||
Region string `json:"region"`
|
||||
|
||||
Application string `json:"application"`
|
||||
Provider string `json:"provider"`
|
||||
Code string `json:"code"`
|
||||
State string `json:"state"`
|
||||
RedirectUri string `json:"redirectUri"`
|
||||
Method string `json:"method"`
|
||||
|
||||
EmailCode string `json:"emailCode"`
|
||||
PhoneCode string `json:"phoneCode"`
|
||||
PhonePrefix string `json:"phonePrefix"`
|
||||
|
||||
AutoSignin bool `json:"autoSignin"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Status string `json:"status"`
|
||||
Msg string `json:"msg"`
|
||||
Sub string `json:"sub"`
|
||||
Name string `json:"name"`
|
||||
Data interface{} `json:"data"`
|
||||
Data2 interface{} `json:"data2"`
|
||||
Data3 interface{} `json:"data3"`
|
||||
}
|
||||
|
||||
type Captcha struct {
|
||||
Owner string `json:"owner"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
AppKey string `json:"appKey"`
|
||||
Scene string `json:"scene"`
|
||||
CaptchaId string `json:"captchaId"`
|
||||
CaptchaImage []byte `json:"captchaImage"`
|
||||
ClientId string `json:"clientId"`
|
||||
ClientSecret string `json:"clientSecret"`
|
||||
ClientId2 string `json:"clientId2"`
|
||||
ClientSecret2 string `json:"clientSecret2"`
|
||||
SubType string `json:"subType"`
|
||||
}
|
||||
|
||||
// this API is used by "Api URL" of Flarum's FoF Passport plugin
|
||||
// https://github.com/FriendsOfFlarum/passport
|
||||
type LaravelResponse struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
EmailVerifiedAt string `json:"email_verified_at"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
type HumanCheck struct {
|
||||
Type string `json:"type"`
|
||||
AppKey string `json:"appKey"`
|
||||
Scene string `json:"scene"`
|
||||
CaptchaId string `json:"captchaId"`
|
||||
CaptchaImage interface{} `json:"captchaImage"`
|
||||
}
|
||||
|
||||
// Signup
|
||||
// @Tag Login API
|
||||
// @Title Signup
|
||||
// @Description sign up a new user
|
||||
// @Param username formData string true "The username to sign up"
|
||||
@@ -82,726 +78,150 @@ type LaravelResponse struct {
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /signup [post]
|
||||
func (c *ApiController) Signup() {
|
||||
var authForm form.AuthForm
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &authForm)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
if c.GetSessionUsername() != "" {
|
||||
c.ResponseError("Please sign out first before signing up", c.GetSessionUsername())
|
||||
return
|
||||
}
|
||||
|
||||
application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
var form RequestForm
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application))
|
||||
return
|
||||
panic(err)
|
||||
}
|
||||
|
||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||
if !application.EnableSignUp {
|
||||
c.ResponseError(c.T("account:The application does not allow to sign up new account"))
|
||||
c.ResponseError("The application does not allow to sign up new account")
|
||||
return
|
||||
}
|
||||
|
||||
organization, err := object.GetOrganization(util.GetId("admin", authForm.Organization))
|
||||
if err != nil {
|
||||
c.ResponseError(c.T(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if organization == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The organization: %s does not exist"), authForm.Organization))
|
||||
return
|
||||
}
|
||||
|
||||
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
|
||||
err = object.CheckEntryIp(clientIp, nil, application, organization, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
msg := object.CheckUserSignup(application, organization, &authForm, c.GetAcceptLanguage())
|
||||
if msg != "" {
|
||||
c.ResponseError(msg)
|
||||
return
|
||||
}
|
||||
|
||||
invitation, msg := object.CheckInvitationCode(application, organization, &authForm, c.GetAcceptLanguage())
|
||||
if msg != "" {
|
||||
c.ResponseError(msg)
|
||||
return
|
||||
}
|
||||
invitationName := ""
|
||||
if invitation != nil {
|
||||
invitationName = invitation.Name
|
||||
}
|
||||
|
||||
userEmailVerified := false
|
||||
|
||||
if application.IsSignupItemVisible("Email") && application.GetSignupItemRule("Email") != "No verification" && authForm.Email != "" {
|
||||
var checkResult *object.VerifyResult
|
||||
checkResult, err = object.CheckVerificationCode(authForm.Email, authForm.EmailCode, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(c.T(err.Error()))
|
||||
if application.IsSignupItemEnabled("Email") {
|
||||
checkResult := object.CheckVerificationCode(form.Email, form.EmailCode)
|
||||
if len(checkResult) != 0 {
|
||||
c.ResponseError(fmt.Sprintf("Email%s", checkResult))
|
||||
return
|
||||
}
|
||||
if checkResult.Code != object.VerificationSuccess {
|
||||
c.ResponseError(checkResult.Msg)
|
||||
return
|
||||
}
|
||||
|
||||
userEmailVerified = true
|
||||
}
|
||||
|
||||
var checkPhone string
|
||||
if application.IsSignupItemVisible("Phone") && application.GetSignupItemRule("Phone") != "No verification" && authForm.Phone != "" {
|
||||
checkPhone, _ = util.GetE164Number(authForm.Phone, authForm.CountryCode)
|
||||
|
||||
var checkResult *object.VerifyResult
|
||||
checkResult, err = object.CheckVerificationCode(checkPhone, authForm.PhoneCode, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(c.T(err.Error()))
|
||||
return
|
||||
}
|
||||
if checkResult.Code != object.VerificationSuccess {
|
||||
c.ResponseError(checkResult.Msg)
|
||||
if application.IsSignupItemEnabled("Phone") {
|
||||
checkPhone = fmt.Sprintf("+%s%s", form.PhonePrefix, form.Phone)
|
||||
checkResult := object.CheckVerificationCode(checkPhone, form.PhoneCode)
|
||||
if len(checkResult) != 0 {
|
||||
c.ResponseError(fmt.Sprintf("Phone%s", checkResult))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
id, err := object.GenerateIdForNewUser(application)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
userId := fmt.Sprintf("%s/%s", form.Organization, form.Username)
|
||||
|
||||
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", form.Organization))
|
||||
msg := object.CheckUserSignup(application, organization, form.Username, form.Password, form.Name, form.Email, form.Phone, form.Affiliation)
|
||||
if msg != "" {
|
||||
c.ResponseError(msg)
|
||||
return
|
||||
}
|
||||
|
||||
username := authForm.Username
|
||||
id := util.GenerateId()
|
||||
if application.GetSignupItemRule("ID") == "Incremental" {
|
||||
lastUser := object.GetLastUser(form.Organization)
|
||||
lastIdInt := util.ParseInt(lastUser.Id)
|
||||
id = strconv.Itoa(lastIdInt + 1)
|
||||
}
|
||||
|
||||
username := form.Username
|
||||
if !application.IsSignupItemVisible("Username") {
|
||||
if organization.UseEmailAsUsername && application.IsSignupItemVisible("Email") {
|
||||
username = authForm.Email
|
||||
} else {
|
||||
username = id
|
||||
}
|
||||
}
|
||||
|
||||
initScore, err := organization.GetInitScore()
|
||||
if err != nil {
|
||||
c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error())
|
||||
return
|
||||
}
|
||||
|
||||
userType := "normal-user"
|
||||
if authForm.Plan != "" && authForm.Pricing != "" {
|
||||
err = object.CheckPricingAndPlan(authForm.Organization, authForm.Pricing, authForm.Plan, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
userType = "paid-user"
|
||||
username = id
|
||||
}
|
||||
|
||||
user := &object.User{
|
||||
Owner: authForm.Organization,
|
||||
Owner: form.Organization,
|
||||
Name: username,
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
Id: id,
|
||||
Type: userType,
|
||||
Password: authForm.Password,
|
||||
DisplayName: authForm.Name,
|
||||
Gender: authForm.Gender,
|
||||
Bio: authForm.Bio,
|
||||
Tag: authForm.Tag,
|
||||
Education: authForm.Education,
|
||||
Type: "normal-user",
|
||||
Password: form.Password,
|
||||
DisplayName: form.Name,
|
||||
Avatar: organization.DefaultAvatar,
|
||||
Email: strings.ToLower(authForm.Email),
|
||||
Phone: authForm.Phone,
|
||||
CountryCode: authForm.CountryCode,
|
||||
Email: form.Email,
|
||||
Phone: form.Phone,
|
||||
Address: []string{},
|
||||
Affiliation: authForm.Affiliation,
|
||||
IdCard: authForm.IdCard,
|
||||
Region: authForm.Region,
|
||||
Score: initScore,
|
||||
Affiliation: form.Affiliation,
|
||||
Region: form.Region,
|
||||
Score: getInitScore(),
|
||||
IsAdmin: false,
|
||||
IsGlobalAdmin: false,
|
||||
IsForbidden: false,
|
||||
IsDeleted: false,
|
||||
SignupApplication: application.Name,
|
||||
Properties: map[string]string{},
|
||||
Karma: 0,
|
||||
Invitation: invitationName,
|
||||
InvitationCode: authForm.InvitationCode,
|
||||
EmailVerified: userEmailVerified,
|
||||
RegisterType: "Application Signup",
|
||||
RegisterSource: fmt.Sprintf("%s/%s", authForm.Organization, application.Name),
|
||||
}
|
||||
|
||||
if len(organization.Tags) > 0 {
|
||||
tokens := strings.Split(organization.Tags[0], "|")
|
||||
if len(tokens) > 0 {
|
||||
user.Tag = tokens[0]
|
||||
}
|
||||
affected := object.AddUser(user)
|
||||
if affected {
|
||||
original.AddUserToOriginalDatabase(user)
|
||||
}
|
||||
|
||||
if application.GetSignupItemRule("Display name") == "First, last" {
|
||||
if authForm.FirstName != "" || authForm.LastName != "" {
|
||||
user.DisplayName = fmt.Sprintf("%s %s", authForm.FirstName, authForm.LastName)
|
||||
user.FirstName = authForm.FirstName
|
||||
user.LastName = authForm.LastName
|
||||
}
|
||||
}
|
||||
|
||||
if invitation != nil && invitation.SignupGroup != "" {
|
||||
user.Groups = []string{invitation.SignupGroup}
|
||||
}
|
||||
|
||||
if application.DefaultGroup != "" && user.Groups == nil {
|
||||
user.Groups = []string{application.DefaultGroup}
|
||||
}
|
||||
|
||||
affected, err := object.AddUser(user, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if !affected {
|
||||
c.ResponseError(c.T("account:Failed to add user"), util.StructToJson(user))
|
||||
return
|
||||
}
|
||||
|
||||
err = object.AddUserToOriginalDatabase(user)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if invitation != nil {
|
||||
invitation.UsedCount += 1
|
||||
_, err := object.UpdateInvitation(invitation.GetId(), invitation, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if user.Type == "normal-user" {
|
||||
if application.HasPromptPage() {
|
||||
// The prompt page needs the user to be signed in
|
||||
c.SetSessionUsername(user.GetId())
|
||||
} else if user.Type == "paid-user" {
|
||||
c.SetSession("paidUsername", user.GetId())
|
||||
}
|
||||
|
||||
if authForm.Email != "" {
|
||||
err = object.DisableVerificationCode(authForm.Email)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
object.DisableVerificationCode(form.Email)
|
||||
object.DisableVerificationCode(checkPhone)
|
||||
|
||||
if checkPhone != "" {
|
||||
err = object.DisableVerificationCode(checkPhone)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.Ctx.Input.SetParam("recordUserId", user.GetId())
|
||||
c.Ctx.Input.SetParam("recordSignup", "true")
|
||||
|
||||
userId := user.GetId()
|
||||
util.LogInfo(c.Ctx, "API: [%s] is signed up as new user", userId)
|
||||
|
||||
// Check if this is an OAuth flow and automatically generate code
|
||||
clientId := c.Ctx.Input.Query("clientId")
|
||||
responseType := c.Ctx.Input.Query("responseType")
|
||||
redirectUri := c.Ctx.Input.Query("redirectUri")
|
||||
scope := c.Ctx.Input.Query("scope")
|
||||
state := c.Ctx.Input.Query("state")
|
||||
nonce := c.Ctx.Input.Query("nonce")
|
||||
codeChallenge := c.Ctx.Input.Query("code_challenge")
|
||||
|
||||
// If OAuth parameters are present, generate OAuth code and return it
|
||||
if clientId != "" && responseType == ResponseTypeCode {
|
||||
consentRequired, err := object.CheckConsentRequired(user, application, scope)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if consentRequired {
|
||||
c.ResponseOk(map[string]bool{"required": true})
|
||||
return
|
||||
}
|
||||
|
||||
code, err := object.GetOAuthCode(userId, clientId, "", "password", responseType, redirectUri, scope, state, nonce, codeChallenge, "", c.Ctx.Request.Host, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
resp := codeToResponse(code)
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(userId)
|
||||
}
|
||||
|
||||
// Logout
|
||||
// @Title Logout
|
||||
// @Tag Login API
|
||||
// @Description logout the current user
|
||||
// @Param id_token_hint query string false "id_token_hint"
|
||||
// @Param post_logout_redirect_uri query string false "post_logout_redirect_uri"
|
||||
// @Param state query string false "state"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /logout [post]
|
||||
func (c *ApiController) Logout() {
|
||||
// https://openid.net/specs/openid-connect-rpinitiated-1_0-final.html
|
||||
accessToken := c.GetString("id_token_hint")
|
||||
redirectUri := c.GetString("post_logout_redirect_uri")
|
||||
state := c.GetString("state")
|
||||
|
||||
user := c.GetSessionUsername()
|
||||
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
|
||||
|
||||
if accessToken == "" && redirectUri == "" {
|
||||
// TODO https://github.com/casdoor/casdoor/pull/1494#discussion_r1095675265
|
||||
if user == "" {
|
||||
c.ResponseOk()
|
||||
return
|
||||
}
|
||||
c.SetSessionUsername("")
|
||||
c.SetSessionData(nil)
|
||||
|
||||
// Retrieve application and token before clearing the session
|
||||
application := c.GetSessionApplication()
|
||||
sessionToken := c.GetSessionToken()
|
||||
|
||||
c.ClearUserSession()
|
||||
c.ClearTokenSession()
|
||||
|
||||
if err := c.deleteUserSession(user); err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Propagate logout to external Custom OAuth2 providers
|
||||
object.InvokeCustomProviderLogout(application, sessionToken)
|
||||
|
||||
if application == nil || application.Name == "app-built-in" || application.HomepageUrl == "" {
|
||||
c.ResponseOk(user)
|
||||
return
|
||||
}
|
||||
c.ResponseOk(user, application.HomepageUrl)
|
||||
return
|
||||
} else {
|
||||
// "post_logout_redirect_uri" has been made optional, see: https://github.com/casdoor/casdoor/issues/2151
|
||||
// if redirectUri == "" {
|
||||
// c.ResponseError(c.T("general:Missing parameter") + ": post_logout_redirect_uri")
|
||||
// return
|
||||
// }
|
||||
if accessToken == "" {
|
||||
c.ResponseError(c.T("general:Missing parameter") + ": id_token_hint")
|
||||
return
|
||||
}
|
||||
|
||||
_, application, token, err := object.ExpireTokenByAccessToken(accessToken)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if token == nil {
|
||||
c.ResponseError(c.T("token:Token not found, invalid accessToken"))
|
||||
return
|
||||
}
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), token.Application))
|
||||
return
|
||||
}
|
||||
|
||||
if user == "" {
|
||||
user = util.GetId(token.Organization, token.User)
|
||||
}
|
||||
|
||||
c.ClearUserSession()
|
||||
c.ClearTokenSession()
|
||||
|
||||
// TODO https://github.com/casdoor/casdoor/pull/1494#discussion_r1095675265
|
||||
if err := c.deleteUserSession(user); err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Propagate logout to external Custom OAuth2 providers
|
||||
object.InvokeCustomProviderLogout(application, accessToken)
|
||||
|
||||
if redirectUri == "" {
|
||||
c.ResponseOk()
|
||||
return
|
||||
} else {
|
||||
if application.IsRedirectUriValid(redirectUri) {
|
||||
redirectUrl := redirectUri
|
||||
if state != "" {
|
||||
if strings.Contains(redirectUri, "?") {
|
||||
redirectUrl = fmt.Sprintf("%s&state=%s", strings.TrimSuffix(redirectUri, "/"), state)
|
||||
} else {
|
||||
redirectUrl = fmt.Sprintf("%s?state=%s", strings.TrimSuffix(redirectUri, "/"), state)
|
||||
}
|
||||
}
|
||||
c.Ctx.Redirect(http.StatusFound, redirectUrl)
|
||||
} else {
|
||||
c.ResponseError(fmt.Sprintf(c.T("token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SsoLogout
|
||||
// @Title SsoLogout
|
||||
// @Tag Login API
|
||||
// @Description logout the current user from all applications or current session only
|
||||
// @Param logoutAll query string false "Whether to logout from all sessions. Accepted values: 'true', '1', or empty (default: true). Any other value means false."
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /sso-logout [get,post]
|
||||
func (c *ApiController) SsoLogout() {
|
||||
user := c.GetSessionUsername()
|
||||
|
||||
if user == "" {
|
||||
c.ResponseOk()
|
||||
return
|
||||
}
|
||||
|
||||
// Check if user wants to logout from all sessions or just current session
|
||||
// Default is true for backward compatibility
|
||||
logoutAll := c.Ctx.Input.Query("logoutAll")
|
||||
logoutAllSessions := logoutAll == "" || logoutAll == "true" || logoutAll == "1"
|
||||
|
||||
// Retrieve application and token before clearing the session
|
||||
ssoApplication := c.GetSessionApplication()
|
||||
ssoSessionToken := c.GetSessionToken()
|
||||
|
||||
c.ClearUserSession()
|
||||
c.ClearTokenSession()
|
||||
owner, username, err := util.GetOwnerAndNameFromIdWithError(user)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
currentSessionId := c.Ctx.Input.CruSession.SessionID(context.Background())
|
||||
_, err = object.DeleteSessionId(util.GetSessionId(owner, username, object.CasdoorApplication), currentSessionId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var tokens []*object.Token
|
||||
var sessionIds []string
|
||||
|
||||
// Get tokens for notification (needed for both session-level and full logout)
|
||||
// This enables subsystems to identify and invalidate corresponding access tokens
|
||||
// Note: Tokens must be retrieved BEFORE expiration to include their hashes in the notification
|
||||
tokens, err = object.GetTokensByUser(owner, username)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if logoutAllSessions {
|
||||
// Logout from all sessions: expire all tokens and delete all sessions
|
||||
_, err = object.ExpireTokenByUser(owner, username)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
sessions, err := object.GetUserSessions(owner, username)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
for _, session := range sessions {
|
||||
sessionIds = append(sessionIds, session.SessionId...)
|
||||
}
|
||||
object.DeleteBeegoSession(sessionIds)
|
||||
|
||||
_, err = object.DeleteAllUserSessions(owner, username)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
util.LogInfo(c.Ctx, "API: [%s] logged out from all applications", user)
|
||||
} else {
|
||||
// Logout from current session only
|
||||
sessionIds = []string{currentSessionId}
|
||||
|
||||
// Only delete the current session's Beego session
|
||||
object.DeleteBeegoSession(sessionIds)
|
||||
|
||||
util.LogInfo(c.Ctx, "API: [%s] logged out from current session", user)
|
||||
}
|
||||
|
||||
// Send SSO logout notifications to all notification providers in the user's signup application
|
||||
// Now includes session-level information for targeted logout
|
||||
userObj, err := object.GetUser(user)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if userObj != nil {
|
||||
err = object.SendSsoLogoutNotifications(userObj, sessionIds, tokens)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Propagate logout to external Custom OAuth2 providers
|
||||
object.InvokeCustomProviderLogout(ssoApplication, ssoSessionToken)
|
||||
|
||||
c.ResponseOk()
|
||||
c.ResponseOk(user)
|
||||
}
|
||||
|
||||
// GetAccount
|
||||
// @Title GetAccount
|
||||
// @Tag Account API
|
||||
// @Description get the details of the current account
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /get-account [get]
|
||||
func (c *ApiController) GetAccount() {
|
||||
var err error
|
||||
err = util.AppendWebConfigCookie(c.Ctx)
|
||||
if err != nil {
|
||||
logs.Error("AppendWebConfigCookie failed in GetAccount, error: %s", err)
|
||||
}
|
||||
|
||||
user, ok := c.RequireSignedInUser()
|
||||
userId, ok := c.RequireSignedIn()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
managedAccounts := c.Ctx.Input.Query("managedAccounts")
|
||||
if managedAccounts == "1" {
|
||||
user, err = object.ExtendManagedAccountsWithUser(user)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = object.ExtendUserWithRolesAndPermissions(user)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
user := object.GetUser(userId)
|
||||
if user == nil {
|
||||
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId))
|
||||
return
|
||||
}
|
||||
|
||||
if user != nil {
|
||||
user.Permissions = object.GetMaskedPermissions(user.Permissions)
|
||||
user.Roles = object.GetMaskedRoles(user.Roles)
|
||||
user.MultiFactorAuths = object.GetAllMfaProps(user, true)
|
||||
}
|
||||
organization := object.GetOrganizationByUser(user)
|
||||
|
||||
organization, err := object.GetMaskedOrganization(object.GetOrganizationByUser(user))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
c.ResponseOk(user, organization)
|
||||
}
|
||||
|
||||
// GetHumanCheck ...
|
||||
func (c *ApiController) GetHumanCheck() {
|
||||
c.Data["json"] = HumanCheck{Type: "none"}
|
||||
|
||||
provider := object.GetDefaultHumanCheckProvider()
|
||||
if provider == nil {
|
||||
id, img := object.GetCaptcha()
|
||||
c.Data["json"] = HumanCheck{Type: "captcha", CaptchaId: id, CaptchaImage: img}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
isAdminOrSelf := c.IsAdminOrSelf(user)
|
||||
u, err := object.GetMaskedUser(user, isAdminOrSelf)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if organization != nil && len(organization.CountryCodes) == 1 && u != nil && u.CountryCode == "" {
|
||||
u.CountryCode = organization.CountryCodes[0]
|
||||
}
|
||||
|
||||
accessToken := c.GetSessionToken()
|
||||
if accessToken == "" {
|
||||
accessToken, err = object.GetAccessTokenByUser(user, c.Ctx.Request.Host)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
c.SetSessionToken(accessToken)
|
||||
}
|
||||
u.AccessToken = accessToken
|
||||
|
||||
resp := Response{
|
||||
Status: "ok",
|
||||
Sub: user.Id,
|
||||
Name: user.Name,
|
||||
Data: u,
|
||||
Data2: organization,
|
||||
}
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetUserinfo
|
||||
// UserInfo
|
||||
// @Title UserInfo
|
||||
// @Tag Account API
|
||||
// @Description return user information according to OIDC standards
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
// @router /userinfo [get]
|
||||
func (c *ApiController) GetUserinfo() {
|
||||
user, ok := c.RequireSignedInUser()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
scope, aud := c.GetSessionOidc()
|
||||
host := c.Ctx.Request.Host
|
||||
|
||||
userInfo, err := object.GetUserInfo(user, scope, aud, host)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = userInfo
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetUserinfo2
|
||||
// LaravelResponse
|
||||
// @Title UserInfo2
|
||||
// @Tag Account API
|
||||
// @Description return Laravel compatible user information according to OAuth 2.0
|
||||
// @Success 200 {object} controllers.LaravelResponse The Response object
|
||||
// @router /user [get]
|
||||
func (c *ApiController) GetUserinfo2() {
|
||||
user, ok := c.RequireSignedInUser()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
response := LaravelResponse{
|
||||
Id: user.Id,
|
||||
Name: user.Name,
|
||||
Email: user.Email,
|
||||
EmailVerifiedAt: user.CreatedTime,
|
||||
CreatedAt: user.CreatedTime,
|
||||
UpdatedAt: user.UpdatedTime,
|
||||
}
|
||||
|
||||
c.Data["json"] = response
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetCaptcha ...
|
||||
// @Tag Login API
|
||||
// @Title GetCaptcha
|
||||
// @router /get-captcha [get]
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
func (c *ApiController) GetCaptcha() {
|
||||
applicationId := c.Ctx.Input.Query("applicationId")
|
||||
isCurrentProvider := c.Ctx.Input.Query("isCurrentProvider")
|
||||
|
||||
// When isCurrentProvider == "true", the frontend passes a provider ID instead of an application ID.
|
||||
// In that case, skip application lookup and rule evaluation, and just return the provider config.
|
||||
shouldSkipCaptcha := false
|
||||
|
||||
if isCurrentProvider != "true" {
|
||||
application, err := object.GetApplication(applicationId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), applicationId))
|
||||
return
|
||||
}
|
||||
|
||||
// Check the CAPTCHA rule to determine if CAPTCHA should be shown
|
||||
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
|
||||
|
||||
// For Internet-Only rule, we can determine on the backend if CAPTCHA should be shown
|
||||
// For other rules (Dynamic, Always), we need to return the CAPTCHA config
|
||||
for _, providerItem := range application.Providers {
|
||||
if providerItem.Provider == nil || providerItem.Provider.Category != "Captcha" {
|
||||
continue
|
||||
}
|
||||
|
||||
// For "None" rule, skip CAPTCHA
|
||||
if providerItem.Rule == "None" || providerItem.Rule == "" {
|
||||
shouldSkipCaptcha = true
|
||||
} else if providerItem.Rule == "Internet-Only" {
|
||||
// For Internet-Only rule, check if the client is from intranet
|
||||
if !util.IsInternetIp(clientIp) {
|
||||
// Client is from intranet, skip CAPTCHA
|
||||
shouldSkipCaptcha = true
|
||||
}
|
||||
}
|
||||
|
||||
break // Only check the first CAPTCHA provider
|
||||
}
|
||||
|
||||
if shouldSkipCaptcha {
|
||||
c.ResponseOk(Captcha{Type: "none"})
|
||||
return
|
||||
}
|
||||
}
|
||||
captchaProvider, err := object.GetCaptchaProviderByApplication(applicationId, isCurrentProvider, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if captchaProvider != nil {
|
||||
if captchaProvider.Type == "Default" {
|
||||
id, img, err := object.GetCaptcha()
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(Captcha{Owner: captchaProvider.Owner, Name: captchaProvider.Name, Type: captchaProvider.Type, CaptchaId: id, CaptchaImage: img})
|
||||
return
|
||||
} else if captchaProvider.Type != "" {
|
||||
c.ResponseOk(Captcha{
|
||||
Owner: captchaProvider.Owner,
|
||||
Name: captchaProvider.Name,
|
||||
Type: captchaProvider.Type,
|
||||
SubType: captchaProvider.SubType,
|
||||
ClientId: captchaProvider.ClientId,
|
||||
ClientSecret: "***",
|
||||
ClientId2: captchaProvider.ClientId2,
|
||||
ClientSecret2: captchaProvider.ClientSecret2,
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.ResponseOk(Captcha{Type: "none"})
|
||||
}
|
||||
|
||||
func (c *ApiController) deleteUserSession(user string) error {
|
||||
owner, username, err := util.GetOwnerAndNameFromIdWithError(user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Casdoor session ID derived from owner, username, and application
|
||||
sessionId := util.GetSessionId(owner, username, object.CasdoorApplication)
|
||||
|
||||
// Explicitly get the Beego session ID from the context
|
||||
beegoSessionId := c.Ctx.Input.CruSession.SessionID(context.Background())
|
||||
|
||||
_, err = object.DeleteSessionId(sessionId, beegoSessionId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/beego/beego/v2/core/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetAdapters
|
||||
// @Title GetAdapters
|
||||
// @Tag Adapter API
|
||||
// @Description get adapters
|
||||
// @Param owner query string true "The owner of adapters"
|
||||
// @Success 200 {array} object.Adapter The Response object
|
||||
// @router /get-adapters [get]
|
||||
func (c *ApiController) GetAdapters() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
adapters, err := object.GetAdapters(owner)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(adapters)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetAdapterCount(owner, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
adapters, err := object.GetPaginationAdapters(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(adapters, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetAdapter
|
||||
// @Title GetAdapter
|
||||
// @Tag Adapter API
|
||||
// @Description get adapter
|
||||
// @Param id query string true "The id ( owner/name ) of the adapter"
|
||||
// @Success 200 {object} object.Adapter The Response object
|
||||
// @router /get-adapter [get]
|
||||
func (c *ApiController) GetAdapter() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
adapter, err := object.GetAdapter(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(adapter)
|
||||
}
|
||||
|
||||
// UpdateAdapter
|
||||
// @Title UpdateAdapter
|
||||
// @Tag Adapter API
|
||||
// @Description update adapter
|
||||
// @Param id query string true "The id ( owner/name ) of the adapter"
|
||||
// @Param body body object.Adapter true "The details of the adapter"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-adapter [post]
|
||||
func (c *ApiController) UpdateAdapter() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
var adapter object.Adapter
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &adapter)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateAdapter(id, &adapter))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddAdapter
|
||||
// @Title AddAdapter
|
||||
// @Tag Adapter API
|
||||
// @Description add adapter
|
||||
// @Param body body object.Adapter true "The details of the adapter"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-adapter [post]
|
||||
func (c *ApiController) AddAdapter() {
|
||||
var adapter object.Adapter
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &adapter)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddAdapter(&adapter))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteAdapter
|
||||
// @Title DeleteAdapter
|
||||
// @Tag Adapter API
|
||||
// @Description delete adapter
|
||||
// @Param body body object.Adapter true "The details of the adapter"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-adapter [post]
|
||||
func (c *ApiController) DeleteAdapter() {
|
||||
var adapter object.Adapter
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &adapter)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteAdapter(&adapter))
|
||||
c.ServeJSON()
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
// Copyright 2026 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/beego/beego/v2/server/web/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetAgents
|
||||
// @Title GetAgents
|
||||
// @Tag Agent API
|
||||
// @Description get agents
|
||||
// @Param owner query string true "The owner of agents"
|
||||
// @Success 200 {array} object.Agent The Response object
|
||||
// @router /get-agents [get]
|
||||
func (c *ApiController) GetAgents() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
if owner == "admin" {
|
||||
owner = ""
|
||||
}
|
||||
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
agents, err := object.GetAgents(owner)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
c.ResponseOk(agents)
|
||||
return
|
||||
}
|
||||
|
||||
limitInt := util.ParseInt(limit)
|
||||
count, err := object.GetAgentCount(owner, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.SetPaginator(c.Ctx, limitInt, count)
|
||||
agents, err := object.GetPaginationAgents(owner, paginator.Offset(), limitInt, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(agents, paginator.Nums())
|
||||
}
|
||||
|
||||
// GetAgent
|
||||
// @Title GetAgent
|
||||
// @Tag Agent API
|
||||
// @Description get agent
|
||||
// @Param id query string true "The id ( owner/name ) of the agent"
|
||||
// @Success 200 {object} object.Agent The Response object
|
||||
// @router /get-agent [get]
|
||||
func (c *ApiController) GetAgent() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
agent, err := object.GetAgent(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(agent)
|
||||
}
|
||||
|
||||
// UpdateAgent
|
||||
// @Title UpdateAgent
|
||||
// @Tag Agent API
|
||||
// @Description update agent
|
||||
// @Param id query string true "The id ( owner/name ) of the agent"
|
||||
// @Param body body object.Agent true "The details of the agent"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-agent [post]
|
||||
func (c *ApiController) UpdateAgent() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
var agent object.Agent
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &agent)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateAgent(id, &agent))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddAgent
|
||||
// @Title AddAgent
|
||||
// @Tag Agent API
|
||||
// @Description add agent
|
||||
// @Param body body object.Agent true "The details of the agent"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-agent [post]
|
||||
func (c *ApiController) AddAgent() {
|
||||
var agent object.Agent
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &agent)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddAgent(&agent))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteAgent
|
||||
// @Title DeleteAgent
|
||||
// @Tag Agent API
|
||||
// @Description delete agent
|
||||
// @Param body body object.Agent true "The details of the agent"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-agent [post]
|
||||
func (c *ApiController) DeleteAgent() {
|
||||
var agent object.Agent
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &agent)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteAgent(&agent))
|
||||
c.ServeJSON()
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -16,234 +16,76 @@ package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/beego/beego/v2/core/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/casbin/casdoor/object"
|
||||
)
|
||||
|
||||
// GetApplications
|
||||
// @Title GetApplications
|
||||
// @Tag Application API
|
||||
// @Description get all applications
|
||||
// @Param owner query string true "The owner of applications."
|
||||
// @Success 200 {array} object.Application The Response object
|
||||
// @router /get-applications [get]
|
||||
func (c *ApiController) GetApplications() {
|
||||
userId := c.GetSessionUsername()
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
organization := c.Ctx.Input.Query("organization")
|
||||
var err error
|
||||
if limit == "" || page == "" {
|
||||
var applications []*object.Application
|
||||
if organization == "" {
|
||||
applications, err = object.GetApplications(owner)
|
||||
} else {
|
||||
applications, err = object.GetOrganizationApplications(owner, organization)
|
||||
}
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
c.ResponseOk(object.GetMaskedApplications(applications, userId))
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetApplicationCount(owner, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
owner := c.Input().Get("owner")
|
||||
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
application, err := object.GetPaginationApplications(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
applications := object.GetMaskedApplications(application, userId)
|
||||
c.ResponseOk(applications, paginator.Nums())
|
||||
}
|
||||
c.Data["json"] = object.GetApplications(owner)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetApplication
|
||||
// @Title GetApplication
|
||||
// @Tag Application API
|
||||
// @Description get the detail of an application
|
||||
// @Param id query string true "The id ( owner/name ) of the application."
|
||||
// @Param id query string true "The id of the application."
|
||||
// @Success 200 {object} object.Application The Response object
|
||||
// @router /get-application [get]
|
||||
func (c *ApiController) GetApplication() {
|
||||
userId := c.GetSessionUsername()
|
||||
id := c.Ctx.Input.Query("id")
|
||||
id := c.Input().Get("id")
|
||||
|
||||
application, err := object.GetApplication(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if c.Ctx.Input.Query("withKey") != "" && application != nil && application.Cert != "" {
|
||||
cert, err := object.GetCert(util.GetId(application.Owner, application.Cert))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if cert == nil {
|
||||
cert, err = object.GetCert(util.GetId(application.Organization, application.Cert))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if cert != nil {
|
||||
application.CertPublicKey = cert.Certificate
|
||||
}
|
||||
}
|
||||
|
||||
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
|
||||
object.CheckEntryIp(clientIp, nil, application, nil, c.GetAcceptLanguage())
|
||||
|
||||
c.ResponseOk(object.GetMaskedApplication(application, userId))
|
||||
c.Data["json"] = object.GetApplication(id)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetUserApplication
|
||||
// @Title GetUserApplication
|
||||
// @Tag Application API
|
||||
// @Description get the detail of the user's application
|
||||
// @Param id query string true "The id ( owner/name ) of the user"
|
||||
// @Param id query string true "The id of the user"
|
||||
// @Success 200 {object} object.Application The Response object
|
||||
// @router /get-user-application [get]
|
||||
func (c *ApiController) GetUserApplication() {
|
||||
userId := c.GetSessionUsername()
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
user, err := object.GetUser(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
id := c.Input().Get("id")
|
||||
user := object.GetUser(id)
|
||||
if user == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), id))
|
||||
c.ResponseError("No such user.")
|
||||
return
|
||||
}
|
||||
|
||||
application, err := object.GetApplicationByUser(user)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The organization: %s should have one application at least"), user.Owner))
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetMaskedApplication(application, userId))
|
||||
}
|
||||
|
||||
// GetOrganizationApplications
|
||||
// @Title GetOrganizationApplications
|
||||
// @Tag Application API
|
||||
// @Description get the detail of the organization's application
|
||||
// @Param organization query string true "The organization name"
|
||||
// @Success 200 {array} object.Application The Response object
|
||||
// @router /get-organization-applications [get]
|
||||
func (c *ApiController) GetOrganizationApplications() {
|
||||
userId := c.GetSessionUsername()
|
||||
organization := c.Ctx.Input.Query("organization")
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
|
||||
if organization == "" {
|
||||
c.ResponseError(c.T("general:Missing parameter") + ": organization")
|
||||
return
|
||||
}
|
||||
|
||||
if limit == "" || page == "" {
|
||||
applications, err := object.GetOrganizationApplications(owner, organization)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
applications, err = object.GetAllowedApplications(applications, userId, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetMaskedApplications(applications, userId))
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
|
||||
count, err := object.GetOrganizationApplicationCount(owner, organization, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
applications, err := object.GetPaginationOrganizationApplications(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
applications, err = object.GetAllowedApplications(applications, userId, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
applications = object.GetMaskedApplications(applications, userId)
|
||||
c.ResponseOk(applications, paginator.Nums())
|
||||
}
|
||||
c.Data["json"] = object.GetApplicationByUser(user)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// UpdateApplication
|
||||
// @Title UpdateApplication
|
||||
// @Tag Application API
|
||||
// @Description update an application
|
||||
// @Param id query string true "The id ( owner/name ) of the application"
|
||||
// @Param id query string true "The id of the application"
|
||||
// @Param body body object.Application true "The details of the application"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-application [post]
|
||||
func (c *ApiController) UpdateApplication() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
id := c.Input().Get("id")
|
||||
|
||||
var application object.Application
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &application)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err = object.CheckIpWhitelist(application.IpWhitelist, c.GetAcceptLanguage()); err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateApplication(id, &application, c.IsGlobalAdmin(), c.GetAcceptLanguage()))
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateApplication(id, &application))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddApplication
|
||||
// @Title AddApplication
|
||||
// @Tag Application API
|
||||
// @Description add an application
|
||||
// @Param body body object.Application true "The details of the application"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
@@ -252,24 +94,7 @@ func (c *ApiController) AddApplication() {
|
||||
var application object.Application
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &application)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
count, err := object.GetApplicationCount("", "", "")
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err := checkQuotaForApplication(int(count)); err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err = object.CheckIpWhitelist(application.IpWhitelist, c.GetAcceptLanguage()); err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddApplication(&application))
|
||||
@@ -278,7 +103,6 @@ func (c *ApiController) AddApplication() {
|
||||
|
||||
// DeleteApplication
|
||||
// @Title DeleteApplication
|
||||
// @Tag Application API
|
||||
// @Description delete an application
|
||||
// @Param body body object.Application true "The details of the application"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
@@ -287,10 +111,16 @@ func (c *ApiController) DeleteApplication() {
|
||||
var application object.Application
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &application)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteApplication(&application))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) GetApplicationByClientId() {
|
||||
clientId := c.Input().Get("clientId")
|
||||
|
||||
c.Data["json"] = object.GetApplicationByClientId(clientId)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
1505
controllers/auth.go
1505
controllers/auth.go
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -15,111 +15,29 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/beego/beego/v2/core/logs"
|
||||
"github.com/beego/beego/v2/server/web"
|
||||
"github.com/casdoor/casdoor/mcpself"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casbin/casdoor/util"
|
||||
)
|
||||
|
||||
// ApiController
|
||||
// controller for handlers under /api uri
|
||||
type ApiController struct {
|
||||
web.Controller
|
||||
}
|
||||
|
||||
// RootController
|
||||
// controller for handlers directly under / (root)
|
||||
type RootController struct {
|
||||
ApiController
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
type SessionData struct {
|
||||
ExpireTime int64
|
||||
}
|
||||
|
||||
func (c *ApiController) IsGlobalAdmin() bool {
|
||||
isGlobalAdmin, _ := c.isGlobalAdmin()
|
||||
|
||||
return isGlobalAdmin
|
||||
}
|
||||
|
||||
func (c *ApiController) IsAdmin() bool {
|
||||
isGlobalAdmin, user := c.isGlobalAdmin()
|
||||
if !isGlobalAdmin && user == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return isGlobalAdmin || user.IsAdmin
|
||||
}
|
||||
|
||||
func (c *ApiController) IsAdminOrSelf(user2 *object.User) bool {
|
||||
isGlobalAdmin, user := c.isGlobalAdmin()
|
||||
if isGlobalAdmin || (user != nil && user.IsAdmin) {
|
||||
return true
|
||||
}
|
||||
|
||||
if user == nil || user2 == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if user.Owner == user2.Owner && user.Name == user2.Name {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *ApiController) isGlobalAdmin() (bool, *object.User) {
|
||||
username := c.GetSessionUsername()
|
||||
if object.IsAppUser(username) {
|
||||
// e.g., "app/app-casnode"
|
||||
return true, nil
|
||||
}
|
||||
|
||||
user := c.getCurrentUser()
|
||||
if user == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return user.IsGlobalAdmin(), user
|
||||
}
|
||||
|
||||
func (c *ApiController) getCurrentUser() *object.User {
|
||||
var user *object.User
|
||||
var err error
|
||||
userId := c.GetSessionUsername()
|
||||
if userId == "" {
|
||||
user = nil
|
||||
} else {
|
||||
user, err = object.GetUser(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
// GetSessionUsername ...
|
||||
func (c *ApiController) GetSessionUsername() string {
|
||||
// prefer username stored in Beego context by ApiFilter
|
||||
if ctxUser := c.Ctx.Input.GetData("currentUserId"); ctxUser != nil {
|
||||
if username, ok := ctxUser.(string); ok {
|
||||
return username
|
||||
}
|
||||
}
|
||||
|
||||
// check if user session expired
|
||||
sessionData := c.GetSessionData()
|
||||
|
||||
if sessionData != nil &&
|
||||
sessionData.ExpireTime != 0 &&
|
||||
sessionData.ExpireTime < time.Now().Unix() {
|
||||
c.ClearUserSession()
|
||||
c.SetSessionUsername("")
|
||||
c.SetSessionData(nil)
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -131,89 +49,11 @@ func (c *ApiController) GetSessionUsername() string {
|
||||
return user.(string)
|
||||
}
|
||||
|
||||
// GetPaidUsername ...
|
||||
func (c *ApiController) GetPaidUsername() string {
|
||||
// check if user session expired
|
||||
sessionData := c.GetSessionData()
|
||||
|
||||
if sessionData != nil &&
|
||||
sessionData.ExpireTime != 0 &&
|
||||
sessionData.ExpireTime < time.Now().Unix() {
|
||||
c.ClearUserSession()
|
||||
return ""
|
||||
}
|
||||
|
||||
user := c.GetSession("paidUsername")
|
||||
if user == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return user.(string)
|
||||
}
|
||||
|
||||
func (c *ApiController) GetSessionToken() string {
|
||||
accessToken := c.GetSession("accessToken")
|
||||
if accessToken == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return accessToken.(string)
|
||||
}
|
||||
|
||||
func (c *ApiController) GetSessionApplication() *object.Application {
|
||||
clientId := c.GetSession("aud")
|
||||
if clientId == nil {
|
||||
return nil
|
||||
}
|
||||
application, err := object.GetApplicationByClientId(clientId.(string))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
return application
|
||||
}
|
||||
|
||||
func (c *ApiController) ClearUserSession() {
|
||||
c.SetSessionUsername("")
|
||||
c.SetSessionData(nil)
|
||||
_ = c.SessionRegenerateID()
|
||||
}
|
||||
|
||||
func (c *ApiController) ClearTokenSession() {
|
||||
c.SetSessionToken("")
|
||||
}
|
||||
|
||||
func (c *ApiController) GetSessionOidc() (string, string) {
|
||||
sessionData := c.GetSessionData()
|
||||
if sessionData != nil &&
|
||||
sessionData.ExpireTime != 0 &&
|
||||
sessionData.ExpireTime < time.Now().Unix() {
|
||||
c.ClearUserSession()
|
||||
return "", ""
|
||||
}
|
||||
scopeValue := c.GetSession("scope")
|
||||
audValue := c.GetSession("aud")
|
||||
var scope, aud string
|
||||
var ok bool
|
||||
if scope, ok = scopeValue.(string); !ok {
|
||||
scope = ""
|
||||
}
|
||||
if aud, ok = audValue.(string); !ok {
|
||||
aud = ""
|
||||
}
|
||||
return scope, aud
|
||||
}
|
||||
|
||||
// SetSessionUsername ...
|
||||
func (c *ApiController) SetSessionUsername(user string) {
|
||||
c.SetSession("username", user)
|
||||
}
|
||||
|
||||
func (c *ApiController) SetSessionToken(accessToken string) {
|
||||
c.SetSession("accessToken", accessToken)
|
||||
}
|
||||
|
||||
// GetSessionData ...
|
||||
func (c *ApiController) GetSessionData() *SessionData {
|
||||
session := c.GetSession("SessionData")
|
||||
@@ -224,8 +64,7 @@ func (c *ApiController) GetSessionData() *SessionData {
|
||||
sessionData := &SessionData{}
|
||||
err := util.JsonToStruct(session.(string), sessionData)
|
||||
if err != nil {
|
||||
logs.Error("GetSessionData failed, error: %s", err)
|
||||
return nil
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return sessionData
|
||||
@@ -241,65 +80,10 @@ func (c *ApiController) SetSessionData(s *SessionData) {
|
||||
c.SetSession("SessionData", util.StructToJson(s))
|
||||
}
|
||||
|
||||
func (c *ApiController) setMfaUserSession(userId string) {
|
||||
c.SetSession(object.MfaSessionUserId, userId)
|
||||
}
|
||||
|
||||
func (c *ApiController) getMfaUserSession() string {
|
||||
userId := c.Ctx.Input.CruSession.Get(context.Background(), object.MfaSessionUserId)
|
||||
if userId == nil {
|
||||
return ""
|
||||
}
|
||||
return userId.(string)
|
||||
}
|
||||
|
||||
func (c *ApiController) setExpireForSession(cookieExpireInHours int64) {
|
||||
timestamp := time.Now().Unix()
|
||||
if cookieExpireInHours == 0 {
|
||||
cookieExpireInHours = 720
|
||||
}
|
||||
timestamp += 3600 * cookieExpireInHours
|
||||
c.SetSessionData(&SessionData{
|
||||
ExpireTime: timestamp,
|
||||
})
|
||||
}
|
||||
|
||||
func wrapActionResponse(affected bool, e ...error) *Response {
|
||||
if len(e) != 0 && e[0] != nil {
|
||||
return &Response{Status: "error", Msg: e[0].Error()}
|
||||
} else if affected {
|
||||
func wrapActionResponse(affected bool) *Response {
|
||||
if affected {
|
||||
return &Response{Status: "ok", Msg: "", Data: "Affected"}
|
||||
} else {
|
||||
return &Response{Status: "ok", Msg: "", Data: "Unaffected"}
|
||||
}
|
||||
}
|
||||
|
||||
func wrapErrorResponse(err error) *Response {
|
||||
if err == nil {
|
||||
return &Response{Status: "ok", Msg: ""}
|
||||
} else {
|
||||
return &Response{Status: "error", Msg: err.Error()}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ApiController) Finish() {
|
||||
if strings.HasPrefix(c.Ctx.Input.URL(), "/api") {
|
||||
startTime := c.Ctx.Input.GetData("startTime")
|
||||
if startTime != nil {
|
||||
latency := time.Since(startTime.(time.Time)).Milliseconds()
|
||||
object.ApiLatency.WithLabelValues(c.Ctx.Input.URL(), c.Ctx.Input.Method()).Observe(float64(latency))
|
||||
}
|
||||
}
|
||||
c.Controller.Finish()
|
||||
}
|
||||
|
||||
func (c *ApiController) McpResponseError(id interface{}, code int, message string, data interface{}) {
|
||||
resp := mcpself.BuildMcpResponse(id, nil, &mcpself.McpError{
|
||||
Code: code,
|
||||
Message: message,
|
||||
Data: data,
|
||||
})
|
||||
c.Ctx.Output.Header("Content-Type", "application/json")
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
@@ -1,281 +0,0 @@
|
||||
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
)
|
||||
|
||||
const (
|
||||
InvalidRequest string = "INVALID_REQUEST"
|
||||
InvalidTicketSpec string = "INVALID_TICKET_SPEC"
|
||||
UnauthorizedServiceProxy string = "UNAUTHORIZED_SERVICE_PROXY"
|
||||
InvalidProxyCallback string = "INVALID_PROXY_CALLBACK"
|
||||
InvalidTicket string = "INVALID_TICKET"
|
||||
InvalidService string = "INVALID_SERVICE"
|
||||
InternalError string = "INTERNAL_ERROR"
|
||||
UnauthorizedService string = "UNAUTHORIZED_SERVICE"
|
||||
)
|
||||
|
||||
func queryUnescape(service string) string {
|
||||
s, _ := url.QueryUnescape(service)
|
||||
return s
|
||||
}
|
||||
|
||||
func (c *RootController) CasValidate() {
|
||||
ticket := c.Ctx.Input.Query("ticket")
|
||||
service := c.Ctx.Input.Query("service")
|
||||
c.Ctx.Output.Header("Content-Type", "text/html; charset=utf-8")
|
||||
if service == "" || ticket == "" {
|
||||
c.Ctx.Output.Body([]byte("no\n"))
|
||||
return
|
||||
}
|
||||
if ok, response, issuedService, _ := object.GetCasTokenByTicket(ticket); ok {
|
||||
// check whether service is the one for which we previously issued token
|
||||
if issuedService == service {
|
||||
c.Ctx.Output.Body([]byte(fmt.Sprintf("yes\n%s\n", response.User)))
|
||||
return
|
||||
}
|
||||
}
|
||||
// token not found
|
||||
c.Ctx.Output.Body([]byte("no\n"))
|
||||
}
|
||||
|
||||
func (c *RootController) CasServiceValidate() {
|
||||
ticket := c.Ctx.Input.Query("ticket")
|
||||
format := c.Ctx.Input.Query("format")
|
||||
if !strings.HasPrefix(ticket, "ST") {
|
||||
c.sendCasAuthenticationResponseErr(InvalidTicket, fmt.Sprintf("Ticket %s not recognized", ticket), format)
|
||||
}
|
||||
c.CasP3ProxyValidate()
|
||||
}
|
||||
|
||||
func (c *RootController) CasProxyValidate() {
|
||||
// https://apereo.github.io/cas/6.6.x/protocol/CAS-Protocol-Specification.html#26-proxyvalidate-cas-20
|
||||
// "/proxyValidate" should accept both service tickets and proxy tickets.
|
||||
c.CasP3ProxyValidate()
|
||||
}
|
||||
|
||||
func (c *RootController) CasP3ServiceValidate() {
|
||||
ticket := c.Ctx.Input.Query("ticket")
|
||||
format := c.Ctx.Input.Query("format")
|
||||
if !strings.HasPrefix(ticket, "ST") {
|
||||
c.sendCasAuthenticationResponseErr(InvalidTicket, fmt.Sprintf("Ticket %s not recognized", ticket), format)
|
||||
}
|
||||
c.CasP3ProxyValidate()
|
||||
}
|
||||
|
||||
func (c *RootController) CasP3ProxyValidate() {
|
||||
ticket := c.Ctx.Input.Query("ticket")
|
||||
format := c.Ctx.Input.Query("format")
|
||||
service := c.Ctx.Input.Query("service")
|
||||
pgtUrl := c.Ctx.Input.Query("pgtUrl")
|
||||
|
||||
serviceResponse := object.CasServiceResponse{
|
||||
Xmlns: "http://www.yale.edu/tp/cas",
|
||||
}
|
||||
|
||||
// check whether all required parameters are met
|
||||
if service == "" || ticket == "" {
|
||||
c.sendCasAuthenticationResponseErr(InvalidRequest, "service and ticket must exist", format)
|
||||
return
|
||||
}
|
||||
ok, response, issuedService, userId := object.GetCasTokenByTicket(ticket)
|
||||
// find the token
|
||||
if ok {
|
||||
// check whether service is the one for which we previously issued token
|
||||
if strings.HasPrefix(service, issuedService) || strings.HasPrefix(queryUnescape(service), issuedService) {
|
||||
serviceResponse.Success = response
|
||||
} else {
|
||||
// service not match
|
||||
c.sendCasAuthenticationResponseErr(InvalidService, fmt.Sprintf("service %s and %s does not match", service, issuedService), format)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// token not found
|
||||
c.sendCasAuthenticationResponseErr(InvalidTicket, fmt.Sprintf("Ticket %s not recognized", ticket), format)
|
||||
return
|
||||
}
|
||||
|
||||
if pgtUrl != "" && serviceResponse.Failure == nil {
|
||||
// that means we are in proxy web flow
|
||||
pgt := object.StoreCasTokenForPgt(serviceResponse.Success, service, userId)
|
||||
pgtiou := serviceResponse.Success.ProxyGrantingTicket
|
||||
// todo: check whether it is https
|
||||
pgtUrlObj, err := url.Parse(pgtUrl)
|
||||
if err != nil {
|
||||
c.sendCasAuthenticationResponseErr(InvalidProxyCallback, err.Error(), format)
|
||||
return
|
||||
}
|
||||
|
||||
if pgtUrlObj.Scheme != "https" {
|
||||
c.sendCasAuthenticationResponseErr(InvalidProxyCallback, "callback is not https", format)
|
||||
return
|
||||
}
|
||||
|
||||
// make a request to pgturl passing pgt and pgtiou
|
||||
param := pgtUrlObj.Query()
|
||||
param.Add("pgtId", pgt)
|
||||
param.Add("pgtIou", pgtiou)
|
||||
pgtUrlObj.RawQuery = param.Encode()
|
||||
|
||||
request, err := http.NewRequest("GET", pgtUrlObj.String(), nil)
|
||||
if err != nil {
|
||||
c.sendCasAuthenticationResponseErr(InternalError, err.Error(), format)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(request)
|
||||
if err != nil || !(resp.StatusCode >= 200 && resp.StatusCode < 400) {
|
||||
// failed to send request
|
||||
c.sendCasAuthenticationResponseErr(InvalidProxyCallback, err.Error(), format)
|
||||
return
|
||||
}
|
||||
}
|
||||
// everything is ok, send the response
|
||||
if format == "json" {
|
||||
c.Data["json"] = serviceResponse
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
c.Data["xml"] = serviceResponse
|
||||
c.ServeXML()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *RootController) CasProxy() {
|
||||
pgt := c.Ctx.Input.Query("pgt")
|
||||
targetService := c.Ctx.Input.Query("targetService")
|
||||
format := c.Ctx.Input.Query("format")
|
||||
if pgt == "" || targetService == "" {
|
||||
c.sendCasProxyResponseErr(InvalidRequest, "pgt and targetService must exist", format)
|
||||
return
|
||||
}
|
||||
|
||||
ok, authenticationSuccess, issuedService, userId := object.GetCasTokenByPgt(pgt)
|
||||
if !ok {
|
||||
c.sendCasProxyResponseErr(UnauthorizedService, "service not authorized", format)
|
||||
return
|
||||
}
|
||||
|
||||
newAuthenticationSuccess := authenticationSuccess.DeepCopy()
|
||||
if newAuthenticationSuccess.Proxies == nil {
|
||||
newAuthenticationSuccess.Proxies = &object.CasProxies{}
|
||||
}
|
||||
newAuthenticationSuccess.Proxies.Proxies = append(newAuthenticationSuccess.Proxies.Proxies, issuedService)
|
||||
proxyTicket := object.StoreCasTokenForProxyTicket(&newAuthenticationSuccess, targetService, userId)
|
||||
|
||||
serviceResponse := object.CasServiceResponse{
|
||||
Xmlns: "http://www.yale.edu/tp/cas",
|
||||
ProxySuccess: &object.CasProxySuccess{
|
||||
ProxyTicket: proxyTicket,
|
||||
},
|
||||
}
|
||||
|
||||
if format == "json" {
|
||||
c.Data["json"] = serviceResponse
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
c.Data["xml"] = serviceResponse
|
||||
c.ServeXML()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *RootController) SamlValidate() {
|
||||
c.Ctx.Output.Header("Content-Type", "text/xml; charset=utf-8")
|
||||
target := c.Ctx.Input.Query("TARGET")
|
||||
body := c.Ctx.Input.RequestBody
|
||||
envelopRequest := struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Body struct {
|
||||
XMLName xml.Name `xml:"Body"`
|
||||
Content string `xml:",innerxml"`
|
||||
}
|
||||
}{}
|
||||
|
||||
err := xml.Unmarshal(body, &envelopRequest)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response, service, err := object.GetValidationBySaml(envelopRequest.Body.Content, c.Ctx.Request.Host)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(target, service) {
|
||||
c.ResponseError(fmt.Sprintf(c.T("cas:Service %s and %s do not match"), target, service))
|
||||
return
|
||||
}
|
||||
|
||||
envelopResponse := struct {
|
||||
XMLName xml.Name `xml:"SOAP-ENV:Envelope"`
|
||||
Xmlns string `xml:"xmlns:SOAP-ENV"`
|
||||
Body struct {
|
||||
XMLName xml.Name `xml:"SOAP-ENV:Body"`
|
||||
Content string `xml:",innerxml"`
|
||||
}
|
||||
}{}
|
||||
envelopResponse.Xmlns = "http://schemas.xmlsoap.org/soap/envelope/"
|
||||
envelopResponse.Body.Content = response
|
||||
|
||||
data, err := xml.Marshal(envelopResponse)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
c.Ctx.Output.Body(data)
|
||||
}
|
||||
|
||||
func (c *RootController) sendCasProxyResponseErr(code, msg, format string) {
|
||||
serviceResponse := object.CasServiceResponse{
|
||||
Xmlns: "http://www.yale.edu/tp/cas",
|
||||
ProxyFailure: &object.CasProxyFailure{
|
||||
Code: code,
|
||||
Message: msg,
|
||||
},
|
||||
}
|
||||
if format == "json" {
|
||||
c.Data["json"] = serviceResponse
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
c.Data["xml"] = serviceResponse
|
||||
c.ServeXML()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *RootController) sendCasAuthenticationResponseErr(code, msg, format string) {
|
||||
serviceResponse := object.CasServiceResponse{
|
||||
Xmlns: "http://www.yale.edu/tp/cas",
|
||||
Failure: &object.CasAuthenticationFailure{
|
||||
Code: code,
|
||||
Message: msg,
|
||||
},
|
||||
}
|
||||
if format == "json" {
|
||||
c.Data["json"] = serviceResponse
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
c.Data["xml"] = serviceResponse
|
||||
c.ServeXML()
|
||||
}
|
||||
}
|
||||
@@ -1,385 +0,0 @@
|
||||
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// Enforce
|
||||
// @Title Enforce
|
||||
// @Tag Enforcer API
|
||||
// @Description Call Casbin Enforce API
|
||||
// @Param body body []string true "Casbin request"
|
||||
// @Param permissionId query string false "permission id"
|
||||
// @Param modelId query string false "model id"
|
||||
// @Param resourceId query string false "resource id"
|
||||
// @Param owner query string false "owner"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /enforce [post]
|
||||
func (c *ApiController) Enforce() {
|
||||
permissionId := c.Ctx.Input.Query("permissionId")
|
||||
modelId := c.Ctx.Input.Query("modelId")
|
||||
resourceId := c.Ctx.Input.Query("resourceId")
|
||||
enforcerId := c.Ctx.Input.Query("enforcerId")
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
|
||||
params := []string{permissionId, modelId, resourceId, enforcerId, owner}
|
||||
nonEmpty := 0
|
||||
for _, param := range params {
|
||||
if param != "" {
|
||||
nonEmpty++
|
||||
}
|
||||
}
|
||||
if nonEmpty > 1 {
|
||||
c.ResponseError("Only one of the parameters (permissionId, modelId, resourceId, enforcerId, owner) should be provided")
|
||||
return
|
||||
}
|
||||
|
||||
if len(c.Ctx.Input.RequestBody) == 0 {
|
||||
c.ResponseError("The request body should not be empty")
|
||||
return
|
||||
}
|
||||
|
||||
// Accept both plain string arrays (["alice","data1","read"]) and mixed arrays
|
||||
// with JSON objects ([{"DivisionGuid":"x"}, "resource", "read"]) for ABAC support.
|
||||
var request []interface{}
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &request)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if enforcerId != "" {
|
||||
enforcer, err := object.GetInitializedEnforcer(enforcerId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res := []bool{}
|
||||
keyRes := []string{}
|
||||
|
||||
// Convert elements: JSON-object strings and maps become anonymous structs for ABAC.
|
||||
interfaceRequest := util.InterfaceToEnforceArray(request)
|
||||
|
||||
enforceResult, err := enforcer.Enforce(interfaceRequest...)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res = append(res, enforceResult)
|
||||
keyRes = append(keyRes, enforcer.GetModelAndAdapter())
|
||||
|
||||
c.ResponseOk(res, keyRes)
|
||||
return
|
||||
}
|
||||
|
||||
if permissionId != "" {
|
||||
permission, err := object.GetPermission(permissionId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if permission == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("permission:The permission: \"%s\" doesn't exist"), permissionId))
|
||||
return
|
||||
}
|
||||
|
||||
res := []bool{}
|
||||
keyRes := []string{}
|
||||
|
||||
enforceResult, err := object.Enforce(permission, request)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res = append(res, enforceResult)
|
||||
keyRes = append(keyRes, permission.GetModelAndAdapter())
|
||||
|
||||
c.ResponseOk(res, keyRes)
|
||||
return
|
||||
}
|
||||
|
||||
permissions := []*object.Permission{}
|
||||
if modelId != "" {
|
||||
owner, modelName, err := util.GetOwnerAndNameFromIdWithError(modelId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
permissions, err = object.GetPermissionsByModel(owner, modelName)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
} else if resourceId != "" {
|
||||
permissions, err = object.GetPermissionsByResource(resourceId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
} else if owner != "" {
|
||||
permissions, err = object.GetPermissions(owner)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
c.ResponseError(c.T("general:Missing parameter"))
|
||||
return
|
||||
}
|
||||
|
||||
res := []bool{}
|
||||
keyRes := []string{}
|
||||
listPermissionIdMap := object.GroupPermissionsByModelAdapter(permissions)
|
||||
for key, permissionIds := range listPermissionIdMap {
|
||||
firstPermission, err := object.GetPermission(permissionIds[0])
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
enforceResult, err := object.Enforce(firstPermission, request, permissionIds...)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res = append(res, enforceResult)
|
||||
keyRes = append(keyRes, key)
|
||||
}
|
||||
|
||||
c.ResponseOk(res, keyRes)
|
||||
}
|
||||
|
||||
// BatchEnforce
|
||||
// @Title BatchEnforce
|
||||
// @Tag Enforcer API
|
||||
// @Description Call Casbin BatchEnforce API
|
||||
// @Param body body []string true "array of casbin requests"
|
||||
// @Param permissionId query string false "permission id"
|
||||
// @Param modelId query string false "model id"
|
||||
// @Param owner query string false "owner"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /batch-enforce [post]
|
||||
func (c *ApiController) BatchEnforce() {
|
||||
permissionId := c.Ctx.Input.Query("permissionId")
|
||||
modelId := c.Ctx.Input.Query("modelId")
|
||||
enforcerId := c.Ctx.Input.Query("enforcerId")
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
|
||||
params := []string{permissionId, modelId, enforcerId, owner}
|
||||
nonEmpty := 0
|
||||
for _, param := range params {
|
||||
if param != "" {
|
||||
nonEmpty++
|
||||
}
|
||||
}
|
||||
if nonEmpty > 1 {
|
||||
c.ResponseError("Only one of the parameters (permissionId, modelId, enforcerId, owner) should be provided")
|
||||
return
|
||||
}
|
||||
|
||||
// Accept both string arrays and mixed arrays with JSON objects for ABAC support.
|
||||
var requests [][]interface{}
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &requests)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if enforcerId != "" {
|
||||
enforcer, err := object.GetInitializedEnforcer(enforcerId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res := [][]bool{}
|
||||
keyRes := []string{}
|
||||
|
||||
// Convert elements: JSON-object strings and maps become anonymous structs for ABAC.
|
||||
interfaceRequests := util.InterfaceToEnforceArray2d(requests)
|
||||
|
||||
enforceResult, err := enforcer.BatchEnforce(interfaceRequests)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res = append(res, enforceResult)
|
||||
keyRes = append(keyRes, enforcer.GetModelAndAdapter())
|
||||
|
||||
c.ResponseOk(res, keyRes)
|
||||
return
|
||||
}
|
||||
|
||||
if permissionId != "" {
|
||||
permission, err := object.GetPermission(permissionId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if permission == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("permission:The permission: \"%s\" doesn't exist"), permissionId))
|
||||
return
|
||||
}
|
||||
|
||||
res := [][]bool{}
|
||||
keyRes := []string{}
|
||||
|
||||
enforceResult, err := object.BatchEnforce(permission, requests)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res = append(res, enforceResult)
|
||||
keyRes = append(keyRes, permission.GetModelAndAdapter())
|
||||
|
||||
c.ResponseOk(res, keyRes)
|
||||
return
|
||||
}
|
||||
|
||||
permissions := []*object.Permission{}
|
||||
if modelId != "" {
|
||||
owner, modelName, err := util.GetOwnerAndNameFromIdWithError(modelId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
permissions, err = object.GetPermissionsByModel(owner, modelName)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
} else if owner != "" {
|
||||
permissions, err = object.GetPermissions(owner)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
c.ResponseError(c.T("general:Missing parameter"))
|
||||
return
|
||||
}
|
||||
|
||||
res := [][]bool{}
|
||||
keyRes := []string{}
|
||||
listPermissionIdMap := object.GroupPermissionsByModelAdapter(permissions)
|
||||
for _, permissionIds := range listPermissionIdMap {
|
||||
firstPermission, err := object.GetPermission(permissionIds[0])
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
enforceResult, err := object.BatchEnforce(firstPermission, requests, permissionIds...)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res = append(res, enforceResult)
|
||||
keyRes = append(keyRes, firstPermission.GetModelAndAdapter())
|
||||
}
|
||||
|
||||
c.ResponseOk(res, keyRes)
|
||||
}
|
||||
|
||||
// GetAllObjects
|
||||
// @Title GetAllObjects
|
||||
// @Tag Enforcer API
|
||||
// @Description Get all objects for a user (Casbin API)
|
||||
// @Param userId query string false "user id like built-in/admin"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /get-all-objects [get]
|
||||
func (c *ApiController) GetAllObjects() {
|
||||
userId := c.Ctx.Input.Query("userId")
|
||||
if userId == "" {
|
||||
userId = c.GetSessionUsername()
|
||||
if userId == "" {
|
||||
c.ResponseError(c.T("general:Please login first"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
objects, err := object.GetAllObjects(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(objects)
|
||||
}
|
||||
|
||||
// GetAllActions
|
||||
// @Title GetAllActions
|
||||
// @Tag Enforcer API
|
||||
// @Description Get all actions for a user (Casbin API)
|
||||
// @Param userId query string false "user id like built-in/admin"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /get-all-actions [get]
|
||||
func (c *ApiController) GetAllActions() {
|
||||
userId := c.Ctx.Input.Query("userId")
|
||||
if userId == "" {
|
||||
userId = c.GetSessionUsername()
|
||||
if userId == "" {
|
||||
c.ResponseError(c.T("general:Please login first"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
actions, err := object.GetAllActions(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(actions)
|
||||
}
|
||||
|
||||
// GetAllRoles
|
||||
// @Title GetAllRoles
|
||||
// @Tag Enforcer API
|
||||
// @Description Get all roles for a user (Casbin API)
|
||||
// @Param userId query string false "user id like built-in/admin"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /get-all-roles [get]
|
||||
func (c *ApiController) GetAllRoles() {
|
||||
userId := c.Ctx.Input.Query("userId")
|
||||
if userId == "" {
|
||||
userId = c.GetSessionUsername()
|
||||
if userId == "" {
|
||||
c.ResponseError(c.T("general:Please login first"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
roles, err := object.GetAllRoles(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(roles)
|
||||
}
|
||||
@@ -1,319 +0,0 @@
|
||||
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
)
|
||||
|
||||
type CLIVersionInfo struct {
|
||||
Version string
|
||||
BinaryPath string
|
||||
BinaryTime time.Time
|
||||
}
|
||||
|
||||
var (
|
||||
cliVersionCache = make(map[string]*CLIVersionInfo)
|
||||
cliVersionMutex sync.RWMutex
|
||||
)
|
||||
|
||||
// cleanOldMEIFolders cleans up old _MEIXXX folders from the Casdoor temp directory
|
||||
// that are older than 24 hours. These folders are created by PyInstaller when
|
||||
// executing casbin-python-cli and can accumulate over time.
|
||||
func cleanOldMEIFolders() {
|
||||
tempDir := "temp"
|
||||
cutoffTime := time.Now().Add(-24 * time.Hour)
|
||||
|
||||
entries, err := os.ReadDir(tempDir)
|
||||
if err != nil {
|
||||
// Log error but don't fail - cleanup is best-effort
|
||||
// This is expected if temp directory doesn't exist yet
|
||||
return
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
// Check if the entry is a directory and matches the _MEI pattern
|
||||
if !entry.IsDir() || !strings.HasPrefix(entry.Name(), "_MEI") {
|
||||
continue
|
||||
}
|
||||
|
||||
dirPath := filepath.Join(tempDir, entry.Name())
|
||||
info, err := entry.Info()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if the folder is older than 24 hours
|
||||
if info.ModTime().Before(cutoffTime) {
|
||||
// Try to remove the directory
|
||||
err = os.RemoveAll(dirPath)
|
||||
if err != nil {
|
||||
// Log but continue with other folders
|
||||
fmt.Printf("failed to remove old MEI folder %s: %v\n", dirPath, err)
|
||||
} else {
|
||||
fmt.Printf("removed old MEI folder: %s\n", dirPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getCLIVersion
|
||||
// @Title getCLIVersion
|
||||
// @Description Get CLI version with cache mechanism
|
||||
// @Param language string The language of CLI (go/java/rust etc.)
|
||||
// @Return string The version string of CLI
|
||||
// @Return error Error if CLI execution fails
|
||||
func getCLIVersion(language string) (string, error) {
|
||||
binaryName := fmt.Sprintf("casbin-%s-cli", language)
|
||||
|
||||
binaryPath, err := exec.LookPath(binaryName)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("executable file not found: %v", err)
|
||||
}
|
||||
|
||||
fileInfo, err := os.Stat(binaryPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get binary info: %v", err)
|
||||
}
|
||||
|
||||
cliVersionMutex.RLock()
|
||||
if info, exists := cliVersionCache[language]; exists {
|
||||
if info.BinaryPath == binaryPath && info.BinaryTime == fileInfo.ModTime() {
|
||||
cliVersionMutex.RUnlock()
|
||||
return info.Version, nil
|
||||
}
|
||||
}
|
||||
cliVersionMutex.RUnlock()
|
||||
|
||||
// Clean up old _MEI folders before running the command
|
||||
cleanOldMEIFolders()
|
||||
|
||||
cmd := exec.Command(binaryName, "--version")
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get CLI version: %v", err)
|
||||
}
|
||||
|
||||
version := strings.TrimSpace(string(output))
|
||||
|
||||
cliVersionMutex.Lock()
|
||||
cliVersionCache[language] = &CLIVersionInfo{
|
||||
Version: version,
|
||||
BinaryPath: binaryPath,
|
||||
BinaryTime: fileInfo.ModTime(),
|
||||
}
|
||||
cliVersionMutex.Unlock()
|
||||
|
||||
return version, nil
|
||||
}
|
||||
|
||||
func processArgsToTempFiles(args []string) ([]string, []string, error) {
|
||||
tempFiles := []string{}
|
||||
newArgs := []string{}
|
||||
for i := 0; i < len(args); i++ {
|
||||
if (args[i] == "-m" || args[i] == "-p") && i+1 < len(args) {
|
||||
pattern := fmt.Sprintf("casbin_temp_%s_*.conf", args[i])
|
||||
tempFile, err := os.CreateTemp("", pattern)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create temp file: %v", err)
|
||||
}
|
||||
|
||||
_, err = tempFile.WriteString(args[i+1])
|
||||
if err != nil {
|
||||
tempFile.Close()
|
||||
return nil, nil, fmt.Errorf("failed to write to temp file: %v", err)
|
||||
}
|
||||
|
||||
tempFile.Close()
|
||||
tempFiles = append(tempFiles, tempFile.Name())
|
||||
newArgs = append(newArgs, args[i], tempFile.Name())
|
||||
i++
|
||||
} else {
|
||||
newArgs = append(newArgs, args[i])
|
||||
}
|
||||
}
|
||||
return tempFiles, newArgs, nil
|
||||
}
|
||||
|
||||
// RunCasbinCommand
|
||||
// @Title RunCasbinCommand
|
||||
// @Tag Enforcer API
|
||||
// @Description Call Casbin CLI commands
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /run-casbin-command [get]
|
||||
func (c *ApiController) RunCasbinCommand() {
|
||||
if !conf.IsDemoMode() && !c.IsAdmin() {
|
||||
c.ResponseError(c.T("auth:Unauthorized operation"))
|
||||
return
|
||||
}
|
||||
|
||||
if err := validateIdentifier(c); err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
language := c.Ctx.Input.Query("language")
|
||||
argString := c.Ctx.Input.Query("args")
|
||||
|
||||
if language == "" {
|
||||
language = "go"
|
||||
}
|
||||
// use "casbin-go-cli" by default, can be also "casbin-java-cli", "casbin-node-cli", etc.
|
||||
// the pre-built binary of "casbin-go-cli" can be found at: https://github.com/casbin/casbin-go-cli/releases
|
||||
binaryName := fmt.Sprintf("casbin-%s-cli", language)
|
||||
|
||||
_, err := exec.LookPath(binaryName)
|
||||
if err != nil {
|
||||
c.ResponseError(fmt.Sprintf("executable file: %s not found in PATH", binaryName))
|
||||
return
|
||||
}
|
||||
|
||||
// RBAC model & policy example:
|
||||
// https://door.casdoor.com/api/run-casbin-command?language=go&args=["enforce", "-m", "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[role_definition]\ng = _, _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub) %26%26 r.obj == p.obj %26%26 r.act == p.act", "-p", "p, alice, data1, read\np, bob, data2, write\np, data2_admin, data2, read\np, data2_admin, data2, write\ng, alice, data2_admin", "alice", "data1", "read"]
|
||||
// Casbin CLI usage:
|
||||
// https://github.com/jcasbin/casbin-java-cli?tab=readme-ov-file#get-started
|
||||
var args []string
|
||||
err = json.Unmarshal([]byte(argString), &args)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Generate cache key for this command
|
||||
cacheKey, err := generateCacheKey(language, args)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Check if result is cached
|
||||
if cachedOutput, found := getCachedCommandResult(cacheKey); found {
|
||||
c.ResponseOk(cachedOutput)
|
||||
return
|
||||
}
|
||||
|
||||
if len(args) > 0 && args[0] == "--version" {
|
||||
version, err := getCLIVersion(language)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
c.ResponseOk(version)
|
||||
return
|
||||
}
|
||||
|
||||
tempFiles, processedArgs, err := processArgsToTempFiles(args)
|
||||
defer func() {
|
||||
for _, file := range tempFiles {
|
||||
os.Remove(file)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Clean up old _MEI folders before running the command
|
||||
// This is especially important for Python CLI which creates these folders
|
||||
cleanOldMEIFolders()
|
||||
|
||||
command := exec.Command(binaryName, processedArgs...)
|
||||
outputBytes, err := command.CombinedOutput()
|
||||
if err != nil {
|
||||
errorString := err.Error()
|
||||
if outputBytes != nil {
|
||||
output := string(outputBytes)
|
||||
errorString = fmt.Sprintf("%s, error: %s", output, err.Error())
|
||||
}
|
||||
|
||||
c.ResponseError(errorString)
|
||||
return
|
||||
}
|
||||
|
||||
output := string(outputBytes)
|
||||
output = strings.TrimSuffix(output, "\n")
|
||||
|
||||
// Store result in cache
|
||||
setCachedCommandResult(cacheKey, output)
|
||||
|
||||
c.ResponseOk(output)
|
||||
}
|
||||
|
||||
// validateIdentifier
|
||||
// @Title validateIdentifier
|
||||
// @Description Validate the request hash and timestamp
|
||||
// @Param hash string The SHA-256 hash string
|
||||
// @Return error Returns error if validation fails, nil if successful
|
||||
func validateIdentifier(c *ApiController) error {
|
||||
language := c.Ctx.Input.Query("language")
|
||||
args := c.Ctx.Input.Query("args")
|
||||
hash := c.Ctx.Input.Query("m")
|
||||
timestamp := c.Ctx.Input.Query("t")
|
||||
|
||||
if hash == "" || timestamp == "" || language == "" || args == "" {
|
||||
return fmt.Errorf("invalid identifier")
|
||||
}
|
||||
|
||||
requestTime, err := time.Parse(time.RFC3339, timestamp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid identifier")
|
||||
}
|
||||
timeDiff := time.Since(requestTime)
|
||||
if timeDiff > 5*time.Minute || timeDiff < -5*time.Minute {
|
||||
return fmt.Errorf("invalid identifier")
|
||||
}
|
||||
|
||||
params := map[string]string{
|
||||
"language": language,
|
||||
"args": args,
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(params))
|
||||
for k := range params {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
var paramParts []string
|
||||
for _, k := range keys {
|
||||
paramParts = append(paramParts, fmt.Sprintf("%s=%s", k, params[k]))
|
||||
}
|
||||
paramString := strings.Join(paramParts, "&")
|
||||
|
||||
version := "casbin-editor-v1"
|
||||
rawString := fmt.Sprintf("%s|%s|%s", version, timestamp, paramString)
|
||||
|
||||
hasher := sha256.New()
|
||||
hasher.Write([]byte(rawString))
|
||||
|
||||
calculatedHash := strings.ToLower(hex.EncodeToString(hasher.Sum(nil)))
|
||||
if calculatedHash != strings.ToLower(hash) {
|
||||
return fmt.Errorf("invalid identifier")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CommandCacheEntry struct {
|
||||
Output string
|
||||
CachedTime time.Time
|
||||
}
|
||||
|
||||
var (
|
||||
commandCache = make(map[string]*CommandCacheEntry)
|
||||
commandCacheMutex sync.RWMutex
|
||||
cacheTTL = 5 * time.Minute
|
||||
cleanupInProgress = false
|
||||
cleanupMutex sync.Mutex
|
||||
)
|
||||
|
||||
// generateCacheKey creates a unique cache key based on language and arguments
|
||||
func generateCacheKey(language string, args []string) (string, error) {
|
||||
argsJSON, err := json.Marshal(args)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to marshal args: %v", err)
|
||||
}
|
||||
data := fmt.Sprintf("%s:%s", language, string(argsJSON))
|
||||
hash := sha256.Sum256([]byte(data))
|
||||
return hex.EncodeToString(hash[:]), nil
|
||||
}
|
||||
|
||||
// cleanExpiredCacheEntries removes expired entries from the cache
|
||||
func cleanExpiredCacheEntries() {
|
||||
commandCacheMutex.Lock()
|
||||
defer commandCacheMutex.Unlock()
|
||||
|
||||
for key, entry := range commandCache {
|
||||
if time.Since(entry.CachedTime) >= cacheTTL {
|
||||
delete(commandCache, key)
|
||||
}
|
||||
}
|
||||
|
||||
cleanupMutex.Lock()
|
||||
cleanupInProgress = false
|
||||
cleanupMutex.Unlock()
|
||||
}
|
||||
|
||||
// getCachedCommandResult retrieves cached command result if available and not expired
|
||||
func getCachedCommandResult(cacheKey string) (string, bool) {
|
||||
commandCacheMutex.RLock()
|
||||
defer commandCacheMutex.RUnlock()
|
||||
|
||||
if entry, exists := commandCache[cacheKey]; exists {
|
||||
if time.Since(entry.CachedTime) < cacheTTL {
|
||||
return entry.Output, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// setCachedCommandResult stores command result in cache and performs periodic cleanup
|
||||
func setCachedCommandResult(cacheKey string, output string) {
|
||||
commandCacheMutex.Lock()
|
||||
commandCache[cacheKey] = &CommandCacheEntry{
|
||||
Output: output,
|
||||
CachedTime: time.Now(),
|
||||
}
|
||||
shouldCleanup := len(commandCache)%100 == 0
|
||||
commandCacheMutex.Unlock()
|
||||
|
||||
// Periodically clean expired entries (every 100 cache sets)
|
||||
if shouldCleanup {
|
||||
cleanupMutex.Lock()
|
||||
if !cleanupInProgress {
|
||||
cleanupInProgress = true
|
||||
cleanupMutex.Unlock()
|
||||
go cleanExpiredCacheEntries()
|
||||
} else {
|
||||
cleanupMutex.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,222 +0,0 @@
|
||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/beego/beego/v2/core/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetCerts
|
||||
// @Title GetCerts
|
||||
// @Tag Cert API
|
||||
// @Description get certs
|
||||
// @Param owner query string true "The owner of certs"
|
||||
// @Success 200 {array} object.Cert The Response object
|
||||
// @router /get-certs [get]
|
||||
func (c *ApiController) GetCerts() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
certs, err := object.GetMaskedCerts(object.GetCerts(owner))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(certs)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetCertCount(owner, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
certs, err := object.GetMaskedCerts(object.GetPaginationCerts(owner, paginator.Offset(), limit, field, value, sortField, sortOrder))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(certs, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetGlobalCerts
|
||||
// @Title GetGlobalCerts
|
||||
// @Tag Cert API
|
||||
// @Description get global certs
|
||||
// @Success 200 {array} object.Cert The Response object
|
||||
// @router /get-global-certs [get]
|
||||
func (c *ApiController) GetGlobalCerts() {
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
certs, err := object.GetMaskedCerts(object.GetGlobalCerts())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(certs)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetGlobalCertsCount(field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
certs, err := object.GetMaskedCerts(object.GetPaginationGlobalCerts(paginator.Offset(), limit, field, value, sortField, sortOrder))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(certs, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetCert
|
||||
// @Title GetCert
|
||||
// @Tag Cert API
|
||||
// @Description get cert
|
||||
// @Param id query string true "The id ( owner/name ) of the cert"
|
||||
// @Success 200 {object} object.Cert The Response object
|
||||
// @router /get-cert [get]
|
||||
func (c *ApiController) GetCert() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
cert, err := object.GetCert(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetMaskedCert(cert))
|
||||
}
|
||||
|
||||
// UpdateCert
|
||||
// @Title UpdateCert
|
||||
// @Tag Cert API
|
||||
// @Description update cert
|
||||
// @Param id query string true "The id ( owner/name ) of the cert"
|
||||
// @Param body body object.Cert true "The details of the cert"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-cert [post]
|
||||
func (c *ApiController) UpdateCert() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
var cert object.Cert
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &cert)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateCert(id, &cert))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddCert
|
||||
// @Title AddCert
|
||||
// @Tag Cert API
|
||||
// @Description add cert
|
||||
// @Param body body object.Cert true "The details of the cert"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-cert [post]
|
||||
func (c *ApiController) AddCert() {
|
||||
var cert object.Cert
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &cert)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddCert(&cert))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteCert
|
||||
// @Title DeleteCert
|
||||
// @Tag Cert API
|
||||
// @Description delete cert
|
||||
// @Param body body object.Cert true "The details of the cert"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-cert [post]
|
||||
func (c *ApiController) DeleteCert() {
|
||||
var cert object.Cert
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &cert)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteCert(&cert))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// UpdateCertDomainExpire
|
||||
// @Title UpdateCertDomainExpire
|
||||
// @Tag Cert API
|
||||
// @Description update cert domain expire time
|
||||
// @Param id query string true "The ID of the cert"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-cert-domain-expire [post]
|
||||
func (c *ApiController) UpdateCertDomainExpire() {
|
||||
if _, ok := c.RequireSignedIn(); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
id := c.Ctx.Input.Query("id")
|
||||
cert, err := object.GetCert(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
domainExpireTime, err := object.GetDomainExpireTime(cert.Name)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if domainExpireTime == "" {
|
||||
c.ResponseError("Failed to determine domain expiration time for domain " + cert.Name +
|
||||
". Please verify that the domain is valid, publicly resolvable, and has a retrievable expiration date, " +
|
||||
"or update the domain expiration time manually.")
|
||||
return
|
||||
}
|
||||
cert.DomainExpireTime = domainExpireTime
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateCert(id, cert))
|
||||
c.ServeJSON()
|
||||
}
|
||||
@@ -1,542 +0,0 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"archive/zip"
|
||||
"compress/gzip"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/beego/beego/v2/server/web"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
const (
|
||||
javaCliRepo = "https://api.github.com/repos/jcasbin/casbin-java-cli/releases/latest"
|
||||
goCliRepo = "https://api.github.com/repos/casbin/casbin-go-cli/releases/latest"
|
||||
rustCliRepo = "https://api.github.com/repos/casbin-rs/casbin-rust-cli/releases/latest"
|
||||
pythonCliRepo = "https://api.github.com/repos/casbin/casbin-python-cli/releases/latest"
|
||||
dotnetCliRepo = "https://api.github.com/repos/casbin-net/casbin-dotnet-cli/releases/latest"
|
||||
downloadFolder = "bin"
|
||||
)
|
||||
|
||||
type ReleaseInfo struct {
|
||||
TagName string `json:"tag_name"`
|
||||
Assets []struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"browser_download_url"`
|
||||
} `json:"assets"`
|
||||
}
|
||||
|
||||
// @Title getBinaryNames
|
||||
// @Description Get binary names for different platforms and architectures
|
||||
// @Success 200 {map[string]string} map[string]string "Binary names map"
|
||||
func getBinaryNames() map[string]string {
|
||||
const (
|
||||
golang = "go"
|
||||
java = "java"
|
||||
rust = "rust"
|
||||
python = "python"
|
||||
dotnet = "dotnet"
|
||||
)
|
||||
|
||||
arch := runtime.GOARCH
|
||||
archMap := map[string]struct{ goArch, rustArch string }{
|
||||
"amd64": {"x86_64", "x86_64"},
|
||||
"arm64": {"arm64", "aarch64"},
|
||||
}
|
||||
|
||||
archNames, ok := archMap[arch]
|
||||
if !ok {
|
||||
archNames = struct{ goArch, rustArch string }{arch, arch}
|
||||
}
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
return map[string]string{
|
||||
golang: fmt.Sprintf("casbin-go-cli_Windows_%s.zip", archNames.goArch),
|
||||
java: "casbin-java-cli.jar",
|
||||
rust: fmt.Sprintf("casbin-rust-cli-%s-pc-windows-gnu", archNames.rustArch),
|
||||
python: fmt.Sprintf("casbin-python-cli-windows-%s.exe", archNames.goArch),
|
||||
dotnet: fmt.Sprintf("casbin-dotnet-cli-windows-%s.exe", archNames.goArch),
|
||||
}
|
||||
case "darwin":
|
||||
return map[string]string{
|
||||
golang: fmt.Sprintf("casbin-go-cli_Darwin_%s.tar.gz", archNames.goArch),
|
||||
java: "casbin-java-cli.jar",
|
||||
rust: fmt.Sprintf("casbin-rust-cli-%s-apple-darwin", archNames.rustArch),
|
||||
python: fmt.Sprintf("casbin-python-cli-darwin-%s", archNames.goArch),
|
||||
dotnet: fmt.Sprintf("casbin-dotnet-cli-darwin-%s", archNames.goArch),
|
||||
}
|
||||
case "linux":
|
||||
return map[string]string{
|
||||
golang: fmt.Sprintf("casbin-go-cli_Linux_%s.tar.gz", archNames.goArch),
|
||||
java: "casbin-java-cli.jar",
|
||||
rust: fmt.Sprintf("casbin-rust-cli-%s-unknown-linux-gnu", archNames.rustArch),
|
||||
python: fmt.Sprintf("casbin-python-cli-linux-%s", archNames.goArch),
|
||||
dotnet: fmt.Sprintf("casbin-dotnet-cli-linux-%s", archNames.goArch),
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// @Title getFinalBinaryName
|
||||
// @Description Get final binary name for specific language
|
||||
// @Param lang string true "Language type (go/java/rust)"
|
||||
// @Success 200 {string} string "Final binary name"
|
||||
func getFinalBinaryName(lang string) string {
|
||||
switch lang {
|
||||
case "go":
|
||||
if runtime.GOOS == "windows" {
|
||||
return "casbin-go-cli.exe"
|
||||
}
|
||||
return "casbin-go-cli"
|
||||
case "java":
|
||||
return "casbin-java-cli.jar"
|
||||
case "rust":
|
||||
if runtime.GOOS == "windows" {
|
||||
return "casbin-rust-cli.exe"
|
||||
}
|
||||
return "casbin-rust-cli"
|
||||
case "python":
|
||||
if runtime.GOOS == "windows" {
|
||||
return "casbin-python-cli.exe"
|
||||
}
|
||||
return "casbin-python-cli"
|
||||
case "dotnet":
|
||||
if runtime.GOOS == "windows" {
|
||||
return "casbin-dotnet-cli.exe"
|
||||
}
|
||||
return "casbin-dotnet-cli"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// @Title getLatestCLIURL
|
||||
// @Description Get latest CLI download URL from GitHub
|
||||
// @Param repoURL string true "GitHub repository URL"
|
||||
// @Param language string true "Language type"
|
||||
// @Success 200 {string} string "Download URL and version"
|
||||
func getLatestCLIURL(repoURL string, language string) (string, string, error) {
|
||||
client := proxy.GetHttpClient(repoURL)
|
||||
resp, err := client.Get(repoURL)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to fetch release info: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var release ReleaseInfo
|
||||
if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
binaryNames := getBinaryNames()
|
||||
if binaryNames == nil {
|
||||
return "", "", fmt.Errorf("unsupported OS: %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
binaryName := binaryNames[language]
|
||||
for _, asset := range release.Assets {
|
||||
if asset.Name == binaryName {
|
||||
return asset.URL, release.TagName, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", "", fmt.Errorf("no suitable binary found for OS: %s, language: %s", runtime.GOOS, language)
|
||||
}
|
||||
|
||||
// @Title extractGoCliFile
|
||||
// @Description Extract the Go CLI file
|
||||
// @Param filePath string true "The file path"
|
||||
// @Success 200 {string} string "The extracted file path"
|
||||
// @router /extractGoCliFile [post]
|
||||
func extractGoCliFile(filePath string) error {
|
||||
tempDir := filepath.Join(downloadFolder, "temp")
|
||||
if err := os.MkdirAll(tempDir, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
if err := unzipFile(filePath, tempDir); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := untarFile(filePath, tempDir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
execName := "casbin-go-cli"
|
||||
if runtime.GOOS == "windows" {
|
||||
execName += ".exe"
|
||||
}
|
||||
|
||||
var execPath string
|
||||
err := filepath.Walk(tempDir, func(path string, info os.FileInfo, err error) error {
|
||||
if info.Name() == execName {
|
||||
execPath = path
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
finalPath := filepath.Join(downloadFolder, execName)
|
||||
if err := os.Rename(execPath, finalPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Remove(filePath)
|
||||
}
|
||||
|
||||
// @Title unzipFile
|
||||
// @Description Unzip the file
|
||||
// @Param zipPath string true "The zip file path"
|
||||
// @Param destDir string true "The destination directory"
|
||||
// @Success 200 {string} string "The extracted file path"
|
||||
// @router /unzipFile [post]
|
||||
func unzipFile(zipPath, destDir string) error {
|
||||
r, err := zip.OpenReader(zipPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
for _, f := range r.File {
|
||||
fpath := filepath.Join(destDir, f.Name)
|
||||
|
||||
if f.FileInfo().IsDir() {
|
||||
os.MkdirAll(fpath, os.ModePerm)
|
||||
continue
|
||||
}
|
||||
|
||||
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
outFile.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(outFile, rc)
|
||||
outFile.Close()
|
||||
rc.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// @Title untarFile
|
||||
// @Description Untar the file
|
||||
// @Param tarPath string true "The tar file path"
|
||||
// @Param destDir string true "The destination directory"
|
||||
// @Success 200 {string} string "The extracted file path"
|
||||
// @router /untarFile [post]
|
||||
func untarFile(tarPath, destDir string) error {
|
||||
file, err := os.Open(tarPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
gzr, err := gzip.NewReader(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer gzr.Close()
|
||||
|
||||
tr := tar.NewReader(gzr)
|
||||
|
||||
for {
|
||||
header, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path := filepath.Join(destDir, header.Name)
|
||||
|
||||
switch header.Typeflag {
|
||||
case tar.TypeDir:
|
||||
if err := os.MkdirAll(path, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
case tar.TypeReg:
|
||||
outFile, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(outFile, tr); err != nil {
|
||||
outFile.Close()
|
||||
return err
|
||||
}
|
||||
outFile.Close()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// @Title createJavaCliWrapper
|
||||
// @Description Create the Java CLI wrapper
|
||||
// @Param binPath string true "The binary path"
|
||||
// @Success 200 {string} string "The created file path"
|
||||
// @router /createJavaCliWrapper [post]
|
||||
func createJavaCliWrapper(binPath string) error {
|
||||
if runtime.GOOS == "windows" {
|
||||
// Create a Windows CMD file
|
||||
cmdPath := filepath.Join(binPath, "casbin-java-cli.cmd")
|
||||
cmdContent := fmt.Sprintf(`@echo off
|
||||
java -jar "%s\casbin-java-cli.jar" %%*`, binPath)
|
||||
|
||||
err := os.WriteFile(cmdPath, []byte(cmdContent), 0o755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create Java CLI wrapper: %v", err)
|
||||
}
|
||||
} else {
|
||||
// Create Unix shell script
|
||||
shPath := filepath.Join(binPath, "casbin-java-cli")
|
||||
shContent := fmt.Sprintf(`#!/bin/sh
|
||||
java -jar "%s/casbin-java-cli.jar" "$@"`, binPath)
|
||||
|
||||
err := os.WriteFile(shPath, []byte(shContent), 0o755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create Java CLI wrapper: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// @Title downloadCLI
|
||||
// @Description Download and setup CLI tools
|
||||
// @Success 200 {error} error "Error if any"
|
||||
func downloadCLI() error {
|
||||
pathEnv := os.Getenv("PATH")
|
||||
binPath, err := filepath.Abs(downloadFolder)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get absolute path to download directory: %v", err)
|
||||
}
|
||||
|
||||
if !strings.Contains(pathEnv, binPath) {
|
||||
newPath := fmt.Sprintf("%s%s%s", binPath, string(os.PathListSeparator), pathEnv)
|
||||
if err := os.Setenv("PATH", newPath); err != nil {
|
||||
return fmt.Errorf("failed to update PATH environment variable: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(downloadFolder, 0o755); err != nil {
|
||||
return fmt.Errorf("failed to create download directory: %v", err)
|
||||
}
|
||||
|
||||
repos := map[string]string{
|
||||
"java": javaCliRepo,
|
||||
"go": goCliRepo,
|
||||
"rust": rustCliRepo,
|
||||
"python": pythonCliRepo,
|
||||
"dotnet": dotnetCliRepo,
|
||||
}
|
||||
|
||||
for lang, repo := range repos {
|
||||
cliURL, version, err := getLatestCLIURL(repo, lang)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to get %s CLI URL: %v\n", lang, err)
|
||||
continue
|
||||
}
|
||||
|
||||
originalPath := filepath.Join(downloadFolder, getBinaryNames()[lang])
|
||||
fmt.Printf("downloading %s CLI: %s\n", lang, cliURL)
|
||||
|
||||
client := proxy.GetHttpClient(cliURL)
|
||||
resp, err := client.Get(cliURL)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to download %s CLI: %v\n", lang, err)
|
||||
continue
|
||||
}
|
||||
|
||||
func() {
|
||||
defer resp.Body.Close()
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(originalPath), 0o755); err != nil {
|
||||
fmt.Printf("failed to create directory for %s CLI: %v\n", lang, err)
|
||||
return
|
||||
}
|
||||
|
||||
tmpFile := originalPath + ".tmp"
|
||||
out, err := os.Create(tmpFile)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to create or write %s CLI: %v\n", lang, err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
out.Close()
|
||||
os.Remove(tmpFile)
|
||||
}()
|
||||
|
||||
if _, err = io.Copy(out, resp.Body); err != nil ||
|
||||
out.Close() != nil ||
|
||||
os.Rename(tmpFile, originalPath) != nil {
|
||||
fmt.Printf("failed to download %s CLI: %v\n", lang, err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
if lang == "go" {
|
||||
if err := extractGoCliFile(originalPath); err != nil {
|
||||
fmt.Printf("failed to extract Go CLI: %v\n", err)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
finalPath := filepath.Join(downloadFolder, getFinalBinaryName(lang))
|
||||
if err := os.Rename(originalPath, finalPath); err != nil {
|
||||
fmt.Printf("failed to rename %s CLI: %v\n", lang, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
execPath := filepath.Join(downloadFolder, getFinalBinaryName(lang))
|
||||
if err := os.Chmod(execPath, 0o755); err != nil {
|
||||
fmt.Printf("failed to set %s CLI execution permission: %v\n", lang, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("downloaded %s CLI version: %s\n", lang, version)
|
||||
|
||||
if lang == "java" {
|
||||
if err := createJavaCliWrapper(binPath); err != nil {
|
||||
fmt.Printf("failed to create Java CLI wrapper: %v\n", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// @Title RefreshEngines
|
||||
// @Tag CLI API
|
||||
// @Description Refresh all CLI engines
|
||||
// @Param m query string true "Hash for request validation"
|
||||
// @Param t query string true "Timestamp for request validation"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /refresh-engines [post]
|
||||
func (c *ApiController) RefreshEngines() {
|
||||
if !conf.IsDemoMode() && !c.IsAdmin() {
|
||||
c.ResponseError(c.T("auth:Unauthorized operation"))
|
||||
return
|
||||
}
|
||||
|
||||
hash := c.Ctx.Input.Query("m")
|
||||
timestamp := c.Ctx.Input.Query("t")
|
||||
|
||||
if hash == "" || timestamp == "" {
|
||||
c.ResponseError("invalid identifier")
|
||||
return
|
||||
}
|
||||
|
||||
requestTime, err := time.Parse(time.RFC3339, timestamp)
|
||||
if err != nil {
|
||||
c.ResponseError("invalid identifier")
|
||||
return
|
||||
}
|
||||
|
||||
timeDiff := time.Since(requestTime)
|
||||
if timeDiff > 5*time.Minute || timeDiff < -5*time.Minute {
|
||||
c.ResponseError("invalid identifier")
|
||||
return
|
||||
}
|
||||
|
||||
version := "casbin-editor-v1"
|
||||
rawString := fmt.Sprintf("%s|%s", version, timestamp)
|
||||
|
||||
hasher := sha256.New()
|
||||
hasher.Write([]byte(rawString))
|
||||
calculatedHash := strings.ToLower(hex.EncodeToString(hasher.Sum(nil)))
|
||||
|
||||
if calculatedHash != strings.ToLower(hash) {
|
||||
c.ResponseError("invalid identifier")
|
||||
return
|
||||
}
|
||||
|
||||
err = downloadCLI()
|
||||
if err != nil {
|
||||
c.ResponseError(fmt.Sprintf("failed to refresh engines: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(map[string]string{
|
||||
"status": "success",
|
||||
"message": "CLI engines updated successfully",
|
||||
})
|
||||
}
|
||||
|
||||
// @Title ScheduleCLIUpdater
|
||||
// @Description Start periodic CLI update scheduler
|
||||
func ScheduleCLIUpdater() {
|
||||
if !web.AppConfig.DefaultBool("isDemoMode", false) {
|
||||
return
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(1 * time.Hour)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
err := downloadCLI()
|
||||
if err != nil {
|
||||
fmt.Printf("failed to update CLI: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("CLI updated successfully")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @Title DownloadCLI
|
||||
// @Description Download the CLI
|
||||
// @Success 200 {string} string "The downloaded file path"
|
||||
// @router /downloadCLI [post]
|
||||
func DownloadCLI() error {
|
||||
return downloadCLI()
|
||||
}
|
||||
|
||||
// @Title InitCLIDownloader
|
||||
// @Description Initialize CLI downloader and start update scheduler
|
||||
func InitCLIDownloader() {
|
||||
if !web.AppConfig.DefaultBool("isDemoMode", false) {
|
||||
return
|
||||
}
|
||||
|
||||
util.SafeGoroutine(func() {
|
||||
err := DownloadCLI()
|
||||
if err != nil {
|
||||
fmt.Printf("failed to initialize CLI downloader: %v\n", err)
|
||||
}
|
||||
|
||||
ScheduleCLIUpdater()
|
||||
})
|
||||
}
|
||||
@@ -1,226 +0,0 @@
|
||||
// Copyright 2026 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
)
|
||||
|
||||
// RevokeConsent revokes a consent record
|
||||
// @Title RevokeConsent
|
||||
// @Tag Consent API
|
||||
// @Description revoke a consent record
|
||||
// @Param body body object.ConsentRecord true "The consent object"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /revoke-consent [post]
|
||||
func (c *ApiController) RevokeConsent() {
|
||||
userId := c.GetSessionUsername()
|
||||
if userId == "" {
|
||||
c.ResponseError(c.T("general:Please login first"))
|
||||
return
|
||||
}
|
||||
|
||||
var consent object.ConsentRecord
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &consent)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Validate that consent.Application is not empty
|
||||
if consent.Application == "" {
|
||||
c.ResponseError(c.T("general:Application cannot be empty"))
|
||||
return
|
||||
}
|
||||
|
||||
// Validate that GrantedScopes is not empty when scope-specific revoke is requested
|
||||
if len(consent.GrantedScopes) == 0 {
|
||||
c.ResponseError(c.T("general:Granted scopes cannot be empty"))
|
||||
return
|
||||
}
|
||||
|
||||
userObj, err := object.GetUser(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if userObj == nil {
|
||||
c.ResponseError(c.T("general:The user doesn't exist"))
|
||||
return
|
||||
}
|
||||
|
||||
newScopes := []object.ConsentRecord{}
|
||||
for _, record := range userObj.ApplicationScopes {
|
||||
if record.Application != consent.Application {
|
||||
// skip other applications
|
||||
newScopes = append(newScopes, record)
|
||||
continue
|
||||
}
|
||||
// revoke specified scopes
|
||||
revokeSet := make(map[string]bool)
|
||||
for _, s := range consent.GrantedScopes {
|
||||
revokeSet[s] = true
|
||||
}
|
||||
remaining := []string{}
|
||||
for _, s := range record.GrantedScopes {
|
||||
if !revokeSet[s] {
|
||||
remaining = append(remaining, s)
|
||||
}
|
||||
}
|
||||
if len(remaining) > 0 {
|
||||
// still have remaining scopes, keep the record and update
|
||||
record.GrantedScopes = remaining
|
||||
newScopes = append(newScopes, record)
|
||||
}
|
||||
// otherwise the application authorization is revoked, delete the whole record
|
||||
}
|
||||
userObj.ApplicationScopes = newScopes
|
||||
success, err := object.UpdateUser(userObj.GetId(), userObj, nil, false)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
c.ResponseOk(success)
|
||||
}
|
||||
|
||||
// GrantConsent grants consent for an OAuth application and returns authorization code
|
||||
// @Title GrantConsent
|
||||
// @Tag Consent API
|
||||
// @Description grant consent for an OAuth application and get authorization code
|
||||
// @Param body body object.ConsentRecord true "The consent object with OAuth parameters"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /grant-consent [post]
|
||||
func (c *ApiController) GrantConsent() {
|
||||
userId := c.GetSessionUsername()
|
||||
if userId == "" {
|
||||
c.ResponseError(c.T("general:Please login first"))
|
||||
return
|
||||
}
|
||||
|
||||
var request struct {
|
||||
Application string `json:"application"`
|
||||
Scopes []string `json:"grantedScopes"`
|
||||
ClientId string `json:"clientId"`
|
||||
Provider string `json:"provider"`
|
||||
SigninMethod string `json:"signinMethod"`
|
||||
ResponseType string `json:"responseType"`
|
||||
RedirectUri string `json:"redirectUri"`
|
||||
Scope string `json:"scope"`
|
||||
State string `json:"state"`
|
||||
Nonce string `json:"nonce"`
|
||||
Challenge string `json:"challenge"`
|
||||
Resource string `json:"resource"`
|
||||
}
|
||||
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &request)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Validate application by clientId
|
||||
application, err := object.GetApplicationByClientId(request.ClientId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if application == nil {
|
||||
c.ResponseError(c.T("general:Invalid client_id"))
|
||||
return
|
||||
}
|
||||
|
||||
// Verify that request.Application matches the application's actual ID
|
||||
if request.Application != application.GetId() {
|
||||
c.ResponseError(c.T("general:Invalid application"))
|
||||
return
|
||||
}
|
||||
|
||||
// Update user's ApplicationScopes
|
||||
userObj, err := object.GetUser(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if userObj == nil {
|
||||
c.ResponseError(c.T("general:User not found"))
|
||||
return
|
||||
}
|
||||
|
||||
appId := application.GetId()
|
||||
found := false
|
||||
// Insert new scope into existing applicationScopes
|
||||
for i, record := range userObj.ApplicationScopes {
|
||||
if record.Application == appId {
|
||||
existing := make(map[string]bool)
|
||||
for _, s := range userObj.ApplicationScopes[i].GrantedScopes {
|
||||
existing[s] = true
|
||||
}
|
||||
for _, s := range request.Scopes {
|
||||
if !existing[s] {
|
||||
userObj.ApplicationScopes[i].GrantedScopes = append(userObj.ApplicationScopes[i].GrantedScopes, s)
|
||||
existing[s] = true
|
||||
}
|
||||
}
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// create a new applicationScopes if not found
|
||||
if !found {
|
||||
uniqueScopes := []string{}
|
||||
existing := make(map[string]bool)
|
||||
for _, s := range request.Scopes {
|
||||
if !existing[s] {
|
||||
uniqueScopes = append(uniqueScopes, s)
|
||||
existing[s] = true
|
||||
}
|
||||
}
|
||||
userObj.ApplicationScopes = append(userObj.ApplicationScopes, object.ConsentRecord{
|
||||
Application: appId,
|
||||
GrantedScopes: uniqueScopes,
|
||||
})
|
||||
}
|
||||
|
||||
_, err = object.UpdateUser(userObj.GetId(), userObj, []string{"application_scopes"}, false)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Now get the OAuth code
|
||||
code, err := object.GetOAuthCode(
|
||||
userId,
|
||||
request.ClientId,
|
||||
request.Provider,
|
||||
request.SigninMethod,
|
||||
request.ResponseType,
|
||||
request.RedirectUri,
|
||||
request.Scope,
|
||||
request.State,
|
||||
request.Nonce,
|
||||
request.Challenge,
|
||||
request.Resource,
|
||||
c.Ctx.Request.Host,
|
||||
c.GetAcceptLanguage(),
|
||||
)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(code.Code)
|
||||
}
|
||||
@@ -1,307 +0,0 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/beego/beego/v2/core/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
xormadapter "github.com/casdoor/xorm-adapter/v3"
|
||||
)
|
||||
|
||||
// GetEnforcers
|
||||
// @Title GetEnforcers
|
||||
// @Tag Enforcer API
|
||||
// @Description get enforcers
|
||||
// @Param owner query string true "The owner of enforcers"
|
||||
// @Success 200 {array} object.Enforcer
|
||||
// @router /get-enforcers [get]
|
||||
func (c *ApiController) GetEnforcers() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
enforcers, err := object.GetEnforcers(owner)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(enforcers)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetEnforcerCount(owner, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
enforcers, err := object.GetPaginationEnforcers(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(enforcers, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetEnforcer
|
||||
// @Title GetEnforcer
|
||||
// @Tag Enforcer API
|
||||
// @Description get enforcer
|
||||
// @Param id query string true "The id ( owner/name ) of enforcer"
|
||||
// @Success 200 {object} object.Enforcer
|
||||
// @router /get-enforcer [get]
|
||||
func (c *ApiController) GetEnforcer() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
loadModelCfg := c.Ctx.Input.Query("loadModelCfg")
|
||||
|
||||
enforcer, err := object.GetEnforcer(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if enforcer != nil {
|
||||
if loadModelCfg == "true" && enforcer.Model != "" {
|
||||
err = enforcer.LoadModelCfg()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.ResponseOk(enforcer)
|
||||
}
|
||||
|
||||
// UpdateEnforcer
|
||||
// @Title UpdateEnforcer
|
||||
// @Tag Enforcer API
|
||||
// @Description update enforcer
|
||||
// @Param id query string true "The id ( owner/name ) of enforcer"
|
||||
// @Param enforcer body object true "The enforcer object"
|
||||
// @Success 200 {object} object.Enforcer
|
||||
// @router /update-enforcer [post]
|
||||
func (c *ApiController) UpdateEnforcer() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
enforcer := object.Enforcer{}
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &enforcer)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateEnforcer(id, &enforcer))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddEnforcer
|
||||
// @Title AddEnforcer
|
||||
// @Tag Enforcer API
|
||||
// @Description add enforcer
|
||||
// @Param enforcer body object true "The enforcer object"
|
||||
// @Success 200 {object} object.Enforcer
|
||||
// @router /add-enforcer [post]
|
||||
func (c *ApiController) AddEnforcer() {
|
||||
enforcer := object.Enforcer{}
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &enforcer)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddEnforcer(&enforcer))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteEnforcer
|
||||
// @Title DeleteEnforcer
|
||||
// @Tag Enforcer API
|
||||
// @Description delete enforcer
|
||||
// @Param body body object.Enforcer true "The enforcer object"
|
||||
// @Success 200 {object} object.Enforcer
|
||||
// @router /delete-enforcer [post]
|
||||
func (c *ApiController) DeleteEnforcer() {
|
||||
var enforcer object.Enforcer
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &enforcer)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteEnforcer(&enforcer))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetPolicies
|
||||
// @Title GetPolicies
|
||||
// @Tag Enforcer API
|
||||
// @Description get policies
|
||||
// @Param id query string true "The id ( owner/name ) of enforcer"
|
||||
// @Param adapterId query string false "The adapter id"
|
||||
// @Success 200 {array} xormadapter.CasbinRule
|
||||
// @router /get-policies [get]
|
||||
func (c *ApiController) GetPolicies() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
adapterId := c.Ctx.Input.Query("adapterId")
|
||||
|
||||
if adapterId != "" {
|
||||
adapter, err := object.GetAdapter(adapterId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if adapter == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("enforcer:the adapter: %s is not found"), adapterId))
|
||||
return
|
||||
}
|
||||
|
||||
err = adapter.InitAdapter()
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk()
|
||||
return
|
||||
}
|
||||
|
||||
policies, err := object.GetPolicies(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(policies)
|
||||
}
|
||||
|
||||
// GetFilteredPolicies
|
||||
// @Title GetFilteredPolicies
|
||||
// @Tag Enforcer API
|
||||
// @Description get filtered policies with support for multiple filters via POST body
|
||||
// @Param id query string true "The id ( owner/name ) of enforcer"
|
||||
// @Param body body []object.Filter true "Array of filter objects for multiple filters"
|
||||
// @Success 200 {array} xormadapter.CasbinRule
|
||||
// @router /get-filtered-policies [post]
|
||||
func (c *ApiController) GetFilteredPolicies() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
var filters []object.Filter
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &filters)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
filteredPolicies, err := object.GetFilteredPoliciesMulti(id, filters)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(filteredPolicies)
|
||||
}
|
||||
|
||||
// UpdatePolicy
|
||||
// @Title UpdatePolicy
|
||||
// @Tag Enforcer API
|
||||
// @Description update policy
|
||||
// @Param id query string true "The id ( owner/name ) of enforcer"
|
||||
// @Param body body []xormadapter.CasbinRule true "Array containing old and new policy"
|
||||
// @Success 200 {object} Response
|
||||
// @router /update-policy [post]
|
||||
func (c *ApiController) UpdatePolicy() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
var policies []xormadapter.CasbinRule
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &policies)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
affected, err := object.UpdatePolicy(id, policies[0].Ptype, util.CasbinToSlice(policies[0]), util.CasbinToSlice(policies[1]))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
c.Data["json"] = wrapActionResponse(affected)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddPolicy
|
||||
// @Title AddPolicy
|
||||
// @Tag Enforcer API
|
||||
// @Description add policy
|
||||
// @Param id query string true "The id ( owner/name ) of enforcer"
|
||||
// @Param body body xormadapter.CasbinRule true "The policy to add"
|
||||
// @Success 200 {object} Response
|
||||
// @router /add-policy [post]
|
||||
func (c *ApiController) AddPolicy() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
var policy xormadapter.CasbinRule
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &policy)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
affected, err := object.AddPolicy(id, policy.Ptype, util.CasbinToSlice(policy))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
c.Data["json"] = wrapActionResponse(affected)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// RemovePolicy
|
||||
// @Title RemovePolicy
|
||||
// @Tag Enforcer API
|
||||
// @Description remove policy
|
||||
// @Param id query string true "The id ( owner/name ) of enforcer"
|
||||
// @Param body body xormadapter.CasbinRule true "The policy to remove"
|
||||
// @Success 200 {object} Response
|
||||
// @router /remove-policy [post]
|
||||
func (c *ApiController) RemovePolicy() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
var policy xormadapter.CasbinRule
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &policy)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
affected, err := object.RemovePolicy(id, policy.Ptype, util.CasbinToSlice(policy))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
c.Data["json"] = wrapActionResponse(affected)
|
||||
c.ServeJSON()
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
// Copyright 2026 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/beego/beego/v2/server/web/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetEntries
|
||||
// @Title GetEntries
|
||||
// @Tag Entry API
|
||||
// @Description get entries
|
||||
// @Param owner query string true "The owner of entries"
|
||||
// @Success 200 {array} object.Entry The Response object
|
||||
// @router /get-entries [get]
|
||||
func (c *ApiController) GetEntries() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
if owner == "admin" {
|
||||
owner = ""
|
||||
}
|
||||
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
entries, err := object.GetEntries(owner)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
c.ResponseOk(entries)
|
||||
return
|
||||
}
|
||||
|
||||
limitInt := util.ParseInt(limit)
|
||||
count, err := object.GetEntryCount(owner, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.SetPaginator(c.Ctx, limitInt, count)
|
||||
entries, err := object.GetPaginationEntries(owner, paginator.Offset(), limitInt, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(entries, paginator.Nums())
|
||||
}
|
||||
|
||||
// GetEntry
|
||||
// @Title GetEntry
|
||||
// @Tag Entry API
|
||||
// @Description get entry
|
||||
// @Param id query string true "The id ( owner/name ) of the entry"
|
||||
// @Success 200 {object} object.Entry The Response object
|
||||
// @router /get-entry [get]
|
||||
func (c *ApiController) GetEntry() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
entry, err := object.GetEntry(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(entry)
|
||||
}
|
||||
|
||||
// GetOpenClawSessionGraph
|
||||
// @Title GetOpenClawSessionGraph
|
||||
// @Tag Entry API
|
||||
// @Description get OpenClaw session graph
|
||||
// @Param id query string true "The id ( owner/name ) of the entry"
|
||||
// @Success 200 {object} object.OpenClawSessionGraph The Response object
|
||||
// @router /get-openclaw-session-graph [get]
|
||||
func (c *ApiController) GetOpenClawSessionGraph() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
graph, err := object.GetOpenClawSessionGraph(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(graph)
|
||||
}
|
||||
|
||||
// UpdateEntry
|
||||
// @Title UpdateEntry
|
||||
// @Tag Entry API
|
||||
// @Description update entry
|
||||
// @Param id query string true "The id ( owner/name ) of the entry"
|
||||
// @Param body body object.Entry true "The details of the entry"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-entry [post]
|
||||
func (c *ApiController) UpdateEntry() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
var entry object.Entry
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &entry)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateEntry(id, &entry))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddEntry
|
||||
// @Title AddEntry
|
||||
// @Tag Entry API
|
||||
// @Description add entry
|
||||
// @Param body body object.Entry true "The details of the entry"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-entry [post]
|
||||
func (c *ApiController) AddEntry() {
|
||||
var entry object.Entry
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &entry)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddEntry(&entry))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteEntry
|
||||
// @Title DeleteEntry
|
||||
// @Tag Entry API
|
||||
// @Description delete entry
|
||||
// @Param body body object.Entry true "The details of the entry"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-entry [post]
|
||||
func (c *ApiController) DeleteEntry() {
|
||||
var entry object.Entry
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &entry)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteEntry(&entry))
|
||||
c.ServeJSON()
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
// Copyright 2026 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
collogspb "go.opentelemetry.io/proto/otlp/collector/logs/v1"
|
||||
colmetricspb "go.opentelemetry.io/proto/otlp/collector/metrics/v1"
|
||||
coltracepb "go.opentelemetry.io/proto/otlp/collector/trace/v1"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// @Title AddOtlpTrace
|
||||
// @Tag OTLP API
|
||||
// @Description receive otlp trace protobuf
|
||||
// @Success 200 {object} string
|
||||
// @router /api/v1/traces [post]
|
||||
func (c *ApiController) AddOtlpTrace() {
|
||||
body := readProtobufBody(c.Ctx)
|
||||
if body == nil {
|
||||
return
|
||||
}
|
||||
provider, status, err := resolveOpenClawProvider(c.Ctx)
|
||||
if err != nil {
|
||||
responseOtlpError(c.Ctx, status, body, "%s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var req coltracepb.ExportTraceServiceRequest
|
||||
if err := proto.Unmarshal(body, &req); err != nil {
|
||||
responseOtlpError(c.Ctx, 400, body, "bad protobuf: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
message, err := protojson.MarshalOptions{Multiline: true, Indent: " "}.Marshal(&req)
|
||||
if err != nil {
|
||||
responseOtlpError(c.Ctx, 500, body, "marshal trace failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
|
||||
userAgent := c.Ctx.Request.Header.Get("User-Agent")
|
||||
if err := provider.AddTrace(message, clientIp, userAgent); err != nil {
|
||||
responseOtlpError(c.Ctx, 500, body, "save trace failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
resp, _ := proto.Marshal(&coltracepb.ExportTraceServiceResponse{})
|
||||
c.Ctx.Output.Header("Content-Type", "application/x-protobuf")
|
||||
c.Ctx.Output.SetStatus(200)
|
||||
c.Ctx.Output.Body(resp)
|
||||
}
|
||||
|
||||
// @Title AddOtlpMetrics
|
||||
// @Tag OTLP API
|
||||
// @Description receive otlp metrics protobuf
|
||||
// @Success 200 {object} string
|
||||
// @router /api/v1/metrics [post]
|
||||
func (c *ApiController) AddOtlpMetrics() {
|
||||
body := readProtobufBody(c.Ctx)
|
||||
if body == nil {
|
||||
return
|
||||
}
|
||||
provider, status, err := resolveOpenClawProvider(c.Ctx)
|
||||
if err != nil {
|
||||
responseOtlpError(c.Ctx, status, body, "%s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var req colmetricspb.ExportMetricsServiceRequest
|
||||
if err := proto.Unmarshal(body, &req); err != nil {
|
||||
responseOtlpError(c.Ctx, 400, body, "bad protobuf: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
message, err := protojson.MarshalOptions{Multiline: true, Indent: " "}.Marshal(&req)
|
||||
if err != nil {
|
||||
responseOtlpError(c.Ctx, 500, body, "marshal metrics failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
|
||||
userAgent := c.Ctx.Request.Header.Get("User-Agent")
|
||||
if err := provider.AddMetrics(message, clientIp, userAgent); err != nil {
|
||||
responseOtlpError(c.Ctx, 500, body, "save metrics failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
resp, _ := proto.Marshal(&colmetricspb.ExportMetricsServiceResponse{})
|
||||
c.Ctx.Output.Header("Content-Type", "application/x-protobuf")
|
||||
c.Ctx.Output.SetStatus(200)
|
||||
c.Ctx.Output.Body(resp)
|
||||
}
|
||||
|
||||
// @Title AddOtlpLogs
|
||||
// @Tag OTLP API
|
||||
// @Description receive otlp logs protobuf
|
||||
// @Success 200 {object} string
|
||||
// @router /api/v1/logs [post]
|
||||
func (c *ApiController) AddOtlpLogs() {
|
||||
body := readProtobufBody(c.Ctx)
|
||||
if body == nil {
|
||||
return
|
||||
}
|
||||
provider, status, err := resolveOpenClawProvider(c.Ctx)
|
||||
if err != nil {
|
||||
responseOtlpError(c.Ctx, status, body, "%s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var req collogspb.ExportLogsServiceRequest
|
||||
if err := proto.Unmarshal(body, &req); err != nil {
|
||||
responseOtlpError(c.Ctx, 400, body, "bad protobuf: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
message, err := protojson.MarshalOptions{Multiline: true, Indent: " "}.Marshal(&req)
|
||||
if err != nil {
|
||||
responseOtlpError(c.Ctx, 500, body, "marshal logs failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
|
||||
userAgent := c.Ctx.Request.Header.Get("User-Agent")
|
||||
if err := provider.AddLogs(message, clientIp, userAgent); err != nil {
|
||||
responseOtlpError(c.Ctx, 500, body, "save logs failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
resp, _ := proto.Marshal(&collogspb.ExportLogsServiceResponse{})
|
||||
c.Ctx.Output.Header("Content-Type", "application/x-protobuf")
|
||||
c.Ctx.Output.SetStatus(200)
|
||||
c.Ctx.Output.Body(resp)
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
// Copyright 2026 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/beego/beego/v2/server/web/context"
|
||||
"github.com/casdoor/casdoor/log"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func responseOtlpError(ctx *context.Context, status int, body []byte, format string, args ...interface{}) {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
req := ctx.Request
|
||||
bodyInfo := "(no body)"
|
||||
if len(body) > 0 {
|
||||
bodyInfo = fmt.Sprintf("%d bytes: %q", len(body), truncate(body, 256))
|
||||
}
|
||||
fmt.Printf("responseOtlpError: [%d] %s | %s %s | remoteAddr=%s | Content-Type=%s | User-Agent=%s | body=%s\n",
|
||||
status, msg,
|
||||
req.Method, req.URL.Path,
|
||||
req.RemoteAddr,
|
||||
req.Header.Get("Content-Type"),
|
||||
req.Header.Get("User-Agent"),
|
||||
bodyInfo,
|
||||
)
|
||||
ctx.Output.SetStatus(status)
|
||||
ctx.Output.Body([]byte(msg))
|
||||
}
|
||||
|
||||
func truncate(b []byte, max int) []byte {
|
||||
if len(b) <= max {
|
||||
return b
|
||||
}
|
||||
return b[:max]
|
||||
}
|
||||
|
||||
func resolveOpenClawProvider(ctx *context.Context) (*log.OpenClawProvider, int, error) {
|
||||
clientIP := util.GetClientIpFromRequest(ctx.Request)
|
||||
provider, err := object.GetOpenClawProviderByIP(clientIP)
|
||||
if err != nil {
|
||||
return nil, 500, fmt.Errorf("provider lookup failed: %w", err)
|
||||
}
|
||||
if provider == nil {
|
||||
return nil, 403, fmt.Errorf("forbidden: no OpenClaw provider configured for IP %s", clientIP)
|
||||
}
|
||||
return provider, 0, nil
|
||||
}
|
||||
|
||||
func readProtobufBody(ctx *context.Context) []byte {
|
||||
if !strings.HasPrefix(ctx.Input.Header("Content-Type"), "application/x-protobuf") {
|
||||
preview, _ := io.ReadAll(io.LimitReader(ctx.Request.Body, 256))
|
||||
responseOtlpError(ctx, 415, preview, "unsupported content type")
|
||||
return nil
|
||||
}
|
||||
body, err := io.ReadAll(ctx.Request.Body)
|
||||
if err != nil {
|
||||
responseOtlpError(ctx, 400, nil, "read body failed")
|
||||
return nil
|
||||
}
|
||||
return body
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Casdoor will expose its providers as services to SDK
|
||||
// We are going to implement those services as APIs here
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// FaceIDSigninBegin
|
||||
// @Title FaceIDSigninBegin
|
||||
// @Tag Login API
|
||||
// @Description FaceId Login Flow 1st stage
|
||||
// @Param owner query string true "owner"
|
||||
// @Param name query string true "name"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /faceid-signin-begin [get]
|
||||
func (c *ApiController) FaceIDSigninBegin() {
|
||||
userOwner := c.Ctx.Input.Query("owner")
|
||||
userName := c.Ctx.Input.Query("name")
|
||||
|
||||
user, err := object.GetUserByFields(userOwner, userName)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if user == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(userOwner, userName)))
|
||||
return
|
||||
}
|
||||
|
||||
if len(user.FaceIds) == 0 {
|
||||
c.ResponseError(c.T("check:Face data does not exist, cannot log in"))
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk()
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
// Copyright 2025 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/beego/beego/v2/core/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetGlobalForms
|
||||
// @Title GetGlobalForms
|
||||
// @Tag Form API
|
||||
// @Description get global forms
|
||||
// @Success 200 {array} object.Form The Response object
|
||||
// @router /get-global-forms [get]
|
||||
func (c *ApiController) GetGlobalForms() {
|
||||
forms, err := object.GetGlobalForms()
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetMaskedForms(forms, true))
|
||||
}
|
||||
|
||||
// GetForms
|
||||
// @Title GetForms
|
||||
// @Tag Form API
|
||||
// @Description get forms
|
||||
// @Param owner query string true "The owner of form"
|
||||
// @Success 200 {array} object.Form The Response object
|
||||
// @router /get-forms [get]
|
||||
func (c *ApiController) GetForms() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
forms, err := object.GetForms(owner)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetMaskedForms(forms, true))
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetFormCount(owner, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
forms, err := object.GetPaginationForms(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
c.ResponseOk(forms, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetForm
|
||||
// @Title GetForm
|
||||
// @Tag Form API
|
||||
// @Description get form
|
||||
// @Param id query string true "The id (owner/name) of form"
|
||||
// @Success 200 {object} object.Form The Response object
|
||||
// @router /get-form [get]
|
||||
func (c *ApiController) GetForm() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
form, err := object.GetForm(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetMaskedForm(form, true))
|
||||
}
|
||||
|
||||
// UpdateForm
|
||||
// @Title UpdateForm
|
||||
// @Tag Form API
|
||||
// @Description update form
|
||||
// @Param id query string true "The id (owner/name) of the form"
|
||||
// @Param body body object.Form true "The details of the form"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-form [post]
|
||||
func (c *ApiController) UpdateForm() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
var form object.Form
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
success, err := object.UpdateForm(id, &form)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(success)
|
||||
}
|
||||
|
||||
// AddForm
|
||||
// @Title AddForm
|
||||
// @Tag Form API
|
||||
// @Description add form
|
||||
// @Param body body object.Form true "The details of the form"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-form [post]
|
||||
func (c *ApiController) AddForm() {
|
||||
var form object.Form
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
success, err := object.AddForm(&form)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(success)
|
||||
}
|
||||
|
||||
// DeleteForm
|
||||
// @Title DeleteForm
|
||||
// @Tag Form API
|
||||
// @Description delete form
|
||||
// @Param body body object.Form true "The details of the form"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-form [post]
|
||||
func (c *ApiController) DeleteForm() {
|
||||
var form object.Form
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
success, err := object.DeleteForm(&form)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(success)
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import "github.com/casdoor/casdoor/object"
|
||||
|
||||
// GetDashboard
|
||||
// @Title GetDashboard
|
||||
// @Tag System API
|
||||
// @Description get information of dashboard
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /get-dashboard [get]
|
||||
func (c *ApiController) GetDashboard() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
|
||||
data, err := object.GetDashboard(owner)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(data)
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/beego/beego/v2/core/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetGroups
|
||||
// @Title GetGroups
|
||||
// @Tag Group API
|
||||
// @Description get groups
|
||||
// @Param owner query string true "The owner of groups"
|
||||
// @Success 200 {array} object.Group The Response object
|
||||
// @router /get-groups [get]
|
||||
func (c *ApiController) GetGroups() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
withTree := c.Ctx.Input.Query("withTree")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
groups, err := object.GetGroups(owner)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = object.ExtendGroupsWithUsers(groups)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if withTree == "true" {
|
||||
c.ResponseOk(object.ConvertToTreeData(groups, owner))
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(groups)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetGroupCount(owner, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
groups, err := object.GetPaginationGroups(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
groupsHaveChildrenMap, err := object.GetGroupsHaveChildrenMap(groups)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
for _, group := range groups {
|
||||
_, ok := groupsHaveChildrenMap[group.GetId()]
|
||||
if ok {
|
||||
group.HaveChildren = true
|
||||
}
|
||||
|
||||
parent, ok := groupsHaveChildrenMap[fmt.Sprintf("%s/%s", group.Owner, group.ParentId)]
|
||||
if ok {
|
||||
group.ParentName = parent.DisplayName
|
||||
}
|
||||
}
|
||||
|
||||
err = object.ExtendGroupsWithUsers(groups)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(groups, paginator.Nums())
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// GetGroup
|
||||
// @Title GetGroup
|
||||
// @Tag Group API
|
||||
// @Description get group
|
||||
// @Param id query string true "The id ( owner/name ) of the group"
|
||||
// @Success 200 {object} object.Group The Response object
|
||||
// @router /get-group [get]
|
||||
func (c *ApiController) GetGroup() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
group, err := object.GetGroup(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = object.ExtendGroupWithUsers(group)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(group)
|
||||
}
|
||||
|
||||
// UpdateGroup
|
||||
// @Title UpdateGroup
|
||||
// @Tag Group API
|
||||
// @Description update group
|
||||
// @Param id query string true "The id ( owner/name ) of the group"
|
||||
// @Param body body object.Group true "The details of the group"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-group [post]
|
||||
func (c *ApiController) UpdateGroup() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
var group object.Group
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &group)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateGroup(id, &group))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddGroup
|
||||
// @Title AddGroup
|
||||
// @Tag Group API
|
||||
// @Description add group
|
||||
// @Param body body object.Group true "The details of the group"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-group [post]
|
||||
func (c *ApiController) AddGroup() {
|
||||
var group object.Group
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &group)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddGroup(&group))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteGroup
|
||||
// @Title DeleteGroup
|
||||
// @Tag Group API
|
||||
// @Description delete group
|
||||
// @Param body body object.Group true "The details of the group"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-group [post]
|
||||
func (c *ApiController) DeleteGroup() {
|
||||
var group object.Group
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &group)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteGroup(&group))
|
||||
c.ServeJSON()
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
// Copyright 2025 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func (c *ApiController) UploadGroups() {
|
||||
userId := c.GetSessionUsername()
|
||||
owner, user, err := util.GetOwnerAndNameFromIdWithError(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
file, header, err := c.Ctx.Request.FormFile("file")
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fileId := fmt.Sprintf("%s_%s_%s", owner, user, util.RemoveExt(header.Filename))
|
||||
path := util.GetUploadXlsxPath(fileId)
|
||||
defer os.Remove(path)
|
||||
|
||||
err = saveFile(path, &file)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
affected, err := object.UploadGroups(owner, path)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if affected {
|
||||
c.ResponseOk()
|
||||
} else {
|
||||
c.ResponseError(c.T("general:Failed to import groups"))
|
||||
}
|
||||
}
|
||||
@@ -1,283 +0,0 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/beego/beego/v2/core/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetInvitations
|
||||
// @Title GetInvitations
|
||||
// @Tag Invitation API
|
||||
// @Description get invitations
|
||||
// @Param owner query string true "The owner of invitations"
|
||||
// @Success 200 {array} object.Invitation The Response object
|
||||
// @router /get-invitations [get]
|
||||
func (c *ApiController) GetInvitations() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
invitations, err := object.GetInvitations(owner)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(invitations)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetInvitationCount(owner, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
invitations, err := object.GetPaginationInvitations(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(invitations, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetInvitation
|
||||
// @Title GetInvitation
|
||||
// @Tag Invitation API
|
||||
// @Description get invitation
|
||||
// @Param id query string true "The id ( owner/name ) of the invitation"
|
||||
// @Success 200 {object} object.Invitation The Response object
|
||||
// @router /get-invitation [get]
|
||||
func (c *ApiController) GetInvitation() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
invitation, err := object.GetInvitation(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(invitation)
|
||||
}
|
||||
|
||||
// GetInvitationCodeInfo
|
||||
// @Title GetInvitationCodeInfo
|
||||
// @Tag Invitation API
|
||||
// @Description get invitation code information
|
||||
// @Param code query string true "Invitation code"
|
||||
// @Success 200 {object} object.Invitation The Response object
|
||||
// @router /get-invitation-info [get]
|
||||
func (c *ApiController) GetInvitationCodeInfo() {
|
||||
code := c.Ctx.Input.Query("code")
|
||||
applicationId := c.Ctx.Input.Query("applicationId")
|
||||
|
||||
application, err := object.GetApplication(applicationId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), applicationId))
|
||||
return
|
||||
}
|
||||
|
||||
invitation, msg := object.GetInvitationByCode(code, application.Organization, c.GetAcceptLanguage())
|
||||
if msg != "" {
|
||||
c.ResponseError(msg)
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetMaskedInvitation(invitation))
|
||||
}
|
||||
|
||||
// UpdateInvitation
|
||||
// @Title UpdateInvitation
|
||||
// @Tag Invitation API
|
||||
// @Description update invitation
|
||||
// @Param id query string true "The id ( owner/name ) of the invitation"
|
||||
// @Param body body object.Invitation true "The details of the invitation"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-invitation [post]
|
||||
func (c *ApiController) UpdateInvitation() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
var invitation object.Invitation
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &invitation)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateInvitation(id, &invitation, c.GetAcceptLanguage()))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddInvitation
|
||||
// @Title AddInvitation
|
||||
// @Tag Invitation API
|
||||
// @Description add invitation
|
||||
// @Param body body object.Invitation true "The details of the invitation"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-invitation [post]
|
||||
func (c *ApiController) AddInvitation() {
|
||||
var invitation object.Invitation
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &invitation)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddInvitation(&invitation, c.GetAcceptLanguage()))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteInvitation
|
||||
// @Title DeleteInvitation
|
||||
// @Tag Invitation API
|
||||
// @Description delete invitation
|
||||
// @Param body body object.Invitation true "The details of the invitation"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-invitation [post]
|
||||
func (c *ApiController) DeleteInvitation() {
|
||||
var invitation object.Invitation
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &invitation)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteInvitation(&invitation))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// VerifyInvitation
|
||||
// @Title VerifyInvitation
|
||||
// @Tag Invitation API
|
||||
// @Description verify invitation
|
||||
// @Param id query string true "The id ( owner/name ) of the invitation"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /verify-invitation [get]
|
||||
func (c *ApiController) VerifyInvitation() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
payment, attachInfo, err := object.VerifyInvitation(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(payment, attachInfo)
|
||||
}
|
||||
|
||||
// SendInvitation
|
||||
// @Title VerifyInvitation
|
||||
// @Tag Invitation API
|
||||
// @Description verify invitation
|
||||
// @Param id query string true "The id ( owner/name ) of the invitation"
|
||||
// @Param body body []string true "The details of the invitation"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /send-invitation [post]
|
||||
func (c *ApiController) SendInvitation() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
var destinations []string
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &destinations)
|
||||
|
||||
if !c.IsAdmin() {
|
||||
c.ResponseError(c.T("auth:Unauthorized operation"))
|
||||
return
|
||||
}
|
||||
|
||||
invitation, err := object.GetInvitation(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if invitation == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("invitation:Invitation %s does not exist"), id))
|
||||
return
|
||||
}
|
||||
|
||||
organization, err := object.GetOrganization(fmt.Sprintf("admin/%s", invitation.Owner))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if organization == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The organization: %s does not exist"), invitation.Owner))
|
||||
return
|
||||
}
|
||||
|
||||
var application *object.Application
|
||||
if invitation.Application != "" {
|
||||
application, err = object.GetApplication(fmt.Sprintf("admin/%s-org-%s", invitation.Application, invitation.Owner))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
application, err = object.GetApplicationByOrganizationName(invitation.Owner)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The organization: %s should have one application at least"), invitation.Owner))
|
||||
return
|
||||
}
|
||||
|
||||
if application.IsShared {
|
||||
application.Name = fmt.Sprintf("%s-org-%s", application.Name, invitation.Owner)
|
||||
}
|
||||
|
||||
provider, err := application.GetEmailProvider("Invitation")
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if provider == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("verification:please add an Email provider to the \"Providers\" list for the application: %s"), invitation.Owner))
|
||||
return
|
||||
}
|
||||
|
||||
content := provider.Metadata
|
||||
|
||||
content = strings.ReplaceAll(content, "%code", invitation.Code)
|
||||
content = strings.ReplaceAll(content, "%link", invitation.GetInvitationLink(c.Ctx.Request.Host, application.Name))
|
||||
|
||||
err = object.SendEmail(provider, provider.Title, content, destinations, organization.DisplayName)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk()
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
// Copyright 2026 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/form"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// KerberosLogin
|
||||
// @Title KerberosLogin
|
||||
// @Tag Login API
|
||||
// @Description Kerberos/SPNEGO login via Integrated Windows Authentication
|
||||
// @Param application query string true "application name"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /kerberos-login [get]
|
||||
func (c *ApiController) KerberosLogin() {
|
||||
applicationName := c.Ctx.Input.Query("application")
|
||||
if applicationName == "" {
|
||||
c.ResponseError(c.T("general:Missing parameter") + ": application")
|
||||
return
|
||||
}
|
||||
|
||||
application, err := object.GetApplication(fmt.Sprintf("admin/%s", applicationName))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), applicationName))
|
||||
return
|
||||
}
|
||||
|
||||
organization, err := object.GetOrganization(util.GetId("admin", application.Organization))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if organization == nil {
|
||||
c.ResponseError(fmt.Sprintf("The organization: %s does not exist", application.Organization))
|
||||
return
|
||||
}
|
||||
|
||||
if organization.KerberosRealm == "" || organization.KerberosKeytab == "" {
|
||||
c.ResponseError("Kerberos is not configured for this organization")
|
||||
return
|
||||
}
|
||||
|
||||
authHeader := c.Ctx.Input.Header("Authorization")
|
||||
if authHeader == "" || !strings.HasPrefix(authHeader, "Negotiate ") {
|
||||
c.Ctx.Output.Header("WWW-Authenticate", "Negotiate")
|
||||
c.Ctx.Output.SetStatus(401)
|
||||
c.Ctx.Output.Body([]byte("Kerberos authentication required"))
|
||||
return
|
||||
}
|
||||
|
||||
spnegoToken := strings.TrimPrefix(authHeader, "Negotiate ")
|
||||
|
||||
kerberosUsername, err := object.ValidateKerberosToken(organization, spnegoToken)
|
||||
if err != nil {
|
||||
c.Ctx.Output.Header("WWW-Authenticate", "Negotiate")
|
||||
c.ResponseError(fmt.Sprintf("Kerberos authentication failed: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
user, err := object.GetUserByKerberosName(organization.Name, kerberosUsername)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if user == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), kerberosUsername))
|
||||
return
|
||||
}
|
||||
|
||||
application.OrganizationObj = organization
|
||||
|
||||
authForm := &form.AuthForm{
|
||||
Type: "code",
|
||||
Application: applicationName,
|
||||
Organization: organization.Name,
|
||||
}
|
||||
|
||||
resp := c.HandleLoggedIn(application, user, authForm)
|
||||
if resp != nil {
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
}
|
||||
}
|
||||
@@ -1,222 +0,0 @@
|
||||
// Copyright 2026 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/beego/beego/v2/core/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetKeys
|
||||
// @Title GetKeys
|
||||
// @Tag Key API
|
||||
// @Description get keys
|
||||
// @Param owner query string true "The owner of keys"
|
||||
// @Success 200 {array} object.Key The Response object
|
||||
// @router /get-keys [get]
|
||||
func (c *ApiController) GetKeys() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
keys, err := object.GetKeys(owner)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
maskedKeys, err := object.GetMaskedKeys(keys, true, nil)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(maskedKeys)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetKeyCount(owner, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
keys, err := object.GetPaginationKeys(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
maskedKeys, err := object.GetMaskedKeys(keys, true, nil)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(maskedKeys, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetGlobalKeys
|
||||
// @Title GetGlobalKeys
|
||||
// @Tag Key API
|
||||
// @Description get global keys
|
||||
// @Success 200 {array} object.Key The Response object
|
||||
// @router /get-global-keys [get]
|
||||
func (c *ApiController) GetGlobalKeys() {
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
keys, err := object.GetGlobalKeys()
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
maskedKeys, err := object.GetMaskedKeys(keys, true, nil)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(maskedKeys)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetGlobalKeyCount(field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
keys, err := object.GetPaginationGlobalKeys(paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
maskedKeys, err := object.GetMaskedKeys(keys, true, nil)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(maskedKeys, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetKey
|
||||
// @Title GetKey
|
||||
// @Tag Key API
|
||||
// @Description get key
|
||||
// @Param id query string true "The id ( owner/name ) of the key"
|
||||
// @Success 200 {object} object.Key The Response object
|
||||
// @router /get-key [get]
|
||||
func (c *ApiController) GetKey() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
key, err := object.GetKey(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(key)
|
||||
}
|
||||
|
||||
// UpdateKey
|
||||
// @Title UpdateKey
|
||||
// @Tag Key API
|
||||
// @Description update key
|
||||
// @Param id query string true "The id ( owner/name ) of the key"
|
||||
// @Param body body object.Key true "The details of the key"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-key [post]
|
||||
func (c *ApiController) UpdateKey() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
oldKey, err := object.GetKey(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if oldKey == nil {
|
||||
c.Data["json"] = wrapActionResponse(false)
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
var key object.Key
|
||||
err = json.Unmarshal(c.Ctx.Input.RequestBody, &key)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if !c.IsGlobalAdmin() && oldKey.Owner != key.Owner {
|
||||
c.ResponseError(c.T("auth:Unauthorized operation"))
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateKey(id, &key))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddKey
|
||||
// @Title AddKey
|
||||
// @Tag Key API
|
||||
// @Description add key
|
||||
// @Param body body object.Key true "The details of the key"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-key [post]
|
||||
func (c *ApiController) AddKey() {
|
||||
var key object.Key
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &key)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddKey(&key))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteKey
|
||||
// @Title DeleteKey
|
||||
// @Tag Key API
|
||||
// @Description delete key
|
||||
// @Param body body object.Key true "The details of the key"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-key [post]
|
||||
func (c *ApiController) DeleteKey() {
|
||||
var key object.Key
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &key)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteKey(&key))
|
||||
c.ServeJSON()
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -16,284 +16,190 @@ package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/util"
|
||||
)
|
||||
|
||||
type LdapResp struct {
|
||||
// Groups []LdapRespGroup `json:"groups"`
|
||||
Users []object.LdapUser `json:"users"`
|
||||
ExistUuids []string `json:"existUuids"`
|
||||
type LdapServer struct {
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
Admin string `json:"admin"`
|
||||
Passwd string `json:"passwd"`
|
||||
BaseDn string `json:"baseDn"`
|
||||
}
|
||||
|
||||
// type LdapRespGroup struct {
|
||||
type LdapResp struct {
|
||||
//Groups []LdapRespGroup `json:"groups"`
|
||||
Users []object.LdapRespUser `json:"users"`
|
||||
}
|
||||
|
||||
//type LdapRespGroup struct {
|
||||
// GroupId string
|
||||
// GroupName string
|
||||
// }
|
||||
//}
|
||||
|
||||
type LdapSyncResp struct {
|
||||
Exist []object.LdapUser `json:"exist"`
|
||||
Failed []object.LdapUser `json:"failed"`
|
||||
Exist []object.LdapRespUser `json:"exist"`
|
||||
Failed []object.LdapRespUser `json:"failed"`
|
||||
}
|
||||
|
||||
// GetLdapUsers
|
||||
// @Title GetLdapser
|
||||
// @Tag Account API
|
||||
// @Description get ldap users
|
||||
// Param id string true "id"
|
||||
// @Success 200 {object} controllers.LdapResp The Response object
|
||||
// @router /get-ldap-users [get]
|
||||
func (c *ApiController) GetLdapUsers() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
func (c *ApiController) GetLdapUser() {
|
||||
ldapServer := LdapServer{}
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldapServer)
|
||||
if err != nil || util.IsStrsEmpty(ldapServer.Host, ldapServer.Admin, ldapServer.Passwd, ldapServer.BaseDn) {
|
||||
c.ResponseError("Missing parameter")
|
||||
return
|
||||
}
|
||||
|
||||
_, ldapId, err := util.GetOwnerAndNameFromIdWithError(id)
|
||||
var resp LdapResp
|
||||
|
||||
conn, err := object.GetLdapConn(ldapServer.Host, ldapServer.Port, ldapServer.Admin, ldapServer.Passwd)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
ldapServer, err := object.GetLdap(ldapId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if ldapServer == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The LDAP: %s does not exist"), ldapId))
|
||||
return
|
||||
}
|
||||
|
||||
conn, err := ldapServer.GetLdapConn()
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// groupsMap, err := conn.GetLdapGroups(ldapServer.BaseDn)
|
||||
// if err != nil {
|
||||
//groupsMap, err := conn.GetLdapGroups(ldapServer.BaseDn)
|
||||
//if err != nil {
|
||||
// c.ResponseError(err.Error())
|
||||
// return
|
||||
// }
|
||||
//}
|
||||
|
||||
// for _, group := range groupsMap {
|
||||
//for _, group := range groupsMap {
|
||||
// resp.Groups = append(resp.Groups, LdapRespGroup{
|
||||
// GroupId: group.GidNumber,
|
||||
// GroupName: group.Cn,
|
||||
// })
|
||||
// }
|
||||
//}
|
||||
|
||||
users, err := conn.GetLdapUsers(ldapServer)
|
||||
users, err := conn.GetLdapUsers(ldapServer.BaseDn)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
uuids := make([]string, len(users))
|
||||
for i, user := range users {
|
||||
uuids[i] = user.GetLdapUuid()
|
||||
}
|
||||
existUuids, err := object.GetExistUuids(ldapServer.Owner, uuids)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
for _, user := range users {
|
||||
resp.Users = append(resp.Users, object.LdapRespUser{
|
||||
UidNumber: user.UidNumber,
|
||||
Uid: user.Uid,
|
||||
Cn: user.Cn,
|
||||
GroupId: user.GidNumber,
|
||||
//GroupName: groupsMap[user.GidNumber].Cn,
|
||||
Uuid: user.Uuid,
|
||||
Email: util.GetMaxLenStr(user.Mail, user.Email, user.EmailAddress),
|
||||
Phone: util.GetMaxLenStr(user.TelephoneNumber, user.Mobile, user.MobileTelephoneNumber),
|
||||
Address: util.GetMaxLenStr(user.RegisteredAddress, user.PostalAddress),
|
||||
})
|
||||
}
|
||||
|
||||
resp := LdapResp{
|
||||
Users: object.AutoAdjustLdapUser(users),
|
||||
ExistUuids: existUuids,
|
||||
}
|
||||
c.ResponseOk(resp)
|
||||
c.Data["json"] = Response{Status: "ok", Data: resp}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetLdaps
|
||||
// @Title GetLdaps
|
||||
// @Tag Account API
|
||||
// @Description get ldaps
|
||||
// @Param owner query string false "owner"
|
||||
// @Success 200 {array} object.Ldap The Response object
|
||||
// @router /get-ldaps [get]
|
||||
func (c *ApiController) GetLdaps() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
owner := c.Input().Get("owner")
|
||||
|
||||
c.ResponseOk(object.GetMaskedLdaps(object.GetLdaps(owner)))
|
||||
c.Data["json"] = Response{Status: "ok", Data: object.GetLdaps(owner)}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetLdap
|
||||
// @Title GetLdap
|
||||
// @Tag Account API
|
||||
// @Description get ldap
|
||||
// @Param id query string true "id"
|
||||
// @Success 200 {object} object.Ldap The Response object
|
||||
// @router /get-ldap [get]
|
||||
func (c *ApiController) GetLdap() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
id := c.Input().Get("id")
|
||||
|
||||
if util.IsStringsEmpty(id) {
|
||||
c.ResponseError(c.T("general:Missing parameter"))
|
||||
if util.IsStrsEmpty(id) {
|
||||
c.ResponseError("Missing parameter")
|
||||
return
|
||||
}
|
||||
|
||||
_, name, err := util.GetOwnerAndNameFromIdWithError(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
ldap, err := object.GetLdap(name)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
c.ResponseOk(object.GetMaskedLdap(ldap))
|
||||
c.Data["json"] = Response{Status: "ok", Data: object.GetLdap(id)}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddLdap
|
||||
// @Title AddLdap
|
||||
// @Tag Account API
|
||||
// @Description add ldap
|
||||
// @Param body body object.Ldap true "The details of the ldap"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-ldap [post]
|
||||
func (c *ApiController) AddLdap() {
|
||||
var ldap object.Ldap
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
c.ResponseError("Missing parameter")
|
||||
return
|
||||
}
|
||||
|
||||
if util.IsStringsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Username, ldap.Password, ldap.BaseDn) {
|
||||
c.ResponseError(c.T("general:Missing parameter"))
|
||||
if util.IsStrsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Admin, ldap.Passwd, ldap.BaseDn) {
|
||||
c.ResponseError("Missing parameter")
|
||||
return
|
||||
}
|
||||
|
||||
if ok, err := object.CheckLdapExist(&ldap); err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
} else if ok {
|
||||
c.ResponseError(c.T("ldap:Ldap server exist"))
|
||||
if object.CheckLdapExist(&ldap) {
|
||||
c.ResponseError("Ldap server exist")
|
||||
return
|
||||
}
|
||||
|
||||
resp := wrapActionResponse(object.AddLdap(&ldap))
|
||||
resp.Data2 = ldap
|
||||
|
||||
if ldap.AutoSync != 0 {
|
||||
err = object.GetLdapAutoSynchronizer().StartAutoSync(ldap.Id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
affected := object.AddLdap(&ldap)
|
||||
resp := wrapActionResponse(affected)
|
||||
if affected {
|
||||
resp.Data2 = ldap
|
||||
}
|
||||
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// UpdateLdap
|
||||
// @Title UpdateLdap
|
||||
// @Tag Account API
|
||||
// @Description update ldap
|
||||
// @Param body body object.Ldap true "The details of the ldap"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-ldap [post]
|
||||
func (c *ApiController) UpdateLdap() {
|
||||
var ldap object.Ldap
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
|
||||
if err != nil || util.IsStringsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Username, ldap.Password, ldap.BaseDn) {
|
||||
c.ResponseError(c.T("general:Missing parameter"))
|
||||
if err != nil || util.IsStrsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Admin, ldap.Passwd, ldap.BaseDn) {
|
||||
c.ResponseError("Missing parameter")
|
||||
return
|
||||
}
|
||||
|
||||
prevLdap, err := object.GetLdap(ldap.Id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
affected := object.UpdateLdap(&ldap)
|
||||
resp := wrapActionResponse(affected)
|
||||
if affected {
|
||||
resp.Data2 = ldap
|
||||
}
|
||||
|
||||
affected, err := object.UpdateLdap(&ldap)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if ldap.AutoSync != 0 {
|
||||
err := object.GetLdapAutoSynchronizer().StartAutoSync(ldap.Id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
} else if ldap.AutoSync == 0 && prevLdap.AutoSync != 0 {
|
||||
object.GetLdapAutoSynchronizer().StopAutoSync(ldap.Id)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(affected)
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteLdap
|
||||
// @Title DeleteLdap
|
||||
// @Tag Account API
|
||||
// @Description delete ldap
|
||||
// @Param body body object.Ldap true "The details of the ldap"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-ldap [post]
|
||||
func (c *ApiController) DeleteLdap() {
|
||||
var ldap object.Ldap
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
panic(err)
|
||||
}
|
||||
|
||||
affected, err := object.DeleteLdap(&ldap)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
object.GetLdapAutoSynchronizer().StopAutoSync(ldap.Id)
|
||||
|
||||
c.Data["json"] = wrapActionResponse(affected)
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteLdap(&ldap))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// SyncLdapUsers
|
||||
// @Title SyncLdapUsers
|
||||
// @Tag Account API
|
||||
// @Description sync ldap users
|
||||
// @Param id query string true "id"
|
||||
// @Success 200 {object} controllers.LdapSyncResp The Response object
|
||||
// @router /sync-ldap-users [post]
|
||||
func (c *ApiController) SyncLdapUsers() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
owner, ldapId, err := util.GetOwnerAndNameFromIdWithError(id)
|
||||
owner := c.Input().Get("owner")
|
||||
ldapId := c.Input().Get("ldapId")
|
||||
var users []object.LdapRespUser
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &users)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
var users []object.LdapUser
|
||||
err = json.Unmarshal(c.Ctx.Input.RequestBody, &users)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = object.UpdateLdapSyncTime(ldapId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
object.UpdateLdapSyncTime(ldapId)
|
||||
|
||||
exist, failed, err := object.SyncLdapUsers(owner, users, ldapId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(&LdapSyncResp{
|
||||
Exist: exist,
|
||||
Failed: failed,
|
||||
})
|
||||
exist, failed := object.SyncLdapUsers(owner, users)
|
||||
c.Data["json"] = &Response{Status: "ok", Data: &LdapSyncResp{
|
||||
Exist: *exist,
|
||||
Failed: *failed,
|
||||
}}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) CheckLdapUsersExist() {
|
||||
owner := c.Input().Get("owner")
|
||||
var uuids []string
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &uuids)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
exist := object.CheckLdapUuidExist(owner, uuids)
|
||||
c.Data["json"] = &Response{Status: "ok", Data: exist}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -17,21 +17,16 @@ package controllers
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casbin/casdoor/object"
|
||||
)
|
||||
|
||||
type LinkForm struct {
|
||||
ProviderType string `json:"providerType"`
|
||||
User object.User `json:"user"`
|
||||
ProviderType string `json:"providerType"`
|
||||
}
|
||||
|
||||
// Unlink ...
|
||||
// @Tag Login API
|
||||
// @Title Unlink
|
||||
// @router /unlink [post]
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
func (c *ApiController) Unlink() {
|
||||
user, ok := c.RequireSignedInUser()
|
||||
userId, ok := c.RequireSignedIn()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
@@ -39,72 +34,20 @@ func (c *ApiController) Unlink() {
|
||||
var form LinkForm
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
panic(err)
|
||||
}
|
||||
providerType := form.ProviderType
|
||||
|
||||
// the user will be unlinked from the provider
|
||||
unlinkedUser := form.User
|
||||
|
||||
if user.Id != unlinkedUser.Id && !user.IsGlobalAdmin() {
|
||||
// if the user is not the same as the one we are unlinking, we need to make sure the user is the global admin.
|
||||
c.ResponseError(c.T("link:You are not the global admin, you can't unlink other users"))
|
||||
return
|
||||
}
|
||||
|
||||
if user.Id == unlinkedUser.Id && !user.IsGlobalAdmin() {
|
||||
// if the user is unlinking themselves, should check the provider can be unlinked, if not, we should return an error.
|
||||
application, err := object.GetApplicationByUser(user)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if application == nil {
|
||||
c.ResponseError(c.T("link:You can't unlink yourself, you are not a member of any application"))
|
||||
return
|
||||
}
|
||||
|
||||
if len(application.Providers) == 0 {
|
||||
c.ResponseError(c.T("link:This application has no providers"))
|
||||
return
|
||||
}
|
||||
|
||||
provider := application.GetProviderItemByType(providerType)
|
||||
if provider == nil {
|
||||
c.ResponseError(c.T("link:This application has no providers of type") + providerType)
|
||||
return
|
||||
}
|
||||
|
||||
if !provider.CanUnlink {
|
||||
c.ResponseError(c.T("link:This provider can't be unlinked"))
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// only two situations can happen here
|
||||
// 1. the user is the global admin
|
||||
// 2. the user is unlinking themselves and provider can be unlinked
|
||||
|
||||
value := object.GetUserField(&unlinkedUser, providerType)
|
||||
user := object.GetUser(userId)
|
||||
value := object.GetUserField(user, providerType)
|
||||
|
||||
if value == "" {
|
||||
c.ResponseError(c.T("link:Please link first"), value)
|
||||
c.ResponseError("Please link first", value)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = object.ClearUserOAuthProperties(&unlinkedUser, providerType)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
_, err = object.LinkUserAccount(&unlinkedUser, providerType, "")
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
object.ClearUserOAuthProperties(user, providerType)
|
||||
|
||||
object.LinkUserAccount(user, providerType, "")
|
||||
c.ResponseOk()
|
||||
}
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
// Copyright 2026 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
|
||||
"github.com/casdoor/casdoor/mcpself"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// ProxyServer
|
||||
// @Title ProxyServer
|
||||
// @Tag Server API
|
||||
// @Description proxy request to the upstream MCP server by Server URL
|
||||
// @Param owner path string true "The owner name of the server"
|
||||
// @Param name path string true "The name of the server"
|
||||
// @Success 200 {object} mcp.McpResponse The Response object
|
||||
// @router /server/:owner/:name [get,post]
|
||||
func (c *ApiController) ProxyServer() {
|
||||
owner := c.Ctx.Input.Param(":owner")
|
||||
name := c.Ctx.Input.Param(":name")
|
||||
|
||||
var mcpReq *mcpself.McpRequest
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &mcpReq)
|
||||
if err != nil {
|
||||
c.McpResponseError(1, -32700, "Parse error", err.Error())
|
||||
return
|
||||
}
|
||||
if util.IsStringsEmpty(owner, name) {
|
||||
c.McpResponseError(1, -32600, "invalid server identifier", nil)
|
||||
return
|
||||
}
|
||||
|
||||
server, err := object.GetServer(util.GetId(owner, name))
|
||||
if err != nil {
|
||||
c.McpResponseError(mcpReq.ID, -32600, "server not found", err.Error())
|
||||
return
|
||||
}
|
||||
if server == nil {
|
||||
c.McpResponseError(mcpReq.ID, -32600, "server not found", nil)
|
||||
return
|
||||
}
|
||||
if server.Url == "" {
|
||||
c.McpResponseError(mcpReq.ID, -32600, "server URL is empty", nil)
|
||||
return
|
||||
}
|
||||
|
||||
targetUrl, err := url.Parse(server.Url)
|
||||
if err != nil || !targetUrl.IsAbs() || targetUrl.Host == "" {
|
||||
c.McpResponseError(mcpReq.ID, -32600, "server URL is invalid", nil)
|
||||
return
|
||||
}
|
||||
if targetUrl.Scheme != "http" && targetUrl.Scheme != "https" {
|
||||
c.McpResponseError(mcpReq.ID, -32600, "server URL scheme is invalid", nil)
|
||||
return
|
||||
}
|
||||
|
||||
if mcpReq.Method == "tools/call" {
|
||||
var params mcpself.McpCallToolParams
|
||||
err = json.Unmarshal(mcpReq.Params, ¶ms)
|
||||
if err != nil {
|
||||
c.McpResponseError(mcpReq.ID, -32600, "Invalid request", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
for _, tool := range server.Tools {
|
||||
if tool.Name == params.Name && !tool.IsAllowed {
|
||||
c.McpResponseError(mcpReq.ID, -32600, "tool is forbidden", nil)
|
||||
return
|
||||
} else if tool.Name == params.Name {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proxy := httputil.NewSingleHostReverseProxy(targetUrl)
|
||||
proxy.ErrorHandler = func(writer http.ResponseWriter, request *http.Request, proxyErr error) {
|
||||
c.Ctx.Output.SetStatus(http.StatusBadGateway)
|
||||
c.McpResponseError(mcpReq.ID, -32603, "failed to proxy server request: %s", proxyErr.Error())
|
||||
}
|
||||
proxy.Director = func(request *http.Request) {
|
||||
request.URL.Scheme = targetUrl.Scheme
|
||||
request.URL.Host = targetUrl.Host
|
||||
request.Host = targetUrl.Host
|
||||
request.URL.Path = targetUrl.Path
|
||||
request.URL.RawPath = ""
|
||||
request.URL.RawQuery = targetUrl.RawQuery
|
||||
|
||||
if server.Token != "" {
|
||||
request.Header.Set("Authorization", "Bearer "+server.Token)
|
||||
}
|
||||
}
|
||||
|
||||
proxy.ServeHTTP(c.Ctx.ResponseWriter, c.Ctx.Request)
|
||||
}
|
||||
@@ -1,339 +0,0 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// MfaSetupInitiate
|
||||
// @Title MfaSetupInitiate
|
||||
// @Tag MFA API
|
||||
// @Description setup MFA
|
||||
// @param owner form string true "owner of user"
|
||||
// @param name form string true "name of user"
|
||||
// @param type form string true "MFA auth type"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /mfa/setup/initiate [post]
|
||||
func (c *ApiController) MfaSetupInitiate() {
|
||||
owner := c.Ctx.Request.Form.Get("owner")
|
||||
name := c.Ctx.Request.Form.Get("name")
|
||||
mfaType := c.Ctx.Request.Form.Get("mfaType")
|
||||
userId := util.GetId(owner, name)
|
||||
|
||||
if len(userId) == 0 {
|
||||
c.ResponseError(http.StatusText(http.StatusBadRequest))
|
||||
return
|
||||
}
|
||||
|
||||
MfaUtil := object.GetMfaUtil(mfaType, nil)
|
||||
if MfaUtil == nil {
|
||||
c.ResponseError("Invalid auth type")
|
||||
}
|
||||
|
||||
user, err := object.GetUser(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
c.ResponseError("User doesn't exist")
|
||||
return
|
||||
}
|
||||
|
||||
organization, err := object.GetOrganizationByUser(user)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
issuer := ""
|
||||
if organization != nil && organization.DisplayName != "" {
|
||||
issuer = organization.DisplayName
|
||||
} else if organization != nil {
|
||||
issuer = organization.Name
|
||||
}
|
||||
|
||||
mfaProps, err := MfaUtil.Initiate(user.GetId(), issuer)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
recoveryCode := util.GenerateUUID()
|
||||
mfaProps.RecoveryCodes = []string{recoveryCode}
|
||||
mfaProps.MfaRememberInHours = organization.MfaRememberInHours
|
||||
|
||||
resp := mfaProps
|
||||
c.ResponseOk(resp)
|
||||
}
|
||||
|
||||
// MfaSetupVerify
|
||||
// @Title MfaSetupVerify
|
||||
// @Tag MFA API
|
||||
// @Description setup verify totp
|
||||
// @param secret form string true "MFA secret"
|
||||
// @param passcode form string true "MFA passcode"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /mfa/setup/verify [post]
|
||||
func (c *ApiController) MfaSetupVerify() {
|
||||
mfaType := c.Ctx.Request.Form.Get("mfaType")
|
||||
passcode := c.Ctx.Request.Form.Get("passcode")
|
||||
secret := c.Ctx.Request.Form.Get("secret")
|
||||
dest := c.Ctx.Request.Form.Get("dest")
|
||||
countryCode := c.Ctx.Request.Form.Get("countryCode")
|
||||
|
||||
if mfaType == "" || passcode == "" {
|
||||
c.ResponseError("missing auth type or passcode")
|
||||
return
|
||||
}
|
||||
|
||||
config := &object.MfaProps{
|
||||
MfaType: mfaType,
|
||||
}
|
||||
if mfaType == object.TotpType {
|
||||
if secret == "" {
|
||||
c.ResponseError("totp secret is missing")
|
||||
return
|
||||
}
|
||||
config.Secret = secret
|
||||
} else if mfaType == object.SmsType {
|
||||
if dest == "" {
|
||||
c.ResponseError("destination is missing")
|
||||
return
|
||||
}
|
||||
config.Secret = dest
|
||||
if countryCode == "" {
|
||||
c.ResponseError("country code is missing")
|
||||
return
|
||||
}
|
||||
config.CountryCode = countryCode
|
||||
} else if mfaType == object.EmailType {
|
||||
if dest == "" {
|
||||
c.ResponseError("destination is missing")
|
||||
return
|
||||
}
|
||||
config.Secret = dest
|
||||
} else if mfaType == object.RadiusType {
|
||||
if dest == "" {
|
||||
c.ResponseError("RADIUS username is missing")
|
||||
return
|
||||
}
|
||||
config.Secret = dest
|
||||
if secret == "" {
|
||||
c.ResponseError("RADIUS provider is missing")
|
||||
return
|
||||
}
|
||||
config.URL = secret
|
||||
} else if mfaType == object.PushType {
|
||||
if dest == "" {
|
||||
c.ResponseError("push notification receiver is missing")
|
||||
return
|
||||
}
|
||||
config.Secret = dest
|
||||
if secret == "" {
|
||||
c.ResponseError("push notification provider is missing")
|
||||
return
|
||||
}
|
||||
config.URL = secret
|
||||
}
|
||||
|
||||
mfaUtil := object.GetMfaUtil(mfaType, config)
|
||||
if mfaUtil == nil {
|
||||
c.ResponseError("Invalid multi-factor authentication type")
|
||||
return
|
||||
}
|
||||
|
||||
err := mfaUtil.SetupVerify(passcode)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
} else {
|
||||
c.ResponseOk(http.StatusText(http.StatusOK))
|
||||
}
|
||||
}
|
||||
|
||||
// MfaSetupEnable
|
||||
// @Title MfaSetupEnable
|
||||
// @Tag MFA API
|
||||
// @Description enable totp
|
||||
// @param owner form string true "owner of user"
|
||||
// @param name form string true "name of user"
|
||||
// @param type form string true "MFA auth type"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /mfa/setup/enable [post]
|
||||
func (c *ApiController) MfaSetupEnable() {
|
||||
owner := c.Ctx.Request.Form.Get("owner")
|
||||
name := c.Ctx.Request.Form.Get("name")
|
||||
mfaType := c.Ctx.Request.Form.Get("mfaType")
|
||||
secret := c.Ctx.Request.Form.Get("secret")
|
||||
dest := c.Ctx.Request.Form.Get("dest")
|
||||
countryCode := c.Ctx.Request.Form.Get("secret")
|
||||
recoveryCodes := c.Ctx.Request.Form.Get("recoveryCodes")
|
||||
|
||||
user, err := object.GetUser(util.GetId(owner, name))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
c.ResponseError("User doesn't exist")
|
||||
return
|
||||
}
|
||||
|
||||
config := &object.MfaProps{
|
||||
MfaType: mfaType,
|
||||
}
|
||||
|
||||
if mfaType == object.TotpType {
|
||||
if secret == "" {
|
||||
c.ResponseError("totp secret is missing")
|
||||
return
|
||||
}
|
||||
config.Secret = secret
|
||||
} else if mfaType == object.EmailType {
|
||||
if user.Email == "" {
|
||||
if dest == "" {
|
||||
c.ResponseError("destination is missing")
|
||||
return
|
||||
}
|
||||
user.Email = dest
|
||||
}
|
||||
} else if mfaType == object.SmsType {
|
||||
if user.Phone == "" {
|
||||
if dest == "" {
|
||||
c.ResponseError("destination is missing")
|
||||
return
|
||||
}
|
||||
user.Phone = dest
|
||||
if countryCode == "" {
|
||||
c.ResponseError("country code is missing")
|
||||
return
|
||||
}
|
||||
user.CountryCode = countryCode
|
||||
}
|
||||
} else if mfaType == object.RadiusType {
|
||||
if dest == "" {
|
||||
c.ResponseError("RADIUS username is missing")
|
||||
return
|
||||
}
|
||||
config.Secret = dest
|
||||
if secret == "" {
|
||||
c.ResponseError("RADIUS provider is missing")
|
||||
return
|
||||
}
|
||||
config.URL = secret
|
||||
} else if mfaType == object.PushType {
|
||||
if dest == "" {
|
||||
c.ResponseError("push notification receiver is missing")
|
||||
return
|
||||
}
|
||||
config.Secret = dest
|
||||
if secret == "" {
|
||||
c.ResponseError("push notification provider is missing")
|
||||
return
|
||||
}
|
||||
config.URL = secret
|
||||
}
|
||||
|
||||
if recoveryCodes == "" {
|
||||
c.ResponseError("recovery codes is missing")
|
||||
return
|
||||
}
|
||||
config.RecoveryCodes = []string{recoveryCodes}
|
||||
|
||||
mfaUtil := object.GetMfaUtil(mfaType, config)
|
||||
if mfaUtil == nil {
|
||||
c.ResponseError("Invalid multi-factor authentication type")
|
||||
return
|
||||
}
|
||||
|
||||
err = mfaUtil.Enable(user)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(http.StatusText(http.StatusOK))
|
||||
}
|
||||
|
||||
// DeleteMfa
|
||||
// @Title DeleteMfa
|
||||
// @Tag MFA API
|
||||
// @Description: Delete MFA
|
||||
// @param owner form string true "owner of user"
|
||||
// @param name form string true "name of user"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-mfa/ [post]
|
||||
func (c *ApiController) DeleteMfa() {
|
||||
owner := c.Ctx.Request.Form.Get("owner")
|
||||
name := c.Ctx.Request.Form.Get("name")
|
||||
userId := util.GetId(owner, name)
|
||||
|
||||
user, err := object.GetUser(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if user == nil {
|
||||
c.ResponseError("User doesn't exist")
|
||||
return
|
||||
}
|
||||
|
||||
err = object.DisabledMultiFactorAuth(user)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetAllMfaProps(user, true))
|
||||
}
|
||||
|
||||
// SetPreferredMfa
|
||||
// @Title SetPreferredMfa
|
||||
// @Tag MFA API
|
||||
// @Description: Set specific Mfa Preferred
|
||||
// @param owner form string true "owner of user"
|
||||
// @param name form string true "name of user"
|
||||
// @param id form string true "id of user's MFA props"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /set-preferred-mfa [post]
|
||||
func (c *ApiController) SetPreferredMfa() {
|
||||
mfaType := c.Ctx.Request.Form.Get("mfaType")
|
||||
owner := c.Ctx.Request.Form.Get("owner")
|
||||
name := c.Ctx.Request.Form.Get("name")
|
||||
userId := util.GetId(owner, name)
|
||||
|
||||
user, err := object.GetUser(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if user == nil {
|
||||
c.ResponseError("User doesn't exist")
|
||||
return
|
||||
}
|
||||
|
||||
err = object.SetPreferredMultiFactorAuth(user, mfaType)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
c.ResponseOk(object.GetAllMfaProps(user, true))
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/beego/beego/v2/core/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetModels
|
||||
// @Title GetModels
|
||||
// @Tag Model API
|
||||
// @Description get models
|
||||
// @Param owner query string true "The owner of models"
|
||||
// @Success 200 {array} object.Model The Response object
|
||||
// @router /get-models [get]
|
||||
func (c *ApiController) GetModels() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
models, err := object.GetModels(owner)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(models)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetModelCount(owner, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
models, err := object.GetPaginationModels(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(models, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetModel
|
||||
// @Title GetModel
|
||||
// @Tag Model API
|
||||
// @Description get model
|
||||
// @Param id query string true "The id ( owner/name ) of the model"
|
||||
// @Success 200 {object} object.Model The Response object
|
||||
// @router /get-model [get]
|
||||
func (c *ApiController) GetModel() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
model, err := object.GetModel(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(model)
|
||||
}
|
||||
|
||||
// UpdateModel
|
||||
// @Title UpdateModel
|
||||
// @Tag Model API
|
||||
// @Description update model
|
||||
// @Param id query string true "The id ( owner/name ) of the model"
|
||||
// @Param body body object.Model true "The details of the model"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-model [post]
|
||||
func (c *ApiController) UpdateModel() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
var model object.Model
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &model)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapErrorResponse(object.UpdateModelWithCheck(id, &model))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddModel
|
||||
// @Title AddModel
|
||||
// @Tag Model API
|
||||
// @Description add model
|
||||
// @Param body body object.Model true "The details of the model"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-model [post]
|
||||
func (c *ApiController) AddModel() {
|
||||
var model object.Model
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &model)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddModel(&model))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteModel
|
||||
// @Title DeleteModel
|
||||
// @Tag Model API
|
||||
// @Description delete model
|
||||
// @Param body body object.Model true "The details of the model"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-model [post]
|
||||
func (c *ApiController) DeleteModel() {
|
||||
var model object.Model
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &model)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteModel(&model))
|
||||
c.ServeJSON()
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
// Copyright 2026 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
)
|
||||
|
||||
// DynamicClientRegister
|
||||
// @Title DynamicClientRegister
|
||||
// @Tag OAuth API
|
||||
// @Description Register a new OAuth 2.0 client dynamically (RFC 7591)
|
||||
// @Param organization query string false "The organization name (defaults to built-in)"
|
||||
// @Param body body object.DynamicClientRegistrationRequest true "Client registration request"
|
||||
// @Success 201 {object} object.DynamicClientRegistrationResponse
|
||||
// @Failure 400 {object} object.DcrError
|
||||
// @router /api/oauth/register [post]
|
||||
func (c *ApiController) DynamicClientRegister() {
|
||||
var req object.DynamicClientRegistrationRequest
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
|
||||
if err != nil {
|
||||
c.Ctx.Output.Status = http.StatusBadRequest
|
||||
c.Data["json"] = object.DcrError{
|
||||
Error: "invalid_client_metadata",
|
||||
ErrorDescription: "invalid request body: " + err.Error(),
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
// Get organization from query parameter or default to built-in
|
||||
organization := c.Ctx.Input.Query("organization")
|
||||
if organization == "" {
|
||||
organization = "built-in"
|
||||
}
|
||||
|
||||
// Register the client
|
||||
response, dcrErr, err := object.RegisterDynamicClient(&req, organization)
|
||||
if err != nil {
|
||||
c.Ctx.Output.Status = http.StatusInternalServerError
|
||||
c.Data["json"] = object.DcrError{
|
||||
Error: "server_error",
|
||||
ErrorDescription: err.Error(),
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
if dcrErr != nil {
|
||||
c.Ctx.Output.Status = http.StatusBadRequest
|
||||
c.Data["json"] = dcrErr
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
// Return 201 Created
|
||||
c.Ctx.Output.Status = http.StatusCreated
|
||||
c.Data["json"] = response
|
||||
c.ServeJSON()
|
||||
}
|
||||
@@ -1,195 +0,0 @@
|
||||
// Copyright 2025 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/beego/beego/v2/core/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetOrders
|
||||
// @Title GetOrders
|
||||
// @Tag Order API
|
||||
// @Description get orders
|
||||
// @Param owner query string true "The owner of orders"
|
||||
// @Success 200 {array} object.Order The Response object
|
||||
// @router /get-orders [get]
|
||||
func (c *ApiController) GetOrders() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
var orders []*object.Order
|
||||
var err error
|
||||
|
||||
if c.IsAdmin() {
|
||||
// If field is "user", filter by that user even for admins
|
||||
if field == "user" && value != "" {
|
||||
orders, err = object.GetUserOrders(owner, value)
|
||||
} else {
|
||||
orders, err = object.GetOrders(owner)
|
||||
}
|
||||
} else {
|
||||
user := c.GetSessionUsername()
|
||||
_, userName, userErr := util.GetOwnerAndNameFromIdWithError(user)
|
||||
if userErr != nil {
|
||||
c.ResponseError(userErr.Error())
|
||||
return
|
||||
}
|
||||
orders, err = object.GetUserOrders(owner, userName)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(orders)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
if !c.IsAdmin() {
|
||||
user := c.GetSessionUsername()
|
||||
_, userName, userErr := util.GetOwnerAndNameFromIdWithError(user)
|
||||
if userErr != nil {
|
||||
c.ResponseError(userErr.Error())
|
||||
return
|
||||
}
|
||||
field = "user"
|
||||
value = userName
|
||||
}
|
||||
count, err := object.GetOrderCount(owner, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
orders, err := object.GetPaginationOrders(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(orders, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetUserOrders
|
||||
// @Title GetUserOrders
|
||||
// @Tag Order API
|
||||
// @Description get orders for a user
|
||||
// @Param owner query string true "The owner of orders"
|
||||
// @Param user query string true "The username of the user"
|
||||
// @Success 200 {array} object.Order The Response object
|
||||
// @router /get-user-orders [get]
|
||||
func (c *ApiController) GetUserOrders() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
user := c.Ctx.Input.Query("user")
|
||||
|
||||
orders, err := object.GetUserOrders(owner, user)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(orders)
|
||||
}
|
||||
|
||||
// GetOrder
|
||||
// @Title GetOrder
|
||||
// @Tag Order API
|
||||
// @Description get order
|
||||
// @Param id query string true "The id ( owner/name ) of the order"
|
||||
// @Success 200 {object} object.Order The Response object
|
||||
// @router /get-order [get]
|
||||
func (c *ApiController) GetOrder() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
order, err := object.GetOrder(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(order)
|
||||
}
|
||||
|
||||
// UpdateOrder
|
||||
// @Title UpdateOrder
|
||||
// @Tag Order API
|
||||
// @Description update order
|
||||
// @Param id query string true "The id ( owner/name ) of the order"
|
||||
// @Param body body object.Order true "The details of the order"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-order [post]
|
||||
func (c *ApiController) UpdateOrder() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
var order object.Order
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &order)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateOrder(id, &order))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddOrder
|
||||
// @Title AddOrder
|
||||
// @Tag Order API
|
||||
// @Description add order
|
||||
// @Param body body object.Order true "The details of the order"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-order [post]
|
||||
func (c *ApiController) AddOrder() {
|
||||
var order object.Order
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &order)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddOrder(&order))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteOrder
|
||||
// @Title DeleteOrder
|
||||
// @Tag Order API
|
||||
// @Description delete order
|
||||
// @Param body body object.Order true "The details of the order"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-order [post]
|
||||
func (c *ApiController) DeleteOrder() {
|
||||
var order object.Order
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &order)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteOrder(&order))
|
||||
c.ServeJSON()
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
// Copyright 2025 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// PlaceOrder
|
||||
// @Title PlaceOrder
|
||||
// @Tag Order API
|
||||
// @Description place an order for a product
|
||||
// @Param productId query string true "The id ( owner/name ) of the product"
|
||||
// @Param pricingName query string false "The name of the pricing (for subscription)"
|
||||
// @Param planName query string false "The name of the plan (for subscription)"
|
||||
// @Param customPrice query number false "Custom price for recharge products"
|
||||
// @Param userName query string false "The username to place order for (admin only)"
|
||||
// @Success 200 {object} object.Order The Response object
|
||||
// @router /place-order [post]
|
||||
func (c *ApiController) PlaceOrder() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
paidUserName := c.Ctx.Input.Query("userName")
|
||||
|
||||
var req struct {
|
||||
ProductInfos []object.ProductInfo `json:"productInfos"`
|
||||
}
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
productInfos := req.ProductInfos
|
||||
if len(productInfos) == 0 {
|
||||
c.ResponseError(c.T("product:Product list cannot be empty"))
|
||||
return
|
||||
}
|
||||
|
||||
var userId string
|
||||
if paidUserName != "" {
|
||||
userId = util.GetId(owner, paidUserName)
|
||||
if userId != c.GetSessionUsername() && !c.IsAdmin() && userId != c.GetPaidUsername() {
|
||||
c.ResponseError(c.T("general:Only admin user can specify user"))
|
||||
return
|
||||
}
|
||||
|
||||
c.SetSession("paidUsername", "")
|
||||
} else {
|
||||
userId = c.GetSessionUsername()
|
||||
}
|
||||
|
||||
if userId == "" {
|
||||
c.ResponseError(c.T("general:Please login first"))
|
||||
return
|
||||
}
|
||||
|
||||
user, err := object.GetUser(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if user == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), userId))
|
||||
return
|
||||
}
|
||||
|
||||
order, err := object.PlaceOrder(owner, productInfos, user)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(order)
|
||||
}
|
||||
|
||||
// PayOrder
|
||||
// @Title PayOrder
|
||||
// @Tag Order API
|
||||
// @Description pay an existing order
|
||||
// @Param id query string true "The id ( owner/name ) of the order"
|
||||
// @Param providerName query string true "The name of the provider"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /pay-order [post]
|
||||
func (c *ApiController) PayOrder() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
host := c.Ctx.Request.Host
|
||||
providerName := c.Ctx.Input.Query("providerName")
|
||||
paymentEnv := c.Ctx.Input.Query("paymentEnv")
|
||||
|
||||
order, err := object.GetOrder(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if order == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The order: %s does not exist"), id))
|
||||
return
|
||||
}
|
||||
|
||||
userId := c.GetSessionUsername()
|
||||
orderUserId := util.GetId(order.Owner, order.User)
|
||||
if userId != orderUserId && !c.IsAdmin() {
|
||||
c.ResponseError(c.T("auth:Unauthorized operation"))
|
||||
return
|
||||
}
|
||||
|
||||
payment, attachInfo, err := object.PayOrder(providerName, host, paymentEnv, order, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(payment, attachInfo)
|
||||
}
|
||||
|
||||
// CancelOrder
|
||||
// @Title CancelOrder
|
||||
// @Tag Order API
|
||||
// @Description cancel an order
|
||||
// @Param id query string true "The id ( owner/name ) of the order"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /cancel-order [post]
|
||||
func (c *ApiController) CancelOrder() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
order, err := object.GetOrder(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if order == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The order: %s does not exist"), id))
|
||||
return
|
||||
}
|
||||
|
||||
userId := c.GetSessionUsername()
|
||||
orderUserId := util.GetId(order.Owner, order.User)
|
||||
if userId != orderUserId && !c.IsAdmin() {
|
||||
c.ResponseError(c.T("auth:Unauthorized operation"))
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.CancelOrder(order))
|
||||
c.ServeJSON()
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -17,130 +17,57 @@ package controllers
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/beego/beego/v2/core/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/casbin/casdoor/object"
|
||||
)
|
||||
|
||||
// GetOrganizations ...
|
||||
// @Title GetOrganizations
|
||||
// @Tag Organization API
|
||||
// @Description get organizations
|
||||
// @Param owner query string true "owner"
|
||||
// @Success 200 {array} object.Organization The Response object
|
||||
// @router /get-organizations [get]
|
||||
func (c *ApiController) GetOrganizations() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
organizationName := c.Ctx.Input.Query("organizationName")
|
||||
owner := c.Input().Get("owner")
|
||||
|
||||
isGlobalAdmin := c.IsGlobalAdmin()
|
||||
if limit == "" || page == "" {
|
||||
var organizations []*object.Organization
|
||||
var err error
|
||||
if isGlobalAdmin {
|
||||
organizations, err = object.GetMaskedOrganizations(object.GetOrganizations(owner))
|
||||
} else {
|
||||
organizations, err = object.GetMaskedOrganizations(object.GetOrganizations(owner, c.getCurrentUser().Owner))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(organizations)
|
||||
} else {
|
||||
if !isGlobalAdmin {
|
||||
organizations, err := object.GetMaskedOrganizations(object.GetOrganizations(owner, c.getCurrentUser().Owner))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
c.ResponseOk(organizations)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetOrganizationCount(owner, organizationName, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
organizations, err := object.GetMaskedOrganizations(object.GetPaginationOrganizations(owner, organizationName, paginator.Offset(), limit, field, value, sortField, sortOrder))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(organizations, paginator.Nums())
|
||||
}
|
||||
}
|
||||
c.Data["json"] = object.GetOrganizations(owner)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetOrganization ...
|
||||
// @Title GetOrganization
|
||||
// @Tag Organization API
|
||||
// @Description get organization
|
||||
// @Param id query string true "organization id"
|
||||
// @Success 200 {object} object.Organization The Response object
|
||||
// @router /get-organization [get]
|
||||
func (c *ApiController) GetOrganization() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
organization, err := object.GetMaskedOrganization(object.GetOrganization(id))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
id := c.Input().Get("id")
|
||||
|
||||
if organization != nil && organization.MfaRememberInHours == 0 {
|
||||
organization.MfaRememberInHours = 12
|
||||
}
|
||||
|
||||
c.ResponseOk(organization)
|
||||
c.Data["json"] = object.GetOrganization(id)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// UpdateOrganization ...
|
||||
// @Title UpdateOrganization
|
||||
// @Tag Organization API
|
||||
// @Description update organization
|
||||
// @Param id query string true "The id ( owner/name ) of the organization"
|
||||
// @Param id query string true "The id of the organization"
|
||||
// @Param body body object.Organization true "The details of the organization"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-organization [post]
|
||||
func (c *ApiController) UpdateOrganization() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
id := c.Input().Get("id")
|
||||
|
||||
var organization object.Organization
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &organization)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err = object.CheckIpWhitelist(organization.IpWhitelist, c.GetAcceptLanguage()); err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
isGlobalAdmin, _ := c.isGlobalAdmin()
|
||||
|
||||
if organization.BalanceCurrency == "" {
|
||||
organization.BalanceCurrency = "USD"
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateOrganization(id, &organization, isGlobalAdmin))
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateOrganization(id, &organization))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddOrganization ...
|
||||
// @Title AddOrganization
|
||||
// @Tag Organization API
|
||||
// @Description add organization
|
||||
// @Param body body object.Organization true "The details of the organization"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
@@ -149,28 +76,7 @@ func (c *ApiController) AddOrganization() {
|
||||
var organization object.Organization
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &organization)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
count, err := object.GetOrganizationCount("", "", "", "")
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err = checkQuotaForOrganization(int(count)); err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err = object.CheckIpWhitelist(organization.IpWhitelist, c.GetAcceptLanguage()); err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if organization.BalanceCurrency == "" {
|
||||
organization.BalanceCurrency = "USD"
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddOrganization(&organization))
|
||||
@@ -179,7 +85,6 @@ func (c *ApiController) AddOrganization() {
|
||||
|
||||
// DeleteOrganization ...
|
||||
// @Title DeleteOrganization
|
||||
// @Tag Organization API
|
||||
// @Description delete organization
|
||||
// @Param body body object.Organization true "The details of the organization"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
@@ -188,49 +93,9 @@ func (c *ApiController) DeleteOrganization() {
|
||||
var organization object.Organization
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &organization)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteOrganization(&organization))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetDefaultApplication ...
|
||||
// @Title GetDefaultApplication
|
||||
// @Tag Organization API
|
||||
// @Description get default application
|
||||
// @Param id query string true "organization id"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /get-default-application [get]
|
||||
func (c *ApiController) GetDefaultApplication() {
|
||||
userId := c.GetSessionUsername()
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
application, err := object.GetDefaultApplication(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
application = object.GetMaskedApplication(application, userId)
|
||||
c.ResponseOk(application)
|
||||
}
|
||||
|
||||
// GetOrganizationNames ...
|
||||
// @Title GetOrganizationNames
|
||||
// @Tag Organization API
|
||||
// @Param owner query string true "owner"
|
||||
// @Description get all organization name and displayName
|
||||
// @Success 200 {array} object.Organization The Response object
|
||||
// @router /get-organization-names [get]
|
||||
func (c *ApiController) GetOrganizationNames() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
organizationNames, err := object.GetOrganizationsByFields(owner, []string{"name", "display_name"}...)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(organizationNames)
|
||||
}
|
||||
|
||||
@@ -1,241 +1,45 @@
|
||||
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/beego/beego/v2/core/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/payment"
|
||||
)
|
||||
|
||||
// GetPayments
|
||||
// @Title GetPayments
|
||||
// @Tag Payment API
|
||||
// @Description get payments
|
||||
// @Param owner query string true "The owner of payments"
|
||||
// @Success 200 {array} object.Payment The Response object
|
||||
// @router /get-payments [get]
|
||||
func (c *ApiController) PaypalPay() {
|
||||
clientId := c.Input().Get("clientId")
|
||||
redirectUri := c.Input().Get("redirectUri")
|
||||
var payItem object.PayItem
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &payItem)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
msg := payment.Paypal(payItem, clientId, redirectUri)
|
||||
|
||||
c.Data["json"] = msg
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) GetPayments() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
var payments []*object.Payment
|
||||
var err error
|
||||
|
||||
if c.IsAdmin() {
|
||||
// If field is "user", filter by that user even for admins
|
||||
if field == "user" && value != "" {
|
||||
payments, err = object.GetUserPayments(owner, value)
|
||||
} else {
|
||||
payments, err = object.GetPayments(owner)
|
||||
}
|
||||
} else {
|
||||
user := c.GetSessionUsername()
|
||||
_, userName, userErr := util.GetOwnerAndNameFromIdWithError(user)
|
||||
if userErr != nil {
|
||||
c.ResponseError(userErr.Error())
|
||||
return
|
||||
}
|
||||
payments, err = object.GetUserPayments(owner, userName)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(payments)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
if !c.IsAdmin() {
|
||||
user := c.GetSessionUsername()
|
||||
_, userName, userErr := util.GetOwnerAndNameFromIdWithError(user)
|
||||
if userErr != nil {
|
||||
c.ResponseError(userErr.Error())
|
||||
return
|
||||
}
|
||||
field = "user"
|
||||
value = userName
|
||||
}
|
||||
count, err := object.GetPaymentCount(owner, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
payments, err := object.GetPaginationPayments(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(payments, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetUserPayments
|
||||
// @Title GetUserPayments
|
||||
// @Tag Payment API
|
||||
// @Description get payments for a user
|
||||
// @Param owner query string true "The owner of payments"
|
||||
// @Param organization query string true "The organization of the user"
|
||||
// @Param user query string true "The username of the user"
|
||||
// @Success 200 {array} object.Payment The Response object
|
||||
// @router /get-user-payments [get]
|
||||
func (c *ApiController) GetUserPayments() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
user := c.Ctx.Input.Query("user")
|
||||
|
||||
payments, err := object.GetUserPayments(owner, user)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(payments)
|
||||
}
|
||||
|
||||
// GetPayment
|
||||
// @Title GetPayment
|
||||
// @Tag Payment API
|
||||
// @Description get payment
|
||||
// @Param id query string true "The id ( owner/name ) of the payment"
|
||||
// @Success 200 {object} object.Payment The Response object
|
||||
// @router /get-payment [get]
|
||||
func (c *ApiController) GetPayment() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
payment, err := object.GetPayment(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(payment)
|
||||
}
|
||||
|
||||
// UpdatePayment
|
||||
// @Title UpdatePayment
|
||||
// @Tag Payment API
|
||||
// @Description update payment
|
||||
// @Param id query string true "The id ( owner/name ) of the payment"
|
||||
// @Param body body object.Payment true "The details of the payment"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-payment [post]
|
||||
func (c *ApiController) UpdatePayment() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
var payment object.Payment
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &payment)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdatePayment(id, &payment))
|
||||
c.Data["json"] = object.GetPayments()
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddPayment
|
||||
// @Title AddPayment
|
||||
// @Tag Payment API
|
||||
// @Description add payment
|
||||
// @Param body body object.Payment true "The details of the payment"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-payment [post]
|
||||
func (c *ApiController) AddPayment() {
|
||||
var payment object.Payment
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &payment)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddPayment(&payment))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeletePayment
|
||||
// @Title DeletePayment
|
||||
// @Tag Payment API
|
||||
// @Description delete payment
|
||||
// @Param body body object.Payment true "The details of the payment"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-payment [post]
|
||||
func (c *ApiController) DeletePayment() {
|
||||
var payment object.Payment
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &payment)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeletePayment(&payment))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// NotifyPayment
|
||||
// @Title NotifyPayment
|
||||
// @Tag Payment API
|
||||
// @Description notify payment
|
||||
// @Param body body object.Payment true "The details of the payment"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /notify-payment [post]
|
||||
func (c *ApiController) NotifyPayment() {
|
||||
owner := c.Ctx.Input.Param(":owner")
|
||||
paymentName := c.Ctx.Input.Param(":payment")
|
||||
|
||||
body := c.Ctx.Input.RequestBody
|
||||
|
||||
payment, err := object.NotifyPayment(body, owner, paymentName, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(payment)
|
||||
}
|
||||
|
||||
// InvoicePayment
|
||||
// @Title InvoicePayment
|
||||
// @Tag Payment API
|
||||
// @Description invoice payment
|
||||
// @Param id query string true "The id ( owner/name ) of the payment"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /invoice-payment [post]
|
||||
func (c *ApiController) InvoicePayment() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
payment, err := object.GetPayment(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
invoiceUrl, err := object.InvoicePayment(payment)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
}
|
||||
c.ResponseOk(invoiceUrl)
|
||||
func (c *ApiController) SuccessPay() {
|
||||
token := c.Input().Get("paymentId")
|
||||
c.Data["json"] = payment.SuccessPay(token)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/beego/beego/v2/core/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetPermissions
|
||||
// @Title GetPermissions
|
||||
// @Tag Permission API
|
||||
// @Description get permissions
|
||||
// @Param owner query string true "The owner of permissions"
|
||||
// @Success 200 {array} object.Permission The Response object
|
||||
// @router /get-permissions [get]
|
||||
func (c *ApiController) GetPermissions() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
permissions, err := object.GetPermissions(owner)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(permissions)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetPermissionCount(owner, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
permissions, err := object.GetPaginationPermissions(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(permissions, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetPermissionsBySubmitter
|
||||
// @Title GetPermissionsBySubmitter
|
||||
// @Tag Permission API
|
||||
// @Description get permissions by submitter
|
||||
// @Success 200 {array} object.Permission The Response object
|
||||
// @router /get-permissions-by-submitter [get]
|
||||
func (c *ApiController) GetPermissionsBySubmitter() {
|
||||
user, ok := c.RequireSignedInUser()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
permissions, err := object.GetPermissionsBySubmitter(user.Owner, user.Name)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(permissions, len(permissions))
|
||||
}
|
||||
|
||||
// GetPermissionsByRole
|
||||
// @Title GetPermissionsByRole
|
||||
// @Tag Permission API
|
||||
// @Description get permissions by role
|
||||
// @Param id query string true "The id ( owner/name ) of the role"
|
||||
// @Success 200 {array} object.Permission The Response object
|
||||
// @router /get-permissions-by-role [get]
|
||||
func (c *ApiController) GetPermissionsByRole() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
permissions, err := object.GetPermissionsByRole(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(permissions, len(permissions))
|
||||
}
|
||||
|
||||
// GetPermission
|
||||
// @Title GetPermission
|
||||
// @Tag Permission API
|
||||
// @Description get permission
|
||||
// @Param id query string true "The id ( owner/name ) of the permission"
|
||||
// @Success 200 {object} object.Permission The Response object
|
||||
// @router /get-permission [get]
|
||||
func (c *ApiController) GetPermission() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
permission, err := object.GetPermission(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(permission)
|
||||
}
|
||||
|
||||
// UpdatePermission
|
||||
// @Title UpdatePermission
|
||||
// @Tag Permission API
|
||||
// @Description update permission
|
||||
// @Param id query string true "The id ( owner/name ) of the permission"
|
||||
// @Param body body object.Permission true "The details of the permission"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-permission [post]
|
||||
func (c *ApiController) UpdatePermission() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
var permission object.Permission
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &permission)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdatePermission(id, &permission))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddPermission
|
||||
// @Title AddPermission
|
||||
// @Tag Permission API
|
||||
// @Description add permission
|
||||
// @Param body body object.Permission true "The details of the permission"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-permission [post]
|
||||
func (c *ApiController) AddPermission() {
|
||||
var permission object.Permission
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &permission)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddPermission(&permission))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeletePermission
|
||||
// @Title DeletePermission
|
||||
// @Tag Permission API
|
||||
// @Description delete permission
|
||||
// @Param body body object.Permission true "The details of the permission"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-permission [post]
|
||||
func (c *ApiController) DeletePermission() {
|
||||
var permission object.Permission
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &permission)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeletePermission(&permission))
|
||||
c.ServeJSON()
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func (c *ApiController) UploadPermissions() {
|
||||
userId := c.GetSessionUsername()
|
||||
owner, user, err := util.GetOwnerAndNameFromIdWithError(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
file, header, err := c.Ctx.Request.FormFile("file")
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fileId := fmt.Sprintf("%s_%s_%s", owner, user, util.RemoveExt(header.Filename))
|
||||
path := util.GetUploadXlsxPath(fileId)
|
||||
defer os.Remove(path)
|
||||
err = saveFile(path, &file)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
affected, err := object.UploadPermissions(owner, path)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
}
|
||||
|
||||
if affected {
|
||||
c.ResponseOk()
|
||||
} else {
|
||||
c.ResponseError(c.T("general:Failed to import users"))
|
||||
}
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/beego/beego/v2/core/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetPlans
|
||||
// @Title GetPlans
|
||||
// @Tag Plan API
|
||||
// @Description get plans
|
||||
// @Param owner query string true "The owner of plans"
|
||||
// @Success 200 {array} object.Plan The Response object
|
||||
// @router /get-plans [get]
|
||||
func (c *ApiController) GetPlans() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
plans, err := object.GetPlans(owner)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(plans)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetPlanCount(owner, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
plan, err := object.GetPaginatedPlans(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(plan, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetPlan
|
||||
// @Title GetPlan
|
||||
// @Tag Plan API
|
||||
// @Description get plan
|
||||
// @Param id query string true "The id ( owner/name ) of the plan"
|
||||
// @Param includeOption query bool false "Should include plan's option"
|
||||
// @Success 200 {object} object.Plan The Response object
|
||||
// @router /get-plan [get]
|
||||
func (c *ApiController) GetPlan() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
includeOption := c.Ctx.Input.Query("includeOption") == "true"
|
||||
|
||||
plan, err := object.GetPlan(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if plan != nil && includeOption {
|
||||
options, err := object.GetPermissionsByRole(plan.Role)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
plan.Options = append(plan.Options, option.DisplayName)
|
||||
}
|
||||
}
|
||||
|
||||
c.ResponseOk(plan)
|
||||
}
|
||||
|
||||
// UpdatePlan
|
||||
// @Title UpdatePlan
|
||||
// @Tag Plan API
|
||||
// @Description update plan
|
||||
// @Param id query string true "The id ( owner/name ) of the plan"
|
||||
// @Param body body object.Plan true "The details of the plan"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-plan [post]
|
||||
func (c *ApiController) UpdatePlan() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
owner := util.GetOwnerFromId(id)
|
||||
var plan object.Plan
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &plan)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if plan.Product != "" {
|
||||
productId := util.GetId(owner, plan.Product)
|
||||
product, err := object.GetProduct(productId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if product != nil {
|
||||
object.UpdateProductForPlan(&plan, product)
|
||||
_, err = object.UpdateProduct(productId, product)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
c.Data["json"] = wrapActionResponse(object.UpdatePlan(id, &plan))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddPlan
|
||||
// @Title AddPlan
|
||||
// @Tag Plan API
|
||||
// @Description add plan
|
||||
// @Param body body object.Plan true "The details of the plan"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-plan [post]
|
||||
func (c *ApiController) AddPlan() {
|
||||
var plan object.Plan
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &plan)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
// Create a related product for plan
|
||||
product := object.CreateProductForPlan(&plan)
|
||||
_, err = object.AddProduct(product)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
plan.Product = product.Name
|
||||
c.Data["json"] = wrapActionResponse(object.AddPlan(&plan))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeletePlan
|
||||
// @Title DeletePlan
|
||||
// @Tag Plan API
|
||||
// @Description delete plan
|
||||
// @Param body body object.Plan true "The details of the plan"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-plan [post]
|
||||
func (c *ApiController) DeletePlan() {
|
||||
var plan object.Plan
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &plan)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if plan.Product != "" {
|
||||
_, err = object.DeleteProduct(&object.Product{Owner: plan.Owner, Name: plan.Product})
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
c.Data["json"] = wrapActionResponse(object.DeletePlan(&plan))
|
||||
c.ServeJSON()
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/beego/beego/v2/core/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetPricings
|
||||
// @Title GetPricings
|
||||
// @Tag Pricing API
|
||||
// @Description get pricings
|
||||
// @Param owner query string true "The owner of pricings"
|
||||
// @Success 200 {array} object.Pricing The Response object
|
||||
// @router /get-pricings [get]
|
||||
func (c *ApiController) GetPricings() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
pricings, err := object.GetPricings(owner)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(pricings)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetPricingCount(owner, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
pricing, err := object.GetPaginatedPricings(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(pricing, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetPricing
|
||||
// @Title GetPricing
|
||||
// @Tag Pricing API
|
||||
// @Description get pricing
|
||||
// @Param id query string true "The id ( owner/name ) of the pricing"
|
||||
// @Success 200 {object} object.Pricing The Response object
|
||||
// @router /get-pricing [get]
|
||||
func (c *ApiController) GetPricing() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
pricing, err := object.GetPricing(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(pricing)
|
||||
}
|
||||
|
||||
// UpdatePricing
|
||||
// @Title UpdatePricing
|
||||
// @Tag Pricing API
|
||||
// @Description update pricing
|
||||
// @Param id query string true "The id ( owner/name ) of the pricing"
|
||||
// @Param body body object.Pricing true "The details of the pricing"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-pricing [post]
|
||||
func (c *ApiController) UpdatePricing() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
var pricing object.Pricing
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &pricing)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdatePricing(id, &pricing))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddPricing
|
||||
// @Title AddPricing
|
||||
// @Tag Pricing API
|
||||
// @Description add pricing
|
||||
// @Param body body object.Pricing true "The details of the pricing"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-pricing [post]
|
||||
func (c *ApiController) AddPricing() {
|
||||
var pricing object.Pricing
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &pricing)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddPricing(&pricing))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeletePricing
|
||||
// @Title DeletePricing
|
||||
// @Tag Pricing API
|
||||
// @Description delete pricing
|
||||
// @Param body body object.Pricing true "The details of the pricing"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-pricing [post]
|
||||
func (c *ApiController) DeletePricing() {
|
||||
var pricing object.Pricing
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &pricing)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeletePricing(&pricing))
|
||||
c.ServeJSON()
|
||||
}
|
||||
@@ -1,228 +0,0 @@
|
||||
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/beego/beego/v2/core/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetProducts
|
||||
// @Title GetProducts
|
||||
// @Tag Product API
|
||||
// @Description get products
|
||||
// @Param owner query string true "The owner of products"
|
||||
// @Success 200 {array} object.Product The Response object
|
||||
// @router /get-products [get]
|
||||
func (c *ApiController) GetProducts() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
products, err := object.GetProducts(owner)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(products)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetProductCount(owner, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
products, err := object.GetPaginationProducts(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(products, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetProduct
|
||||
// @Title GetProduct
|
||||
// @Tag Product API
|
||||
// @Description get product
|
||||
// @Param id query string true "The id ( owner/name ) of the product"
|
||||
// @Success 200 {object} object.Product The Response object
|
||||
// @router /get-product [get]
|
||||
func (c *ApiController) GetProduct() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
product, err := object.GetProduct(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = object.ExtendProductWithProviders(product)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(product)
|
||||
}
|
||||
|
||||
// UpdateProduct
|
||||
// @Title UpdateProduct
|
||||
// @Tag Product API
|
||||
// @Description update product
|
||||
// @Param id query string true "The id ( owner/name ) of the product"
|
||||
// @Param body body object.Product true "The details of the product"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-product [post]
|
||||
func (c *ApiController) UpdateProduct() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
var product object.Product
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &product)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateProduct(id, &product))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddProduct
|
||||
// @Title AddProduct
|
||||
// @Tag Product API
|
||||
// @Description add product
|
||||
// @Param body body object.Product true "The details of the product"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-product [post]
|
||||
func (c *ApiController) AddProduct() {
|
||||
var product object.Product
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &product)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddProduct(&product))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteProduct
|
||||
// @Title DeleteProduct
|
||||
// @Tag Product API
|
||||
// @Description delete product
|
||||
// @Param body body object.Product true "The details of the product"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-product [post]
|
||||
func (c *ApiController) DeleteProduct() {
|
||||
var product object.Product
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &product)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteProduct(&product))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// BuyProduct
|
||||
// @Title BuyProduct (Deprecated)
|
||||
// @Tag Product API
|
||||
// @Description buy product using the deprecated compatibility endpoint, prefer place-order plus pay-order for new integrations
|
||||
// @Param id query string true "The id ( owner/name ) of the product"
|
||||
// @Param providerName query string true "The name of the provider"
|
||||
// @Param pricingName query string false "The name of the pricing (for subscription)"
|
||||
// @Param planName query string false "The name of the plan (for subscription)"
|
||||
// @Param userName query string false "The username to buy product for (admin only)"
|
||||
// @Param paymentEnv query string false "The payment environment"
|
||||
// @Param customPrice query number false "Custom price for recharge products"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /buy-product [post]
|
||||
func (c *ApiController) BuyProduct() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
host := c.Ctx.Request.Host
|
||||
providerName := c.Ctx.Input.Query("providerName")
|
||||
paymentEnv := c.Ctx.Input.Query("paymentEnv")
|
||||
customPriceStr := c.Ctx.Input.Query("customPrice")
|
||||
if customPriceStr == "" {
|
||||
customPriceStr = "0"
|
||||
}
|
||||
|
||||
customPrice, err := strconv.ParseFloat(customPriceStr, 64)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
pricingName := c.Ctx.Input.Query("pricingName")
|
||||
planName := c.Ctx.Input.Query("planName")
|
||||
paidUserName := c.Ctx.Input.Query("userName")
|
||||
|
||||
owner, _, err := util.GetOwnerAndNameFromIdWithError(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var userId string
|
||||
if paidUserName != "" {
|
||||
userId = util.GetId(owner, paidUserName)
|
||||
if userId != c.GetSessionUsername() && !c.IsAdmin() && userId != c.GetPaidUsername() {
|
||||
c.ResponseError(c.T("general:Only admin user can specify user"))
|
||||
return
|
||||
}
|
||||
|
||||
c.SetSession("paidUsername", "")
|
||||
} else {
|
||||
userId = c.GetSessionUsername()
|
||||
}
|
||||
if userId == "" {
|
||||
c.ResponseError(c.T("general:Please login first"))
|
||||
return
|
||||
}
|
||||
|
||||
user, err := object.GetUser(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if user == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), userId))
|
||||
return
|
||||
}
|
||||
|
||||
payment, attachInfo, err := object.BuyProduct(id, user, providerName, pricingName, planName, host, paymentEnv, customPrice, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(payment, attachInfo)
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
// GetPrometheusInfo
|
||||
// @Title GetPrometheusInfo
|
||||
// @Tag System API
|
||||
// @Description get Prometheus Info
|
||||
// @Success 200 {object} object.PrometheusInfo The Response object
|
||||
// @router /get-prometheus-info [get]
|
||||
func (c *ApiController) GetPrometheusInfo() {
|
||||
_, ok := c.RequireAdmin()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
prometheusInfo, err := object.GetPrometheusInfo()
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(prometheusInfo)
|
||||
}
|
||||
|
||||
// GetMetrics
|
||||
// @Title GetMetrics
|
||||
// @Tag System API
|
||||
// @Description get Prometheus metrics
|
||||
// @Success 200 {string} Prometheus metrics in text format
|
||||
// @router /metrics [get]
|
||||
func (c *ApiController) GetMetrics() {
|
||||
_, ok := c.RequireAdmin()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
promhttp.Handler().ServeHTTP(c.Ctx.ResponseWriter, c.Ctx.Request)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -17,174 +17,54 @@ package controllers
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/beego/beego/v2/core/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/casbin/casdoor/object"
|
||||
)
|
||||
|
||||
// GetProviders
|
||||
// @Title GetProviders
|
||||
// @Tag Provider API
|
||||
// @Description get providers
|
||||
// @Param owner query string true "The owner of providers"
|
||||
// @Success 200 {array} object.Provider The Response object
|
||||
// @router /get-providers [get]
|
||||
func (c *ApiController) GetProviders() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
owner := c.Input().Get("owner")
|
||||
|
||||
ok, isMaskEnabled := c.IsMaskedEnabled()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if limit == "" || page == "" {
|
||||
providers, err := object.GetProviders(owner)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetMaskedProviders(providers, isMaskEnabled))
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetProviderCount(owner, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
paginationProviders, err := object.GetPaginationProviders(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
providers := object.GetMaskedProviders(paginationProviders, isMaskEnabled)
|
||||
c.ResponseOk(providers, paginator.Nums())
|
||||
}
|
||||
c.Data["json"] = object.GetProviders(owner)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetGlobalProviders
|
||||
// @Title GetGlobalProviders
|
||||
// @Tag Provider API
|
||||
// @Description get Global providers
|
||||
// @Success 200 {array} object.Provider The Response object
|
||||
// @router /get-global-providers [get]
|
||||
func (c *ApiController) GetGlobalProviders() {
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
|
||||
ok, isMaskEnabled := c.IsMaskedEnabled()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if limit == "" || page == "" {
|
||||
globalProviders, err := object.GetGlobalProviders()
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetMaskedProviders(globalProviders, isMaskEnabled))
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetGlobalProviderCount(field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
paginationGlobalProviders, err := object.GetPaginationGlobalProviders(paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
providers := object.GetMaskedProviders(paginationGlobalProviders, isMaskEnabled)
|
||||
c.ResponseOk(providers, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetProvider
|
||||
// @Title GetProvider
|
||||
// @Tag Provider API
|
||||
// @Description get provider
|
||||
// @Param id query string true "The id ( owner/name ) of the provider"
|
||||
// @Param id query string true "The id of the provider"
|
||||
// @Success 200 {object} object.Provider The Response object
|
||||
// @router /get-provider [get]
|
||||
func (c *ApiController) GetProvider() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
id := c.Input().Get("id")
|
||||
|
||||
ok, isMaskEnabled := c.IsMaskedEnabled()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
provider, err := object.GetProvider(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetMaskedProvider(provider, isMaskEnabled))
|
||||
c.Data["json"] = object.GetProvider(id)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) requireProviderPermission(provider *object.Provider) bool {
|
||||
isGlobalAdmin, user := c.isGlobalAdmin()
|
||||
if isGlobalAdmin {
|
||||
return true
|
||||
}
|
||||
|
||||
if provider.Owner == "admin" || user.Owner != provider.Owner {
|
||||
c.ResponseError(c.T("auth:Unauthorized operation"))
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// UpdateProvider
|
||||
// @Title UpdateProvider
|
||||
// @Tag Provider API
|
||||
// @Description update provider
|
||||
// @Param id query string true "The id ( owner/name ) of the provider"
|
||||
// @Param id query string true "The id of the provider"
|
||||
// @Param body body object.Provider true "The details of the provider"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-provider [post]
|
||||
func (c *ApiController) UpdateProvider() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
id := c.Input().Get("id")
|
||||
|
||||
var provider object.Provider
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &provider)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
ok := c.requireProviderPermission(&provider)
|
||||
if !ok {
|
||||
return
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateProvider(id, &provider))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddProvider
|
||||
// @Title AddProvider
|
||||
// @Tag Provider API
|
||||
// @Description add provider
|
||||
// @Param body body object.Provider true "The details of the provider"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
@@ -193,34 +73,14 @@ func (c *ApiController) AddProvider() {
|
||||
var provider object.Provider
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &provider)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
count, err := object.GetProviderCount("", "", "")
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = checkQuotaForProvider(int(count))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
ok := c.requireProviderPermission(&provider)
|
||||
if !ok {
|
||||
return
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddProvider(&provider))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteProvider
|
||||
// @Title DeleteProvider
|
||||
// @Tag Provider API
|
||||
// @Description delete provider
|
||||
// @Param body body object.Provider true "The details of the provider"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
@@ -229,13 +89,7 @@ func (c *ApiController) DeleteProvider() {
|
||||
var provider object.Provider
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &provider)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
ok := c.requireProviderPermission(&provider)
|
||||
if !ok {
|
||||
return
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteProvider(&provider))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -17,110 +17,32 @@ package controllers
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/beego/beego/v2/core/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/casbin/casdoor/object"
|
||||
)
|
||||
|
||||
// GetRecords
|
||||
// @Title GetRecords
|
||||
// @Tag Record API
|
||||
// @Description get all records
|
||||
// @Param pageSize query string true "The size of each page"
|
||||
// @Param p query string true "The number of the page"
|
||||
// @Success 200 {object} object.Record The Response object
|
||||
// @Success 200 {array} object.Records The Response object
|
||||
// @router /get-records [get]
|
||||
func (c *ApiController) GetRecords() {
|
||||
organization, ok := c.RequireAdmin()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
organizationName := c.Ctx.Input.Query("organizationName")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
records, err := object.GetRecords()
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(records)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
if c.IsGlobalAdmin() && organizationName != "" {
|
||||
organization = organizationName
|
||||
}
|
||||
filterRecord := &object.Record{Organization: organization}
|
||||
count, err := object.GetRecordCount(field, value, filterRecord)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
records, err := object.GetPaginationRecords(paginator.Offset(), limit, field, value, sortField, sortOrder, filterRecord)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(records, paginator.Nums())
|
||||
}
|
||||
c.Data["json"] = object.GetRecords()
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetRecordsByFilter
|
||||
// @Tag Record API
|
||||
// @Title GetRecordsByFilter
|
||||
// @Description get records by filter
|
||||
// @Param filter body string true "filter Record message"
|
||||
// @Success 200 {object} object.Record The Response object
|
||||
// @Param body body object.Records true "filter Record message"
|
||||
// @Success 200 {array} object.Records The Response object
|
||||
// @router /get-records-filter [post]
|
||||
func (c *ApiController) GetRecordsByFilter() {
|
||||
_, ok := c.RequireAdmin()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
body := string(c.Ctx.Input.RequestBody)
|
||||
|
||||
record := &object.Record{}
|
||||
err := util.JsonToStruct(body, record)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
records, err := object.GetRecordsByField(record)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(records)
|
||||
}
|
||||
|
||||
// AddRecord
|
||||
// @Title AddRecord
|
||||
// @Tag Record API
|
||||
// @Description add a record
|
||||
// @Param body body object.Record true "The details of the record"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-record [post]
|
||||
func (c *ApiController) AddRecord() {
|
||||
var record object.Record
|
||||
var record object.Records
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &record)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddRecord(&record))
|
||||
c.Data["json"] = object.GetRecordsByField(&record)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -20,182 +20,64 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/beego/beego/v2/core/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/util"
|
||||
)
|
||||
|
||||
// GetResources
|
||||
// @Tag Resource API
|
||||
// @Title GetResources
|
||||
// @Description get resources
|
||||
// @Param owner query string true "Owner"
|
||||
// @Param user query string true "User"
|
||||
// @Param pageSize query integer false "Page Size"
|
||||
// @Param p query integer false "Page Number"
|
||||
// @Param field query string false "Field"
|
||||
// @Param value query string false "Value"
|
||||
// @Param sortField query string false "Sort Field"
|
||||
// @Param sortOrder query string false "Sort Order"
|
||||
// @Success 200 {array} object.Resource The Response object
|
||||
// @router /get-resources [get]
|
||||
func (c *ApiController) GetResources() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
user := c.Ctx.Input.Query("user")
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
owner := c.Input().Get("owner")
|
||||
user := c.Input().Get("user")
|
||||
|
||||
isOrgAdmin, ok := c.IsOrgAdmin()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if isOrgAdmin {
|
||||
user = ""
|
||||
}
|
||||
|
||||
if sortField == "Direct" {
|
||||
provider, err := c.GetProviderFromContext("Storage")
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
prefix := sortOrder
|
||||
resources, err := object.GetDirectResources(owner, user, provider, prefix, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(resources)
|
||||
} else if limit == "" || page == "" {
|
||||
resources, err := object.GetResources(owner, user)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(resources)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetResourceCount(owner, user, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
resources, err := object.GetPaginationResources(owner, user, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(resources, paginator.Nums())
|
||||
}
|
||||
c.Data["json"] = object.GetResources(owner, user)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetResource
|
||||
// @Tag Resource API
|
||||
// @Title GetResource
|
||||
// @Description get resource
|
||||
// @Param id query string true "The id ( owner/name ) of resource"
|
||||
// @Success 200 {object} object.Resource The Response object
|
||||
// @router /get-resource [get]
|
||||
func (c *ApiController) GetResource() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
id := c.Input().Get("id")
|
||||
|
||||
resource, err := object.GetResource(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(resource)
|
||||
c.Data["json"] = object.GetResource(id)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// UpdateResource
|
||||
// @Tag Resource API
|
||||
// @Title UpdateResource
|
||||
// @Description get resource
|
||||
// @Param id query string true "The id ( owner/name ) of resource"
|
||||
// @Param resource body object.Resource true "The resource object"
|
||||
// @Success 200 {object} controllers.Response Success or error
|
||||
// @router /update-resource [post]
|
||||
func (c *ApiController) UpdateResource() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
id := c.Input().Get("id")
|
||||
|
||||
var resource object.Resource
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &resource)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateResource(id, &resource))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddResource
|
||||
// @Tag Resource API
|
||||
// @Title AddResource
|
||||
// @Param resource body object.Resource true "Resource object"
|
||||
// @Success 200 {object} controllers.Response Success or error
|
||||
// @router /add-resource [post]
|
||||
func (c *ApiController) AddResource() {
|
||||
var resource object.Resource
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &resource)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddResource(&resource))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteResource
|
||||
// @Tag Resource API
|
||||
// @Title DeleteResource
|
||||
// @Param resource body object.Resource true "Resource object"
|
||||
// @Success 200 {object} controllers.Response Success or error
|
||||
// @router /delete-resource [post]
|
||||
func (c *ApiController) DeleteResource() {
|
||||
var resource object.Resource
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &resource)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
panic(err)
|
||||
}
|
||||
|
||||
provider, _, ok := c.GetProviderFromContext("Storage")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if resource.Provider != "" {
|
||||
inputs, _ := c.Input()
|
||||
inputs.Set("provider", resource.Provider)
|
||||
}
|
||||
inputs, _ := c.Input()
|
||||
inputs.Set("fullFilePath", resource.Name)
|
||||
provider, err := c.GetProviderFromContext("Storage")
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
_, resource.Name = refineFullFilePath(resource.Name)
|
||||
|
||||
tag := c.Ctx.Input.Query("tag")
|
||||
if tag == "Direct" {
|
||||
resource.Name = path.Join(provider.PathPrefix, resource.Name)
|
||||
}
|
||||
|
||||
err = object.DeleteFile(provider, resource.Name, c.GetAcceptLanguage())
|
||||
err = object.DeleteFile(provider, resource.Name)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@@ -205,29 +87,13 @@ func (c *ApiController) DeleteResource() {
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// UploadResource
|
||||
// @Tag Resource API
|
||||
// @Title UploadResource
|
||||
// @Param owner query string true "Owner"
|
||||
// @Param user query string true "User"
|
||||
// @Param application query string true "Application"
|
||||
// @Param tag query string false "Tag"
|
||||
// @Param parent query string false "Parent"
|
||||
// @Param fullFilePath query string true "Full File Path"
|
||||
// @Param createdTime query string false "Created Time"
|
||||
// @Param description query string false "Description"
|
||||
// @Param file formData file true "Resource file"
|
||||
// @Success 200 {object} object.Resource FileUrl, objectKey
|
||||
// @router /upload-resource [post]
|
||||
func (c *ApiController) UploadResource() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
username := c.Ctx.Input.Query("user")
|
||||
application := c.Ctx.Input.Query("application")
|
||||
tag := c.Ctx.Input.Query("tag")
|
||||
parent := c.Ctx.Input.Query("parent")
|
||||
fullFilePath := c.Ctx.Input.Query("fullFilePath")
|
||||
createdTime := c.Ctx.Input.Query("createdTime")
|
||||
description := c.Ctx.Input.Query("description")
|
||||
owner := c.Input().Get("owner")
|
||||
username := c.Input().Get("user")
|
||||
application := c.Input().Get("application")
|
||||
tag := c.Input().Get("tag")
|
||||
parent := c.Input().Get("parent")
|
||||
fullFilePath := c.Input().Get("fullFilePath")
|
||||
|
||||
file, header, err := c.GetFile("file")
|
||||
if err != nil {
|
||||
@@ -236,11 +102,6 @@ func (c *ApiController) UploadResource() {
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if username == "" || fullFilePath == "" {
|
||||
c.ResponseError(fmt.Sprintf(c.T("resource:Username or fullFilePath is empty: username = %s, fullFilePath = %s"), username, fullFilePath))
|
||||
return
|
||||
}
|
||||
|
||||
filename := filepath.Base(fullFilePath)
|
||||
fileBuffer := bytes.NewBuffer(nil)
|
||||
if _, err = io.Copy(fileBuffer, file); err != nil {
|
||||
@@ -248,61 +109,33 @@ func (c *ApiController) UploadResource() {
|
||||
return
|
||||
}
|
||||
|
||||
provider, err := c.GetProviderFromContext("Storage")
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
provider, user, ok := c.GetProviderFromContext("Storage")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
_, fullFilePath = refineFullFilePath(fullFilePath)
|
||||
|
||||
fileType := "unknown"
|
||||
contentType := header.Header.Get("Content-Type")
|
||||
fileType, _ = util.GetOwnerAndNameFromIdNoCheck(contentType + "/")
|
||||
fileType, _ = util.GetOwnerAndNameFromId(contentType)
|
||||
|
||||
if fileType != "image" && fileType != "video" {
|
||||
ext := filepath.Ext(filename)
|
||||
mimeType := mime.TypeByExtension(ext)
|
||||
fileType, _ = util.GetOwnerAndNameFromIdNoCheck(mimeType + "/")
|
||||
fileType, _ = util.GetOwnerAndNameFromId(mimeType)
|
||||
}
|
||||
|
||||
fullFilePath = object.GetTruncatedPath(provider, fullFilePath, 450)
|
||||
if tag != "avatar" && tag != "termsOfUse" && !strings.HasPrefix(tag, "idCard") {
|
||||
ext := filepath.Ext(filepath.Base(fullFilePath))
|
||||
index := len(fullFilePath) - len(ext)
|
||||
for i := 1; ; i++ {
|
||||
_, objectKey := object.GetUploadFileUrl(provider, fullFilePath, true)
|
||||
if count, err := object.GetResourceCount(owner, username, "name", objectKey); err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
} else if count == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// duplicated fullFilePath found, change it
|
||||
fullFilePath = fullFilePath[:index] + fmt.Sprintf("-%d", i) + ext
|
||||
}
|
||||
}
|
||||
|
||||
fileUrl, objectKey, err := object.UploadFileSafe(provider, fullFilePath, fileBuffer, c.GetAcceptLanguage())
|
||||
fileUrl, objectKey, err := object.UploadFile(provider, fullFilePath, fileBuffer)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if username == "Built-in-Untracked" {
|
||||
c.ResponseOk(fileUrl, objectKey)
|
||||
return
|
||||
}
|
||||
|
||||
if createdTime == "" {
|
||||
createdTime = util.GetCurrentTime()
|
||||
}
|
||||
fileFormat := filepath.Ext(fullFilePath)
|
||||
fileSize := int(header.Size)
|
||||
resource := &object.Resource{
|
||||
Owner: owner,
|
||||
Name: objectKey,
|
||||
CreatedTime: createdTime,
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
User: username,
|
||||
Provider: provider.Name,
|
||||
Application: application,
|
||||
@@ -313,86 +146,23 @@ func (c *ApiController) UploadResource() {
|
||||
FileFormat: fileFormat,
|
||||
FileSize: fileSize,
|
||||
Url: fileUrl,
|
||||
Description: description,
|
||||
}
|
||||
_, err = object.AddOrUpdateResource(resource)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
object.AddOrUpdateResource(resource)
|
||||
|
||||
switch tag {
|
||||
case "avatar":
|
||||
user, err := object.GetUserNoCheck(util.GetId(owner, username))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
c.ResponseError(c.T("resource:User is nil for tag: avatar"))
|
||||
c.ResponseError("user is nil for tag: \"avatar\"")
|
||||
return
|
||||
}
|
||||
|
||||
user.Avatar = fileUrl
|
||||
_, err = object.UpdateUser(user.GetId(), user, []string{"avatar"}, false)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
object.UpdateUser(user.GetId(), user)
|
||||
case "termsOfUse":
|
||||
user, err := object.GetUserNoCheck(util.GetId(owner, username))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(owner, username)))
|
||||
return
|
||||
}
|
||||
|
||||
if !user.IsAdminUser() {
|
||||
c.ResponseError(c.T("auth:Unauthorized operation"))
|
||||
return
|
||||
}
|
||||
|
||||
_, applicationId := util.GetOwnerAndNameFromIdNoCheck(strings.TrimSuffix(fullFilePath, ".html"))
|
||||
applicationObj, err := object.GetApplication(applicationId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
applicationObj.TermsOfUse = fileUrl
|
||||
_, err = object.UpdateApplication(applicationId, applicationObj, true, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
case "idCardFront", "idCardBack", "idCardWithPerson":
|
||||
user, err := object.GetUserNoCheck(util.GetId(owner, username))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
c.ResponseError(c.T("resource:User is nil for tag: avatar"))
|
||||
return
|
||||
}
|
||||
|
||||
if user.Properties == nil {
|
||||
user.Properties = map[string]string{}
|
||||
}
|
||||
user.Properties[tag] = fileUrl
|
||||
user.Properties["isIdCardVerified"] = "false"
|
||||
_, err = object.UpdateUser(user.GetId(), user, []string{"properties"}, false)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
applicationId := fmt.Sprintf("admin/%s", parent)
|
||||
app := object.GetApplication(applicationId)
|
||||
app.TermsOfUse = fileUrl
|
||||
object.UpdateApplication(applicationId, app)
|
||||
}
|
||||
|
||||
c.ResponseOk(fileUrl, objectKey)
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/beego/beego/v2/core/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetRoles
|
||||
// @Title GetRoles
|
||||
// @Tag Role API
|
||||
// @Description get roles
|
||||
// @Param owner query string true "The owner of roles"
|
||||
// @Success 200 {array} object.Role The Response object
|
||||
// @router /get-roles [get]
|
||||
func (c *ApiController) GetRoles() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
roles, err := object.GetRoles(owner)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(roles)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetRoleCount(owner, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
roles, err := object.GetPaginationRoles(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(roles, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetRole
|
||||
// @Title GetRole
|
||||
// @Tag Role API
|
||||
// @Description get role
|
||||
// @Param id query string true "The id ( owner/name ) of the role"
|
||||
// @Success 200 {object} object.Role The Response object
|
||||
// @router /get-role [get]
|
||||
func (c *ApiController) GetRole() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
role, err := object.GetRole(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(role)
|
||||
}
|
||||
|
||||
// UpdateRole
|
||||
// @Title UpdateRole
|
||||
// @Tag Role API
|
||||
// @Description update role
|
||||
// @Param id query string true "The id ( owner/name ) of the role"
|
||||
// @Param body body object.Role true "The details of the role"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-role [post]
|
||||
func (c *ApiController) UpdateRole() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
var role object.Role
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &role)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateRole(id, &role))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddRole
|
||||
// @Title AddRole
|
||||
// @Tag Role API
|
||||
// @Description add role
|
||||
// @Param body body object.Role true "The details of the role"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-role [post]
|
||||
func (c *ApiController) AddRole() {
|
||||
var role object.Role
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &role)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddRole(&role))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteRole
|
||||
// @Title DeleteRole
|
||||
// @Tag Role API
|
||||
// @Description delete role
|
||||
// @Param body body object.Role true "The details of the role"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-role [post]
|
||||
func (c *ApiController) DeleteRole() {
|
||||
var role object.Role
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &role)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteRole(&role))
|
||||
c.ServeJSON()
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func (c *ApiController) UploadRoles() {
|
||||
userId := c.GetSessionUsername()
|
||||
owner, user, err := util.GetOwnerAndNameFromIdWithError(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
file, header, err := c.Ctx.Request.FormFile("file")
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fileId := fmt.Sprintf("%s_%s_%s", owner, user, util.RemoveExt(header.Filename))
|
||||
path := util.GetUploadXlsxPath(fileId)
|
||||
defer os.Remove(path)
|
||||
err = saveFile(path, &file)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
affected, err := object.UploadRoles(owner, path)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
}
|
||||
|
||||
if affected {
|
||||
c.ResponseOk()
|
||||
} else {
|
||||
c.ResponseError(c.T("general:Failed to import users"))
|
||||
}
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
// Copyright 2023 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/beego/beego/v2/server/web/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/hsluoyz/modsecurity-go/seclang/parser"
|
||||
)
|
||||
|
||||
// GetRules
|
||||
// @Title GetRules
|
||||
// @Tag Rule API
|
||||
// @Description get rules
|
||||
// @Param owner query string true "The owner of rules"
|
||||
// @Success 200 {array} object.Rule The Response object
|
||||
// @router /get-rules [get]
|
||||
func (c *ApiController) GetRules() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
if owner == "admin" {
|
||||
owner = ""
|
||||
}
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
rules, err := object.GetRules(owner)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(rules)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetRuleCount(owner, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||
rules, err := object.GetPaginationRules(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(rules, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetRule
|
||||
// @Title GetRule
|
||||
// @Tag Rule API
|
||||
// @Description get rule
|
||||
// @Param id query string true "The id ( owner/name ) of the rule"
|
||||
// @Success 200 {object} object.Rule The Response object
|
||||
// @router /get-rule [get]
|
||||
func (c *ApiController) GetRule() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
rule, err := object.GetRule(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(rule)
|
||||
}
|
||||
|
||||
// AddRule
|
||||
// @Title AddRule
|
||||
// @Tag Rule API
|
||||
// @Description add rule
|
||||
// @Param body body object.Rule true "The details of the rule"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-rule [post]
|
||||
func (c *ApiController) AddRule() {
|
||||
currentTime := util.GetCurrentTime()
|
||||
rule := object.Rule{
|
||||
CreatedTime: currentTime,
|
||||
UpdatedTime: currentTime,
|
||||
}
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &rule)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
err = checkExpressions(rule.Expressions, rule.Type)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
c.Data["json"] = wrapActionResponse(object.AddRule(&rule))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// UpdateRule
|
||||
// @Title UpdateRule
|
||||
// @Tag Rule API
|
||||
// @Description update rule
|
||||
// @Param id query string true "The id ( owner/name ) of the rule"
|
||||
// @Param body body object.Rule true "The details of the rule"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-rule [post]
|
||||
func (c *ApiController) UpdateRule() {
|
||||
var rule object.Rule
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &rule)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = checkExpressions(rule.Expressions, rule.Type)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
id := c.Ctx.Input.Query("id")
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateRule(id, &rule))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteRule
|
||||
// @Title DeleteRule
|
||||
// @Tag Rule API
|
||||
// @Description delete rule
|
||||
// @Param body body object.Rule true "The details of the rule"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-rule [post]
|
||||
func (c *ApiController) DeleteRule() {
|
||||
var rule object.Rule
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &rule)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteRule(&rule))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func checkExpressions(expressions []*object.Expression, ruleType string) error {
|
||||
values := make([]string, len(expressions))
|
||||
for i, expression := range expressions {
|
||||
values[i] = expression.Value
|
||||
}
|
||||
switch ruleType {
|
||||
case "WAF":
|
||||
return checkWafRule(values)
|
||||
case "IP":
|
||||
return checkIpRule(values)
|
||||
case "IP Rate Limiting":
|
||||
return checkIpRateRule(expressions)
|
||||
case "Compound":
|
||||
return checkCompoundRules(values)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkWafRule(rules []string) error {
|
||||
for _, rule := range rules {
|
||||
scanner := parser.NewSecLangScannerFromString(rule)
|
||||
_, err := scanner.AllDirective()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkIpRule(ipLists []string) error {
|
||||
for _, ipList := range ipLists {
|
||||
for _, ip := range strings.Split(ipList, ",") {
|
||||
_, _, err := net.ParseCIDR(ip)
|
||||
if net.ParseIP(ip) == nil && err != nil {
|
||||
return errors.New("Invalid IP address: " + ip)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkIpRateRule(expressions []*object.Expression) error {
|
||||
if len(expressions) != 1 {
|
||||
return errors.New("IP Rate Limiting rule must have exactly one expression")
|
||||
}
|
||||
expression := expressions[0]
|
||||
_, err := util.ParseIntWithError(expression.Operator)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = util.ParseIntWithError(expression.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkCompoundRules(rules []string) error {
|
||||
_, err := object.GetRulesByRuleIds(rules)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
)
|
||||
|
||||
func (c *ApiController) GetSamlMeta() {
|
||||
host := c.Ctx.Request.Host
|
||||
paramApp := c.Ctx.Input.Query("application")
|
||||
application, err := object.GetApplication(paramApp)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("saml:Application %s not found"), paramApp))
|
||||
return
|
||||
}
|
||||
|
||||
enablePostBinding, err := c.GetBool("enablePostBinding", false)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
metadata, err := object.GetSamlMeta(application, host, enablePostBinding)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["xml"] = metadata
|
||||
c.ServeXML()
|
||||
}
|
||||
|
||||
func (c *ApiController) HandleSamlRedirect() {
|
||||
host := c.Ctx.Request.Host
|
||||
|
||||
owner := c.Ctx.Input.Param(":owner")
|
||||
application := c.Ctx.Input.Param(":application")
|
||||
|
||||
relayState := c.Ctx.Input.Query("RelayState")
|
||||
samlRequest := c.Ctx.Input.Query("SAMLRequest")
|
||||
username := c.Ctx.Input.Query("username")
|
||||
loginHint := c.Ctx.Input.Query("login_hint")
|
||||
|
||||
relayState = url.QueryEscape(relayState)
|
||||
targetURL := object.GetSamlRedirectAddress(owner, application, relayState, samlRequest, host, username, loginHint)
|
||||
|
||||
c.Redirect(targetURL, http.StatusSeeOther)
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/scim"
|
||||
)
|
||||
|
||||
func (c *RootController) HandleScim() {
|
||||
_, ok := c.RequireAdmin()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
path := c.Ctx.Request.URL.Path
|
||||
c.Ctx.Request.URL.Path = strings.TrimPrefix(path, "/scim")
|
||||
scim.Server.ServeHTTP(c.Ctx.ResponseWriter, c.Ctx.Request)
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
// Copyright 2026 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/beego/beego/v2/server/web/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetServers
|
||||
// @Title GetServers
|
||||
// @Tag Server API
|
||||
// @Description get servers
|
||||
// @Param owner query string true "The owner of servers"
|
||||
// @Success 200 {array} object.Server The Response object
|
||||
// @router /get-servers [get]
|
||||
func (c *ApiController) GetServers() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
if owner == "admin" {
|
||||
owner = ""
|
||||
}
|
||||
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
servers, err := object.GetServers(owner)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
c.ResponseOk(servers)
|
||||
return
|
||||
}
|
||||
|
||||
limitInt := util.ParseInt(limit)
|
||||
count, err := object.GetServerCount(owner, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.SetPaginator(c.Ctx, limitInt, count)
|
||||
servers, err := object.GetPaginationServers(owner, paginator.Offset(), limitInt, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(servers, paginator.Nums())
|
||||
}
|
||||
|
||||
// GetServer
|
||||
// @Title GetServer
|
||||
// @Tag Server API
|
||||
// @Description get server
|
||||
// @Param id query string true "The id ( owner/name ) of the server"
|
||||
// @Success 200 {object} object.Server The Response object
|
||||
// @router /get-server [get]
|
||||
func (c *ApiController) GetServer() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
server, err := object.GetServer(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(server)
|
||||
}
|
||||
|
||||
// UpdateServer
|
||||
// @Title UpdateServer
|
||||
// @Tag Server API
|
||||
// @Description update server
|
||||
// @Param id query string true "The id ( owner/name ) of the server"
|
||||
// @Param body body object.Server true "The details of the server"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-server [post]
|
||||
func (c *ApiController) UpdateServer() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
var server object.Server
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &server)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateServer(id, &server))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// SyncMcpTool
|
||||
// @Title SyncMcpTool
|
||||
// @Tag Server API
|
||||
// @Description sync MCP tools for a server and return sync errors directly
|
||||
// @Param id query string true "The id ( owner/name ) of the server"
|
||||
// @Param isCleared query bool false "Whether to clear all tools instead of syncing"
|
||||
// @Param body body object.Server true "The details of the server"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /sync-mcp-tool [post]
|
||||
func (c *ApiController) SyncMcpTool() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
isCleared := c.Ctx.Input.Query("isCleared") == "1"
|
||||
|
||||
var server object.Server
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &server)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.SyncMcpTool(id, &server, isCleared))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddServer
|
||||
// @Title AddServer
|
||||
// @Tag Server API
|
||||
// @Description add server
|
||||
// @Param body body object.Server true "The details of the server"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-server [post]
|
||||
func (c *ApiController) AddServer() {
|
||||
var server object.Server
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &server)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddServer(&server))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteServer
|
||||
// @Title DeleteServer
|
||||
// @Tag Server API
|
||||
// @Description delete server
|
||||
// @Param body body object.Server true "The details of the server"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-server [post]
|
||||
func (c *ApiController) DeleteServer() {
|
||||
var server object.Server
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &server)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteServer(&server))
|
||||
c.ServeJSON()
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
// Copyright 2026 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const onlineServerListUrl = "https://mcp.casdoor.org/registry.json"
|
||||
|
||||
// GetOnlineServers
|
||||
// @Title GetOnlineServers
|
||||
// @Tag Server API
|
||||
// @Description get online MCP server list
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /get-online-servers [get]
|
||||
func (c *ApiController) GetOnlineServers() {
|
||||
httpClient := &http.Client{Timeout: 10 * time.Second}
|
||||
resp, err := httpClient.Get(onlineServerListUrl)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
|
||||
c.ResponseError(fmt.Sprintf("failed to get online server list, status code: %d", resp.StatusCode))
|
||||
return
|
||||
}
|
||||
|
||||
var onlineServers interface{}
|
||||
err = json.NewDecoder(resp.Body).Decode(&onlineServers)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(onlineServers)
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
// Copyright 2026 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/casdoor/casdoor/mcp"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultSyncTimeoutMs = 1200
|
||||
defaultSyncMaxConcurrency = 32
|
||||
maxSyncHosts = 1024
|
||||
)
|
||||
|
||||
var (
|
||||
defaultSyncPorts = []int{3000, 8080, 80}
|
||||
defaultSyncPaths = []string{"/", "/mcp", "/sse", "/mcp/sse"}
|
||||
)
|
||||
|
||||
type SyncInnerServersRequest struct {
|
||||
CIDR []string `json:"cidr"`
|
||||
Scheme string `json:"scheme"`
|
||||
Ports []string `json:"ports"`
|
||||
Paths []string `json:"paths"`
|
||||
TimeoutMs int `json:"timeoutMs"`
|
||||
MaxConcurrency int `json:"maxConcurrency"`
|
||||
}
|
||||
|
||||
type SyncInnerServersResult struct {
|
||||
CIDR []string `json:"cidr"`
|
||||
ScannedHosts int `json:"scannedHosts"`
|
||||
OnlineHosts []string `json:"onlineHosts"`
|
||||
Servers []*mcp.InnerMcpServer `json:"servers"`
|
||||
}
|
||||
|
||||
// SyncIntranetServers
|
||||
// @Title SyncIntranetServers
|
||||
// @Tag Server API
|
||||
// @Description scan intranet IP/CIDR targets and detect MCP servers by probing common ports and paths
|
||||
// @Param body body controllers.SyncInnerServersRequest true "Intranet MCP server scan request"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /sync-intranet-servers [post]
|
||||
func (c *ApiController) SyncIntranetServers() {
|
||||
_, ok := c.RequireAdmin()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var req SyncInnerServersRequest
|
||||
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &req); err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
for i := range req.CIDR {
|
||||
req.CIDR[i] = strings.TrimSpace(req.CIDR[i])
|
||||
}
|
||||
if len(req.CIDR) == 0 {
|
||||
c.ResponseError("scan target (CIDR/IP) is required")
|
||||
return
|
||||
}
|
||||
|
||||
hosts, err := mcp.ParseScanTargets(req.CIDR, maxSyncHosts)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
timeout := mcp.SanitizeTimeout(req.TimeoutMs, defaultSyncTimeoutMs, 10000)
|
||||
concurrency := mcp.SanitizeConcurrency(req.MaxConcurrency, defaultSyncMaxConcurrency, 256)
|
||||
ports := mcp.SanitizePorts(req.Ports, defaultSyncPorts)
|
||||
paths := mcp.SanitizePaths(req.Paths, defaultSyncPaths)
|
||||
scheme := mcp.SanitizeScheme(req.Scheme)
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: timeout,
|
||||
CheckRedirect: func(_ *http.Request, _ []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
defer cancel()
|
||||
|
||||
onlineHostSet := map[string]struct{}{}
|
||||
serverMap := map[string]*mcp.InnerMcpServer{}
|
||||
mutex := sync.Mutex{}
|
||||
waitGroup := sync.WaitGroup{}
|
||||
sem := make(chan struct{}, concurrency)
|
||||
|
||||
for _, host := range hosts {
|
||||
host := host.String()
|
||||
waitGroup.Add(1)
|
||||
go func() {
|
||||
defer waitGroup.Done()
|
||||
|
||||
select {
|
||||
case sem <- struct{}{}:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
defer func() { <-sem }()
|
||||
|
||||
isOnline, servers := mcp.ProbeHost(ctx, client, scheme, host, ports, paths, timeout)
|
||||
if !isOnline {
|
||||
return
|
||||
}
|
||||
|
||||
mutex.Lock()
|
||||
onlineHostSet[host] = struct{}{}
|
||||
for _, server := range servers {
|
||||
serverMap[server.Url] = server
|
||||
}
|
||||
mutex.Unlock()
|
||||
}()
|
||||
}
|
||||
|
||||
waitGroup.Wait()
|
||||
|
||||
onlineHosts := make([]string, 0, len(onlineHostSet))
|
||||
for host := range onlineHostSet {
|
||||
onlineHosts = append(onlineHosts, host)
|
||||
}
|
||||
slices.Sort(onlineHosts)
|
||||
|
||||
servers := make([]*mcp.InnerMcpServer, 0, len(serverMap))
|
||||
for _, server := range serverMap {
|
||||
servers = append(servers, server)
|
||||
}
|
||||
slices.SortFunc(servers, func(a, b *mcp.InnerMcpServer) int {
|
||||
if a.Url < b.Url {
|
||||
return -1
|
||||
}
|
||||
if a.Url > b.Url {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
|
||||
c.ResponseOk(&SyncInnerServersResult{
|
||||
CIDR: req.CIDR,
|
||||
ScannedHosts: len(hosts),
|
||||
OnlineHosts: onlineHosts,
|
||||
Servers: servers,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *ApiController) SyncInnerServers() {
|
||||
c.SyncIntranetServers()
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -20,89 +20,39 @@ package controllers
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/util"
|
||||
)
|
||||
|
||||
type EmailForm struct {
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
Sender string `json:"sender"`
|
||||
Receivers []string `json:"receivers"`
|
||||
Provider string `json:"provider"`
|
||||
ProviderObject object.Provider `json:"providerObject"`
|
||||
}
|
||||
|
||||
type SmsForm struct {
|
||||
Content string `json:"content"`
|
||||
Receivers []string `json:"receivers"`
|
||||
OrgId string `json:"organizationId"` // e.g. "admin/built-in"
|
||||
}
|
||||
|
||||
type NotificationForm struct {
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
// SendEmail
|
||||
// @Title SendEmail
|
||||
// @Tag Service API
|
||||
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
|
||||
// @Param clientId query string true "The clientId of the application"
|
||||
// @Param clientSecret query string true "The clientSecret of the application"
|
||||
// @Param from body controllers.EmailForm true "Details of the email request"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /send-email [post]
|
||||
// @Param body body emailForm true "Details of the email request"
|
||||
// @Success 200 {object} Response object
|
||||
// @router /api/send-email [post]
|
||||
func (c *ApiController) SendEmail() {
|
||||
userId, ok := c.RequireSignedIn()
|
||||
provider, _, ok := c.GetProviderFromContext("Email")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var emailForm EmailForm
|
||||
var emailForm struct {
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
Sender string `json:"sender"`
|
||||
Receivers []string `json:"receivers"`
|
||||
}
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &emailForm)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var provider *object.Provider
|
||||
if emailForm.Provider != "" {
|
||||
// called by frontend's TestEmailWidget, provider name is set by frontend
|
||||
provider, err = object.GetProvider(util.GetId("admin", emailForm.Provider))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// called by Casdoor SDK via Client ID & Client Secret, so the used Email provider will be the application' Email provider or the default Email provider
|
||||
provider, err = c.GetProviderFromContext("Email")
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if emailForm.ProviderObject.Name != "" {
|
||||
if emailForm.ProviderObject.ClientSecret == "***" {
|
||||
emailForm.ProviderObject.ClientSecret = provider.ClientSecret
|
||||
}
|
||||
provider = &emailForm.ProviderObject
|
||||
}
|
||||
|
||||
// when receiver is the reserved keyword: "TestSmtpServer", it means to test the SMTP server instead of sending a real Email
|
||||
if len(emailForm.Receivers) == 1 && emailForm.Receivers[0] == "TestSmtpServer" {
|
||||
err = object.TestSmtpServer(provider)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
c.ResponseOk()
|
||||
}
|
||||
|
||||
if util.IsStringsEmpty(emailForm.Title, emailForm.Content, emailForm.Sender) {
|
||||
c.ResponseError(fmt.Sprintf(c.T("service:Empty parameters for emailForm: %v"), emailForm))
|
||||
if util.IsStrsEmpty(emailForm.Title, emailForm.Content, emailForm.Sender) {
|
||||
c.ResponseError(fmt.Sprintf("Empty parameters for emailForm: %v", emailForm))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -114,37 +64,12 @@ func (c *ApiController) SendEmail() {
|
||||
}
|
||||
|
||||
if len(invalidReceivers) != 0 {
|
||||
c.ResponseError(fmt.Sprintf(c.T("service:Invalid Email receivers: %s"), invalidReceivers))
|
||||
c.ResponseError(fmt.Sprintf("Invalid Email receivers: %s", invalidReceivers))
|
||||
return
|
||||
}
|
||||
|
||||
content := emailForm.Content
|
||||
if content == "" {
|
||||
content = provider.Content
|
||||
}
|
||||
|
||||
code := "123456"
|
||||
// "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes."
|
||||
content = strings.Replace(content, "%s", code, 1)
|
||||
userString := "Hi"
|
||||
if !object.IsAppUser(userId) {
|
||||
var user *object.User
|
||||
user, err = object.GetUser(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if user != nil {
|
||||
userString = user.GetFriendlyName()
|
||||
}
|
||||
}
|
||||
content = strings.Replace(content, "%{user.friendlyName}", userString, 1)
|
||||
|
||||
matchContent := object.ResetLinkReg.Find([]byte(content))
|
||||
content = strings.Replace(content, string(matchContent), "", -1)
|
||||
|
||||
for _, receiver := range emailForm.Receivers {
|
||||
err = object.SendEmail(provider, emailForm.Title, content, []string{receiver}, emailForm.Sender)
|
||||
err = object.SendEmail(provider, emailForm.Title, emailForm.Content, receiver, emailForm.Sender)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@@ -156,35 +81,40 @@ func (c *ApiController) SendEmail() {
|
||||
|
||||
// SendSms
|
||||
// @Title SendSms
|
||||
// @Tag Service API
|
||||
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
|
||||
// @Param clientId query string true "The clientId of the application"
|
||||
// @Param clientSecret query string true "The clientSecret of the application"
|
||||
// @Param from body controllers.SmsForm true "Details of the sms request"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /send-sms [post]
|
||||
// @Param body body smsForm true "Details of the sms request"
|
||||
// @Success 200 {object} Response object
|
||||
// @router /api/send-sms [post]
|
||||
func (c *ApiController) SendSms() {
|
||||
provider, err := c.GetProviderFromContext("SMS")
|
||||
provider, _, ok := c.GetProviderFromContext("SMS")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var smsForm struct {
|
||||
Content string `json:"content"`
|
||||
Receivers []string `json:"receivers"`
|
||||
}
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &smsForm)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var smsForm SmsForm
|
||||
err = json.Unmarshal(c.Ctx.Input.RequestBody, &smsForm)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if provider.Type != "Custom HTTP SMS" {
|
||||
invalidReceivers := getInvalidSmsReceivers(smsForm)
|
||||
if len(invalidReceivers) != 0 {
|
||||
c.ResponseError(fmt.Sprintf(c.T("service:Invalid phone receivers: %s"), strings.Join(invalidReceivers, ", ")))
|
||||
return
|
||||
var invalidReceivers []string
|
||||
for _, receiver := range smsForm.Receivers {
|
||||
if !util.IsPhoneCnValid(receiver) {
|
||||
invalidReceivers = append(invalidReceivers, receiver)
|
||||
}
|
||||
}
|
||||
|
||||
if len(invalidReceivers) != 0 {
|
||||
c.ResponseError(fmt.Sprintf("Invalid phone receivers: %s", invalidReceivers))
|
||||
return
|
||||
}
|
||||
|
||||
err = object.SendSms(provider, smsForm.Content, smsForm.Receivers...)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
@@ -193,33 +123,3 @@ func (c *ApiController) SendSms() {
|
||||
|
||||
c.ResponseOk()
|
||||
}
|
||||
|
||||
// SendNotification
|
||||
// @Title SendNotification
|
||||
// @Tag Service API
|
||||
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
|
||||
// @Param from body controllers.NotificationForm true "Details of the notification request"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /send-notification [post]
|
||||
func (c *ApiController) SendNotification() {
|
||||
provider, err := c.GetProviderFromContext("Notification")
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var notificationForm NotificationForm
|
||||
err = json.Unmarshal(c.Ctx.Input.RequestBody, ¬ificationForm)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = object.SendNotification(provider, notificationForm.Content)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk()
|
||||
}
|
||||
|
||||
@@ -1,178 +0,0 @@
|
||||
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/beego/beego/v2/core/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetSessions
|
||||
// @Title GetSessions
|
||||
// @Tag Session API
|
||||
// @Description Get organization user sessions.
|
||||
// @Param owner query string true "The organization name"
|
||||
// @Success 200 {array} string The Response object
|
||||
// @router /get-sessions [get]
|
||||
func (c *ApiController) GetSessions() {
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
sessions, err := object.GetSessions(owner)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(sessions)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetSessionCount(owner, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
sessions, err := object.GetPaginationSessions(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(sessions, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetSingleSession
|
||||
// @Title GetSingleSession
|
||||
// @Tag Session API
|
||||
// @Description Get session for one user in one application.
|
||||
// @Param sessionPkId query string true "The session ID in format: organization/user/application (e.g., built-in/admin/app-built-in)"
|
||||
// @Success 200 {array} string The Response object
|
||||
// @router /get-session [get]
|
||||
func (c *ApiController) GetSingleSession() {
|
||||
id := c.Ctx.Input.Query("sessionPkId")
|
||||
|
||||
session, err := object.GetSingleSession(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(session)
|
||||
}
|
||||
|
||||
// UpdateSession
|
||||
// @Title UpdateSession
|
||||
// @Tag Session API
|
||||
// @Description Update session for one user in one application.
|
||||
// @Param body body object.Session true "The session object to update"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-session [post]
|
||||
func (c *ApiController) UpdateSession() {
|
||||
var session object.Session
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &session)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateSession(util.GetSessionId(session.Owner, session.Name, session.Application), &session))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddSession
|
||||
// @Title AddSession
|
||||
// @Tag Session API
|
||||
// @Description Add session for one user in one application. If there are other existing sessions, join the session into the list.
|
||||
// @Param body body object.Session true "The session object to add"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-session [post]
|
||||
func (c *ApiController) AddSession() {
|
||||
var session object.Session
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &session)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddSession(&session))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteSession
|
||||
// @Title DeleteSession
|
||||
// @Tag Session API
|
||||
// @Description Delete session for one user in one application.
|
||||
// @Param body body object.Session true "The session object to delete"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-session [post]
|
||||
func (c *ApiController) DeleteSession() {
|
||||
var session object.Session
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &session)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
curSessionId := c.Ctx.Input.CruSession.SessionID(context.Background())
|
||||
|
||||
sessionId := c.Ctx.Input.Query("sessionId")
|
||||
if curSessionId == sessionId && sessionId != "" {
|
||||
c.ResponseError(fmt.Sprintf(c.T("session:session id %s is the current session and cannot be deleted"), curSessionId))
|
||||
return
|
||||
}
|
||||
|
||||
if sessionId != "" {
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteSessionId(util.GetSessionId(session.Owner, session.Name, session.Application), sessionId))
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteSession(util.GetSessionId(session.Owner, session.Name, session.Application), curSessionId))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// IsSessionDuplicated
|
||||
// @Title IsSessionDuplicated
|
||||
// @Tag Session API
|
||||
// @Description Check if there are other different sessions for one user in one application.
|
||||
// @Param sessionPkId query string true "The session ID in format: organization/user/application (e.g., built-in/admin/app-built-in)"
|
||||
// @Param sessionId query string true "The specific session ID to check"
|
||||
// @Success 200 {array} string The Response object
|
||||
// @router /is-session-duplicated [get]
|
||||
func (c *ApiController) IsSessionDuplicated() {
|
||||
id := c.Ctx.Input.Query("sessionPkId")
|
||||
sessionId := c.Ctx.Input.Query("sessionId")
|
||||
|
||||
isUserSessionDuplicated, err := object.IsSessionDuplicated(id, sessionId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(isUserSessionDuplicated)
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
// Copyright 2023 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/beego/beego/v2/server/web/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetGlobalSites
|
||||
// @Title GetGlobalSites
|
||||
// @Tag Site API
|
||||
// @Description get global sites
|
||||
// @Success 200 {array} object.Site The Response object
|
||||
// @router /get-global-sites [get]
|
||||
func (c *ApiController) GetGlobalSites() {
|
||||
sites, err := object.GetGlobalSites()
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetMaskedSites(sites, util.GetHostname()))
|
||||
}
|
||||
|
||||
// GetSites
|
||||
// @Title GetSites
|
||||
// @Tag Site API
|
||||
// @Description get sites
|
||||
// @Param owner query string true "The owner of sites"
|
||||
// @Success 200 {array} object.Site The Response object
|
||||
// @router /get-sites [get]
|
||||
func (c *ApiController) GetSites() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
if owner == "admin" {
|
||||
owner = ""
|
||||
}
|
||||
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
sites, err := object.GetSites(owner)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
c.ResponseOk(object.GetMaskedSites(sites, util.GetHostname()))
|
||||
return
|
||||
}
|
||||
|
||||
limitInt := util.ParseInt(limit)
|
||||
count, err := object.GetSiteCount(owner, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.SetPaginator(c.Ctx, limitInt, count)
|
||||
sites, err := object.GetPaginationSites(owner, paginator.Offset(), limitInt, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetMaskedSites(sites, util.GetHostname()), paginator.Nums())
|
||||
}
|
||||
|
||||
// GetSite
|
||||
// @Title GetSite
|
||||
// @Tag Site API
|
||||
// @Description get site
|
||||
// @Param id query string true "The id ( owner/name ) of the site"
|
||||
// @Success 200 {object} object.Site The Response object
|
||||
// @router /get-site [get]
|
||||
func (c *ApiController) GetSite() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
site, err := object.GetSite(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetMaskedSite(site, util.GetHostname()))
|
||||
}
|
||||
|
||||
// UpdateSite
|
||||
// @Title UpdateSite
|
||||
// @Tag Site API
|
||||
// @Description update site
|
||||
// @Param id query string true "The id ( owner/name ) of the site"
|
||||
// @Param body body object.Site true "The details of the site"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-site [post]
|
||||
func (c *ApiController) UpdateSite() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
var site object.Site
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &site)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateSite(id, &site))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddSite
|
||||
// @Title AddSite
|
||||
// @Tag Site API
|
||||
// @Description add site
|
||||
// @Param body body object.Site true "The details of the site"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-site [post]
|
||||
func (c *ApiController) AddSite() {
|
||||
var site object.Site
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &site)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddSite(&site))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteSite
|
||||
// @Title DeleteSite
|
||||
// @Tag Site API
|
||||
// @Description delete site
|
||||
// @Param body body object.Site true "The details of the site"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-site [post]
|
||||
func (c *ApiController) DeleteSite() {
|
||||
var site object.Site
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &site)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteSite(&site))
|
||||
c.ServeJSON()
|
||||
}
|
||||
@@ -1,195 +0,0 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/beego/beego/v2/core/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetSubscriptions
|
||||
// @Title GetSubscriptions
|
||||
// @Tag Subscription API
|
||||
// @Description get subscriptions
|
||||
// @Param owner query string true "The owner of subscriptions"
|
||||
// @Success 200 {array} object.Subscription The Response object
|
||||
// @router /get-subscriptions [get]
|
||||
func (c *ApiController) GetSubscriptions() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
var subscriptions []*object.Subscription
|
||||
var err error
|
||||
|
||||
if c.IsAdmin() {
|
||||
// If field is "user", filter by that user even for admins
|
||||
if field == "user" && value != "" {
|
||||
subscriptions, err = object.GetSubscriptionsByUser(owner, value)
|
||||
} else {
|
||||
subscriptions, err = object.GetSubscriptions(owner)
|
||||
}
|
||||
} else {
|
||||
user := c.GetSessionUsername()
|
||||
_, userName, userErr := util.GetOwnerAndNameFromIdWithError(user)
|
||||
if userErr != nil {
|
||||
c.ResponseError(userErr.Error())
|
||||
return
|
||||
}
|
||||
subscriptions, err = object.GetSubscriptionsByUser(owner, userName)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(subscriptions)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
if !c.IsAdmin() {
|
||||
user := c.GetSessionUsername()
|
||||
_, userName, userErr := util.GetOwnerAndNameFromIdWithError(user)
|
||||
if userErr != nil {
|
||||
c.ResponseError(userErr.Error())
|
||||
return
|
||||
}
|
||||
field = "user"
|
||||
value = userName
|
||||
}
|
||||
count, err := object.GetSubscriptionCount(owner, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
subscription, err := object.GetPaginationSubscriptions(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(subscription, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetSubscription
|
||||
// @Title GetSubscription
|
||||
// @Tag Subscription API
|
||||
// @Description get subscription
|
||||
// @Param id query string true "The id ( owner/name ) of the subscription"
|
||||
// @Success 200 {object} object.Subscription The Response object
|
||||
// @router /get-subscription [get]
|
||||
func (c *ApiController) GetSubscription() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
subscription, err := object.GetSubscription(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(subscription)
|
||||
}
|
||||
|
||||
// UpdateSubscription
|
||||
// @Title UpdateSubscription
|
||||
// @Tag Subscription API
|
||||
// @Description update subscription
|
||||
// @Param id query string true "The id ( owner/name ) of the subscription"
|
||||
// @Param body body object.Subscription true "The details of the subscription"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-subscription [post]
|
||||
func (c *ApiController) UpdateSubscription() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
var subscription object.Subscription
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &subscription)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateSubscription(id, &subscription))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddSubscription
|
||||
// @Title AddSubscription
|
||||
// @Tag Subscription API
|
||||
// @Description add subscription
|
||||
// @Param body body object.Subscription true "The details of the subscription"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-subscription [post]
|
||||
func (c *ApiController) AddSubscription() {
|
||||
var subscription object.Subscription
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &subscription)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Check if plan restricts user to one subscription
|
||||
if subscription.Plan != "" {
|
||||
plan, err := object.GetPlan(util.GetId(subscription.Owner, subscription.Plan))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if plan != nil && plan.IsExclusive {
|
||||
hasSubscription, err := object.HasActiveSubscriptionForPlan(subscription.Owner, subscription.User, subscription.Plan)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if hasSubscription {
|
||||
c.ResponseError(fmt.Sprintf("User already has an active subscription for plan: %s", subscription.Plan))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddSubscription(&subscription))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteSubscription
|
||||
// @Title DeleteSubscription
|
||||
// @Tag Subscription API
|
||||
// @Description delete subscription
|
||||
// @Param body body object.Subscription true "The details of the subscription"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-subscription [post]
|
||||
func (c *ApiController) DeleteSubscription() {
|
||||
var subscription object.Subscription
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &subscription)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteSubscription(&subscription))
|
||||
c.ServeJSON()
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/beego/beego/v2/core/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetSyncers
|
||||
// @Title GetSyncers
|
||||
// @Tag Syncer API
|
||||
// @Description get syncers
|
||||
// @Param owner query string true "The owner of syncers"
|
||||
// @Success 200 {array} object.Syncer The Response object
|
||||
// @router /get-syncers [get]
|
||||
func (c *ApiController) GetSyncers() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
organization := c.Ctx.Input.Query("organization")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
syncers, err := object.GetMaskedSyncers(object.GetOrganizationSyncers(owner, organization))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(syncers)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetSyncerCount(owner, organization, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
syncers, err := object.GetMaskedSyncers(object.GetPaginationSyncers(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(syncers, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetSyncer
|
||||
// @Title GetSyncer
|
||||
// @Tag Syncer API
|
||||
// @Description get syncer
|
||||
// @Param id query string true "The id ( owner/name ) of the syncer"
|
||||
// @Success 200 {object} object.Syncer The Response object
|
||||
// @router /get-syncer [get]
|
||||
func (c *ApiController) GetSyncer() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
syncer, err := object.GetMaskedSyncer(object.GetSyncer(id))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(syncer)
|
||||
}
|
||||
|
||||
// UpdateSyncer
|
||||
// @Title UpdateSyncer
|
||||
// @Tag Syncer API
|
||||
// @Description update syncer
|
||||
// @Param id query string true "The id ( owner/name ) of the syncer"
|
||||
// @Param body body object.Syncer true "The details of the syncer"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-syncer [post]
|
||||
func (c *ApiController) UpdateSyncer() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
var syncer object.Syncer
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &syncer)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateSyncer(id, &syncer, c.IsGlobalAdmin(), c.GetAcceptLanguage()))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddSyncer
|
||||
// @Title AddSyncer
|
||||
// @Tag Syncer API
|
||||
// @Description add syncer
|
||||
// @Param body body object.Syncer true "The details of the syncer"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-syncer [post]
|
||||
func (c *ApiController) AddSyncer() {
|
||||
var syncer object.Syncer
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &syncer)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddSyncer(&syncer))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteSyncer
|
||||
// @Title DeleteSyncer
|
||||
// @Tag Syncer API
|
||||
// @Description delete syncer
|
||||
// @Param body body object.Syncer true "The details of the syncer"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-syncer [post]
|
||||
func (c *ApiController) DeleteSyncer() {
|
||||
var syncer object.Syncer
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &syncer)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteSyncer(&syncer))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// RunSyncer
|
||||
// @Title RunSyncer
|
||||
// @Tag Syncer API
|
||||
// @Description run syncer
|
||||
// @Param body body object.Syncer true "The details of the syncer"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /run-syncer [get]
|
||||
func (c *ApiController) RunSyncer() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
syncer, err := object.GetSyncer(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if syncer == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The syncer: %s does not exist"), id))
|
||||
return
|
||||
}
|
||||
|
||||
err = object.RunSyncer(syncer)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk()
|
||||
}
|
||||
|
||||
func (c *ApiController) TestSyncerDb() {
|
||||
var syncer object.Syncer
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &syncer)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = object.TestSyncer(syncer)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk()
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/go-git/go-git/v5"
|
||||
)
|
||||
|
||||
// GetSystemInfo
|
||||
// @Title GetSystemInfo
|
||||
// @Tag System API
|
||||
// @Description get system info like CPU and memory usage
|
||||
// @Success 200 {object} util.SystemInfo The Response object
|
||||
// @router /get-system-info [get]
|
||||
func (c *ApiController) GetSystemInfo() {
|
||||
_, ok := c.RequireAdmin()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
systemInfo, err := util.GetSystemInfo()
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(systemInfo)
|
||||
}
|
||||
|
||||
// GetVersionInfo
|
||||
// @Title GetVersionInfo
|
||||
// @Tag System API
|
||||
// @Description get version info like Casdoor release version and commit ID
|
||||
// @Success 200 {object} util.VersionInfo The Response object
|
||||
// @router /get-version-info [get]
|
||||
func (c *ApiController) GetVersionInfo() {
|
||||
versionInfo, err := util.GetVersionInfo()
|
||||
if err != nil && !errors.Is(err, git.ErrRepositoryNotExists) {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if versionInfo.Version != "" {
|
||||
c.ResponseOk(versionInfo)
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(util.GetBuiltInVersionInfo())
|
||||
}
|
||||
|
||||
// Health
|
||||
// @Title Health
|
||||
// @Tag System API
|
||||
// @Description check if the system is live
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /health [get]
|
||||
func (c *ApiController) Health() {
|
||||
c.ResponseOk()
|
||||
}
|
||||
@@ -1,271 +0,0 @@
|
||||
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/beego/beego/v2/core/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetTickets
|
||||
// @Title GetTickets
|
||||
// @Tag Ticket API
|
||||
// @Description get tickets
|
||||
// @Param owner query string true "The owner of tickets"
|
||||
// @Success 200 {array} object.Ticket The Response object
|
||||
// @router /get-tickets [get]
|
||||
func (c *ApiController) GetTickets() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
|
||||
user := c.getCurrentUser()
|
||||
isAdmin := c.IsAdmin()
|
||||
|
||||
var tickets []*object.Ticket
|
||||
var err error
|
||||
|
||||
if limit == "" || page == "" {
|
||||
if isAdmin {
|
||||
tickets, err = object.GetTickets(owner)
|
||||
} else {
|
||||
tickets, err = object.GetUserTickets(owner, user.GetId())
|
||||
}
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(tickets)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
var count int64
|
||||
|
||||
if isAdmin {
|
||||
count, err = object.GetTicketCount(owner, field, value)
|
||||
} else {
|
||||
// For non-admin users, only show their own tickets
|
||||
tickets, err = object.GetUserTickets(owner, user.GetId())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
count = int64(len(tickets))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
|
||||
if isAdmin {
|
||||
tickets, err = object.GetPaginationTickets(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(tickets, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetTicket
|
||||
// @Title GetTicket
|
||||
// @Tag Ticket API
|
||||
// @Description get ticket
|
||||
// @Param id query string true "The id ( owner/name ) of the ticket"
|
||||
// @Success 200 {object} object.Ticket The Response object
|
||||
// @router /get-ticket [get]
|
||||
func (c *ApiController) GetTicket() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
ticket, err := object.GetTicket(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Check permission: user can only view their own tickets unless they are admin
|
||||
user := c.getCurrentUser()
|
||||
isAdmin := c.IsAdmin()
|
||||
|
||||
if ticket != nil && !isAdmin && ticket.User != user.GetId() {
|
||||
c.ResponseError(c.T("auth:Unauthorized operation"))
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(ticket)
|
||||
}
|
||||
|
||||
// UpdateTicket
|
||||
// @Title UpdateTicket
|
||||
// @Tag Ticket API
|
||||
// @Description update ticket
|
||||
// @Param id query string true "The id ( owner/name ) of the ticket"
|
||||
// @Param body body object.Ticket true "The details of the ticket"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-ticket [post]
|
||||
func (c *ApiController) UpdateTicket() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
var ticket object.Ticket
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ticket)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Check permission
|
||||
user := c.getCurrentUser()
|
||||
isAdmin := c.IsAdmin()
|
||||
|
||||
existingTicket, err := object.GetTicket(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if existingTicket == nil {
|
||||
c.ResponseError(c.T("ticket:Ticket not found"))
|
||||
return
|
||||
}
|
||||
|
||||
// Normal users can only close their own tickets
|
||||
if !isAdmin {
|
||||
if existingTicket.User != user.GetId() {
|
||||
c.ResponseError(c.T("auth:Unauthorized operation"))
|
||||
return
|
||||
}
|
||||
// Normal users can only change state to "Closed"
|
||||
if ticket.State != "Closed" && ticket.State != existingTicket.State {
|
||||
c.ResponseError(c.T("auth:Unauthorized operation"))
|
||||
return
|
||||
}
|
||||
// Preserve original fields that users shouldn't modify
|
||||
ticket.Owner = existingTicket.Owner
|
||||
ticket.Name = existingTicket.Name
|
||||
ticket.User = existingTicket.User
|
||||
ticket.CreatedTime = existingTicket.CreatedTime
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateTicket(id, &ticket))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddTicket
|
||||
// @Title AddTicket
|
||||
// @Tag Ticket API
|
||||
// @Description add ticket
|
||||
// @Param body body object.Ticket true "The details of the ticket"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-ticket [post]
|
||||
func (c *ApiController) AddTicket() {
|
||||
var ticket object.Ticket
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ticket)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Set the user field to the current user
|
||||
user := c.getCurrentUser()
|
||||
ticket.User = user.GetId()
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddTicket(&ticket))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteTicket
|
||||
// @Title DeleteTicket
|
||||
// @Tag Ticket API
|
||||
// @Description delete ticket
|
||||
// @Param body body object.Ticket true "The details of the ticket"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-ticket [post]
|
||||
func (c *ApiController) DeleteTicket() {
|
||||
var ticket object.Ticket
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ticket)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Only admins can delete tickets
|
||||
if !c.IsAdmin() {
|
||||
c.ResponseError(c.T("auth:Unauthorized operation"))
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteTicket(&ticket))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddTicketMessage
|
||||
// @Title AddTicketMessage
|
||||
// @Tag Ticket API
|
||||
// @Description add a message to a ticket
|
||||
// @Param id query string true "The id ( owner/name ) of the ticket"
|
||||
// @Param body body object.TicketMessage true "The message to add"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-ticket-message [post]
|
||||
func (c *ApiController) AddTicketMessage() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
var message object.TicketMessage
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &message)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Check permission
|
||||
user := c.getCurrentUser()
|
||||
isAdmin := c.IsAdmin()
|
||||
|
||||
ticket, err := object.GetTicket(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if ticket == nil {
|
||||
c.ResponseError(c.T("ticket:Ticket not found"))
|
||||
return
|
||||
}
|
||||
|
||||
// Users can only add messages to their own tickets, admins can add to any ticket
|
||||
if !isAdmin && ticket.User != user.GetId() {
|
||||
c.ResponseError(c.T("auth:Unauthorized operation"))
|
||||
return
|
||||
}
|
||||
|
||||
// Set the author and admin flag
|
||||
message.Author = user.GetId()
|
||||
message.IsAdmin = isAdmin
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddTicketMessage(id, &message))
|
||||
c.ServeJSON()
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -16,102 +16,58 @@ package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/beego/beego/v2/core/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/casbin/casdoor/object"
|
||||
)
|
||||
|
||||
// GetTokens
|
||||
// @Title GetTokens
|
||||
// @Tag Token API
|
||||
// @Description get tokens
|
||||
// @Param owner query string true "The organization name (e.g., built-in)"
|
||||
// @Param pageSize query string true "The size of each page"
|
||||
// @Param p query string true "The number of the page"
|
||||
// @Param owner query string true "The owner of tokens"
|
||||
// @Success 200 {array} object.Token The Response object
|
||||
// @router /get-tokens [get]
|
||||
func (c *ApiController) GetTokens() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
organization := c.Ctx.Input.Query("organization")
|
||||
if limit == "" || page == "" {
|
||||
token, err := object.GetTokens(owner, organization)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
owner := c.Input().Get("owner")
|
||||
|
||||
c.ResponseOk(token)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetTokenCount(owner, organization, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
tokens, err := object.GetPaginationTokens(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(tokens, paginator.Nums())
|
||||
}
|
||||
c.Data["json"] = object.GetTokens(owner)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetToken
|
||||
// @Title GetToken
|
||||
// @Tag Token API
|
||||
// @Description get token
|
||||
// @Param id query string true "The token ID in format: organization/token-name (e.g., built-in/token-123456)"
|
||||
// @Param id query string true "The id of token"
|
||||
// @Success 200 {object} object.Token The Response object
|
||||
// @router /get-token [get]
|
||||
func (c *ApiController) GetToken() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
token, err := object.GetToken(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
id := c.Input().Get("id")
|
||||
|
||||
c.ResponseOk(token)
|
||||
c.Data["json"] = object.GetToken(id)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// UpdateToken
|
||||
// @Title UpdateToken
|
||||
// @Tag Token API
|
||||
// @Description update token
|
||||
// @Param id query string true "The token ID in format: organization/token-name (e.g., built-in/token-123456)"
|
||||
// @Param id query string true "The id of token"
|
||||
// @Param body body object.Token true "Details of the token"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-token [post]
|
||||
func (c *ApiController) UpdateToken() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
id := c.Input().Get("id")
|
||||
|
||||
var token object.Token
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &token)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateToken(id, &token, c.IsGlobalAdmin()))
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateToken(id, &token))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddToken
|
||||
// @Title AddToken
|
||||
// @Tag Token API
|
||||
// @Description add token
|
||||
// @Param body body object.Token true "Details of the token"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
@@ -120,8 +76,7 @@ func (c *ApiController) AddToken() {
|
||||
var token object.Token
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &token)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddToken(&token))
|
||||
@@ -129,7 +84,6 @@ func (c *ApiController) AddToken() {
|
||||
}
|
||||
|
||||
// DeleteToken
|
||||
// @Tag Token API
|
||||
// @Title DeleteToken
|
||||
// @Description delete token
|
||||
// @Param body body object.Token true "Details of the token"
|
||||
@@ -139,8 +93,7 @@ func (c *ApiController) DeleteToken() {
|
||||
var token object.Token
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &token)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteToken(&token))
|
||||
@@ -149,424 +102,19 @@ func (c *ApiController) DeleteToken() {
|
||||
|
||||
// GetOAuthToken
|
||||
// @Title GetOAuthToken
|
||||
// @Tag Token API
|
||||
// @Description get OAuth access token
|
||||
// @Param grant_type query string true "OAuth grant type"
|
||||
// @Param client_id query string true "OAuth client id"
|
||||
// @Param client_secret query string true "OAuth client secret"
|
||||
// @Param code query string true "OAuth code"
|
||||
// @Description get oAuth token
|
||||
// @Param grant_type query string true "oAuth grant type"
|
||||
// @Param client_id query string true "oAuth client id"
|
||||
// @Param client_secret query string true "oAuth client secret"
|
||||
// @Param code query string true "oAuth code"
|
||||
// @Success 200 {object} object.TokenWrapper The Response object
|
||||
// @Success 400 {object} object.TokenError The Response object
|
||||
// @Success 401 {object} object.TokenError The Response object
|
||||
// @router /login/oauth/access_token [post]
|
||||
func (c *ApiController) GetOAuthToken() {
|
||||
clientId := c.Ctx.Input.Query("client_id")
|
||||
clientSecret := c.Ctx.Input.Query("client_secret")
|
||||
assertion := c.Ctx.Input.Query("assertion")
|
||||
clientAssertion := c.Ctx.Input.Query("client_assertion")
|
||||
clientAssertionType := c.Ctx.Input.Query("client_assertion_type")
|
||||
grantType := c.Ctx.Input.Query("grant_type")
|
||||
code := c.Ctx.Input.Query("code")
|
||||
verifier := c.Ctx.Input.Query("code_verifier")
|
||||
scope := c.Ctx.Input.Query("scope")
|
||||
nonce := c.Ctx.Input.Query("nonce")
|
||||
username := c.Ctx.Input.Query("username")
|
||||
password := c.Ctx.Input.Query("password")
|
||||
tag := c.Ctx.Input.Query("tag")
|
||||
avatar := c.Ctx.Input.Query("avatar")
|
||||
refreshToken := c.Ctx.Input.Query("refresh_token")
|
||||
deviceCode := c.Ctx.Input.Query("device_code")
|
||||
subjectToken := c.Ctx.Input.Query("subject_token")
|
||||
subjectTokenType := c.Ctx.Input.Query("subject_token_type")
|
||||
audience := c.Ctx.Input.Query("audience")
|
||||
resource := c.Ctx.Input.Query("resource")
|
||||
grantType := c.Input().Get("grant_type")
|
||||
clientId := c.Input().Get("client_id")
|
||||
clientSecret := c.Input().Get("client_secret")
|
||||
code := c.Input().Get("code")
|
||||
|
||||
if clientId == "" && clientSecret == "" {
|
||||
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
|
||||
}
|
||||
|
||||
if len(c.Ctx.Input.RequestBody) != 0 && grantType != "urn:ietf:params:oauth:grant-type:device_code" {
|
||||
// If clientId is empty, try to read data from RequestBody
|
||||
var tokenRequest TokenRequest
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest)
|
||||
if err == nil {
|
||||
if clientId == "" {
|
||||
clientId = tokenRequest.ClientId
|
||||
}
|
||||
if clientSecret == "" {
|
||||
clientSecret = tokenRequest.ClientSecret
|
||||
}
|
||||
if clientAssertion == "" {
|
||||
clientAssertion = tokenRequest.ClientAssertion
|
||||
}
|
||||
if clientAssertionType == "" {
|
||||
clientAssertionType = tokenRequest.ClientAssertionType
|
||||
}
|
||||
if grantType == "" {
|
||||
grantType = tokenRequest.GrantType
|
||||
}
|
||||
if code == "" {
|
||||
code = tokenRequest.Code
|
||||
}
|
||||
if verifier == "" {
|
||||
verifier = tokenRequest.Verifier
|
||||
}
|
||||
if scope == "" {
|
||||
scope = tokenRequest.Scope
|
||||
}
|
||||
if nonce == "" {
|
||||
nonce = tokenRequest.Nonce
|
||||
}
|
||||
if username == "" {
|
||||
username = tokenRequest.Username
|
||||
}
|
||||
if password == "" {
|
||||
password = tokenRequest.Password
|
||||
}
|
||||
if tag == "" {
|
||||
tag = tokenRequest.Tag
|
||||
}
|
||||
if avatar == "" {
|
||||
avatar = tokenRequest.Avatar
|
||||
}
|
||||
if refreshToken == "" {
|
||||
refreshToken = tokenRequest.RefreshToken
|
||||
}
|
||||
if subjectToken == "" {
|
||||
subjectToken = tokenRequest.SubjectToken
|
||||
}
|
||||
if subjectTokenType == "" {
|
||||
subjectTokenType = tokenRequest.SubjectTokenType
|
||||
}
|
||||
if audience == "" {
|
||||
audience = tokenRequest.Audience
|
||||
}
|
||||
if resource == "" {
|
||||
resource = tokenRequest.Resource
|
||||
}
|
||||
if assertion == "" {
|
||||
assertion = tokenRequest.Assertion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract DPoP proof header (RFC 9449). Empty string when DPoP is not used.
|
||||
dpopProof := c.Ctx.Request.Header.Get("DPoP")
|
||||
|
||||
host := c.Ctx.Request.Host
|
||||
if deviceCode != "" {
|
||||
deviceAuthCache, ok := object.DeviceAuthMap.Load(deviceCode)
|
||||
if !ok {
|
||||
c.Data["json"] = &object.TokenError{
|
||||
Error: "expired_token",
|
||||
ErrorDescription: "token is expired",
|
||||
}
|
||||
c.SetTokenErrorHttpStatus()
|
||||
c.ServeJSON()
|
||||
c.SetTokenErrorHttpStatus()
|
||||
return
|
||||
}
|
||||
|
||||
deviceAuthCacheCast := deviceAuthCache.(object.DeviceAuthCache)
|
||||
if !deviceAuthCacheCast.UserSignIn {
|
||||
c.Data["json"] = &object.TokenError{
|
||||
Error: "authorization_pending",
|
||||
ErrorDescription: "authorization pending",
|
||||
}
|
||||
c.SetTokenErrorHttpStatus()
|
||||
c.ServeJSON()
|
||||
c.SetTokenErrorHttpStatus()
|
||||
return
|
||||
}
|
||||
|
||||
if deviceAuthCacheCast.RequestAt.Add(time.Second * 120).Before(time.Now()) {
|
||||
c.Data["json"] = &object.TokenError{
|
||||
Error: "expired_token",
|
||||
ErrorDescription: "token is expired",
|
||||
}
|
||||
c.SetTokenErrorHttpStatus()
|
||||
c.ServeJSON()
|
||||
c.SetTokenErrorHttpStatus()
|
||||
return
|
||||
}
|
||||
object.DeviceAuthMap.Delete(deviceCode)
|
||||
|
||||
username = deviceAuthCacheCast.UserName
|
||||
}
|
||||
|
||||
token, err := object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, nonce, username, password, host, refreshToken, tag, avatar, c.GetAcceptLanguage(), subjectToken, subjectTokenType, assertion, clientAssertion, clientAssertionType, audience, resource, dpopProof)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = token
|
||||
c.SetTokenErrorHttpStatus()
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// RefreshToken
|
||||
// @Title RefreshToken
|
||||
// @Tag Token API
|
||||
// @Description refresh OAuth access token
|
||||
// @Param grant_type query string true "OAuth grant type"
|
||||
// @Param refresh_token query string true "OAuth refresh token"
|
||||
// @Param scope query string true "OAuth scope"
|
||||
// @Param client_id query string true "OAuth client id"
|
||||
// @Param client_secret query string false "OAuth client secret"
|
||||
// @Success 200 {object} object.TokenWrapper The Response object
|
||||
// @Success 400 {object} object.TokenError The Response object
|
||||
// @Success 401 {object} object.TokenError The Response object
|
||||
// @router /login/oauth/refresh_token [post]
|
||||
func (c *ApiController) RefreshToken() {
|
||||
grantType := c.Ctx.Input.Query("grant_type")
|
||||
refreshToken := c.Ctx.Input.Query("refresh_token")
|
||||
scope := c.Ctx.Input.Query("scope")
|
||||
clientId := c.Ctx.Input.Query("client_id")
|
||||
clientSecret := c.Ctx.Input.Query("client_secret")
|
||||
host := c.Ctx.Request.Host
|
||||
|
||||
if clientId == "" {
|
||||
// If clientID is empty, try to read data from RequestBody
|
||||
var tokenRequest TokenRequest
|
||||
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest); err == nil {
|
||||
clientId = tokenRequest.ClientId
|
||||
clientSecret = tokenRequest.ClientSecret
|
||||
grantType = tokenRequest.GrantType
|
||||
scope = tokenRequest.Scope
|
||||
refreshToken = tokenRequest.RefreshToken
|
||||
}
|
||||
}
|
||||
|
||||
ok, application, clientId, _, err := c.ValidateOAuth(true)
|
||||
if err != nil || !ok {
|
||||
return
|
||||
}
|
||||
|
||||
dpopProof := c.Ctx.Request.Header.Get("DPoP")
|
||||
refreshToken2, err := object.RefreshToken(application, grantType, refreshToken, scope, clientId, clientSecret, host, dpopProof)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = refreshToken2
|
||||
c.SetTokenErrorHttpStatus()
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) ResponseTokenError(errorMsg string, errorDescription string) {
|
||||
c.Data["json"] = &object.TokenError{
|
||||
Error: errorMsg,
|
||||
ErrorDescription: errorDescription,
|
||||
}
|
||||
c.SetTokenErrorHttpStatus()
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) ValidateOAuth(ignoreValidSecret bool) (ok bool, application *object.Application, clientId, clientSecret string, err error) {
|
||||
reqClientId := c.Ctx.Input.Query("client_id")
|
||||
reqClientSecret := c.Ctx.Input.Query("client_secret")
|
||||
clientAssertion := c.Ctx.Input.Query("client_assertion")
|
||||
clientAssertionType := c.Ctx.Input.Query("client_assertion_type")
|
||||
|
||||
if reqClientId == "" && clientAssertionType == "" {
|
||||
var tokenRequest TokenRequest
|
||||
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest); err == nil {
|
||||
reqClientId = tokenRequest.ClientId
|
||||
reqClientSecret = tokenRequest.ClientSecret
|
||||
clientAssertion = tokenRequest.ClientAssertion
|
||||
clientAssertionType = tokenRequest.ClientAssertionType
|
||||
}
|
||||
}
|
||||
|
||||
if clientAssertionType == "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" {
|
||||
ok, application, err = object.ValidateClientAssertion(clientAssertion, c.Ctx.Request.Host)
|
||||
if err != nil {
|
||||
c.ResponseTokenError(object.InvalidClient, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if !ok || application == nil {
|
||||
c.ResponseTokenError(object.InvalidClient, "client_assertion is invalid")
|
||||
return
|
||||
}
|
||||
|
||||
clientSecret = application.ClientSecret
|
||||
clientId = application.ClientId
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
if reqClientId == "" && reqClientSecret == "" {
|
||||
clientId, clientSecret, ok = c.Ctx.Request.BasicAuth()
|
||||
if !ok {
|
||||
clientId = c.Ctx.Input.Query("client_id")
|
||||
clientSecret = c.Ctx.Input.Query("client_secret")
|
||||
if clientId == "" || clientSecret == "" {
|
||||
c.ResponseTokenError(object.InvalidRequest, "")
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
clientId = reqClientId
|
||||
clientSecret = reqClientSecret
|
||||
}
|
||||
|
||||
application, err = object.GetApplicationByClientId(clientId)
|
||||
if err != nil {
|
||||
c.ResponseTokenError(object.InvalidClient, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if application == nil || (application.ClientSecret != clientSecret && !ignoreValidSecret) {
|
||||
c.ResponseTokenError(object.InvalidClient, c.T("token:Invalid application or wrong clientSecret"))
|
||||
return
|
||||
}
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// IntrospectToken
|
||||
// @Title IntrospectToken
|
||||
// @Tag Login API
|
||||
// @Description The introspection endpoint is an OAuth 2.0 endpoint that takes a
|
||||
// parameter representing an OAuth 2.0 token and returns a JSON document
|
||||
// representing the meta information surrounding the
|
||||
// token, including whether this token is currently active.
|
||||
// This endpoint support Basic Authorization and authorization defined in RFC 7523.
|
||||
//
|
||||
// @Param token formData string true "access_token's value or refresh_token's value"
|
||||
// @Param token_type_hint formData string true "the token type access_token or refresh_token"
|
||||
// @Success 200 {object} object.IntrospectionResponse The Response object
|
||||
// @Success 400 {object} object.TokenError The Response object
|
||||
// @Success 401 {object} object.TokenError The Response object
|
||||
// @router /login/oauth/introspect [post]
|
||||
func (c *ApiController) IntrospectToken() {
|
||||
tokenValue := c.Ctx.Input.Query("token")
|
||||
|
||||
ok, application, clientId, _, err := c.ValidateOAuth(false)
|
||||
if err != nil || !ok {
|
||||
return
|
||||
}
|
||||
|
||||
respondWithInactiveToken := func() {
|
||||
c.Data["json"] = &object.IntrospectionResponse{Active: false}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
tokenTypeHint := c.Ctx.Input.Query("token_type_hint")
|
||||
var token *object.Token
|
||||
if tokenTypeHint != "" {
|
||||
token, err = object.GetTokenByTokenValue(tokenValue, tokenTypeHint)
|
||||
if err != nil {
|
||||
c.ResponseTokenError(object.InvalidRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if token == nil || token.ExpiresIn <= 0 {
|
||||
respondWithInactiveToken()
|
||||
return
|
||||
}
|
||||
|
||||
if token.ExpiresIn <= 0 {
|
||||
c.Data["json"] = &object.IntrospectionResponse{Active: false}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var introspectionResponse object.IntrospectionResponse
|
||||
|
||||
if application.TokenFormat == "JWT-Standard" {
|
||||
jwtToken, err := object.ParseStandardJwtTokenByApplication(tokenValue, application)
|
||||
if err != nil {
|
||||
// and token revoked case. but we not implement
|
||||
// TODO: 2022-03-03 add token revoked check, when we implemented the Token Revocation(rfc7009) Specs.
|
||||
// refs: https://tools.ietf.org/html/rfc7009
|
||||
respondWithInactiveToken()
|
||||
return
|
||||
}
|
||||
|
||||
introspectionResponse = object.IntrospectionResponse{
|
||||
Active: true,
|
||||
Scope: jwtToken.Scope,
|
||||
ClientId: clientId,
|
||||
Username: jwtToken.Name,
|
||||
TokenType: jwtToken.TokenType,
|
||||
Exp: jwtToken.ExpiresAt.Unix(),
|
||||
Iat: jwtToken.IssuedAt.Unix(),
|
||||
Nbf: jwtToken.NotBefore.Unix(),
|
||||
Sub: jwtToken.Subject,
|
||||
Aud: jwtToken.Audience,
|
||||
Iss: jwtToken.Issuer,
|
||||
Jti: jwtToken.ID,
|
||||
}
|
||||
} else {
|
||||
jwtToken, err := object.ParseJwtTokenByApplication(tokenValue, application)
|
||||
if err != nil {
|
||||
// and token revoked case. but we not implement
|
||||
// TODO: 2022-03-03 add token revoked check, when we implemented the Token Revocation(rfc7009) Specs.
|
||||
// refs: https://tools.ietf.org/html/rfc7009
|
||||
respondWithInactiveToken()
|
||||
return
|
||||
}
|
||||
|
||||
introspectionResponse = object.IntrospectionResponse{
|
||||
Active: true,
|
||||
ClientId: clientId,
|
||||
Exp: jwtToken.ExpiresAt.Unix(),
|
||||
Iat: jwtToken.IssuedAt.Unix(),
|
||||
Nbf: jwtToken.NotBefore.Unix(),
|
||||
Sub: jwtToken.Subject,
|
||||
Aud: jwtToken.Audience,
|
||||
Iss: jwtToken.Issuer,
|
||||
Jti: jwtToken.ID,
|
||||
}
|
||||
|
||||
if jwtToken.Scope != "" {
|
||||
introspectionResponse.Scope = jwtToken.Scope
|
||||
}
|
||||
if jwtToken.Name != "" {
|
||||
introspectionResponse.Username = jwtToken.Name
|
||||
}
|
||||
if jwtToken.TokenType != "" {
|
||||
introspectionResponse.TokenType = jwtToken.TokenType
|
||||
}
|
||||
}
|
||||
|
||||
if tokenTypeHint == "" {
|
||||
token, err = object.GetTokenByTokenValue(tokenValue, introspectionResponse.TokenType)
|
||||
if err != nil {
|
||||
c.ResponseTokenError(object.InvalidRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if token == nil || token.ExpiresIn <= 0 {
|
||||
respondWithInactiveToken()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if token != nil {
|
||||
application, err = object.GetApplication(fmt.Sprintf("%s/%s", token.Owner, token.Application))
|
||||
if err != nil {
|
||||
c.ResponseTokenError(object.InvalidClient, err.Error())
|
||||
return
|
||||
}
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), token.Application))
|
||||
return
|
||||
}
|
||||
|
||||
introspectionResponse.TokenType = token.TokenType
|
||||
introspectionResponse.ClientId = application.ClientId
|
||||
|
||||
// Expose DPoP key binding in the introspection response (RFC 9449 §8).
|
||||
if token.DPoPJkt != "" {
|
||||
introspectionResponse.Cnf = &object.DPoPConfirmation{JKT: token.DPoPJkt}
|
||||
}
|
||||
}
|
||||
|
||||
c.Data["json"] = introspectionResponse
|
||||
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
@@ -1,213 +0,0 @@
|
||||
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/beego/beego/v2/core/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetTransactions
|
||||
// @Title GetTransactions
|
||||
// @Tag Transaction API
|
||||
// @Description get transactions
|
||||
// @Param owner query string true "The owner of transactions"
|
||||
// @Success 200 {array} object.Transaction The Response object
|
||||
// @router /get-transactions [get]
|
||||
func (c *ApiController) GetTransactions() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
var transactions []*object.Transaction
|
||||
var err error
|
||||
|
||||
if c.IsAdmin() {
|
||||
// If field is "user", filter by that user even for admins
|
||||
if field == "user" && value != "" {
|
||||
transactions, err = object.GetUserTransactions(owner, value)
|
||||
} else {
|
||||
transactions, err = object.GetTransactions(owner)
|
||||
}
|
||||
} else {
|
||||
user := c.GetSessionUsername()
|
||||
_, userName, userErr := util.GetOwnerAndNameFromIdWithError(user)
|
||||
if userErr != nil {
|
||||
c.ResponseError(userErr.Error())
|
||||
return
|
||||
}
|
||||
transactions, err = object.GetUserTransactions(owner, userName)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(transactions)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
|
||||
// Apply user filter for non-admin users
|
||||
if !c.IsAdmin() {
|
||||
user := c.GetSessionUsername()
|
||||
_, userName, userErr := util.GetOwnerAndNameFromIdWithError(user)
|
||||
if userErr != nil {
|
||||
c.ResponseError(userErr.Error())
|
||||
return
|
||||
}
|
||||
field = "user"
|
||||
value = userName
|
||||
}
|
||||
|
||||
count, err := object.GetTransactionCount(owner, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
transactions, err := object.GetPaginationTransactions(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(transactions, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetTransaction
|
||||
// @Title GetTransaction
|
||||
// @Tag Transaction API
|
||||
// @Description get transaction
|
||||
// @Param id query string true "The id ( owner/name ) of the transaction"
|
||||
// @Success 200 {object} object.Transaction The Response object
|
||||
// @router /get-transaction [get]
|
||||
func (c *ApiController) GetTransaction() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
transaction, err := object.GetTransaction(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if transaction == nil {
|
||||
c.ResponseOk(nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if non-admin user is trying to access someone else's transaction
|
||||
if !c.IsAdmin() {
|
||||
user := c.GetSessionUsername()
|
||||
_, userName, userErr := util.GetOwnerAndNameFromIdWithError(user)
|
||||
if userErr != nil {
|
||||
c.ResponseError(userErr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Only allow users to view their own transactions
|
||||
if transaction.User != userName {
|
||||
c.ResponseError(c.T("auth:Unauthorized operation"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.ResponseOk(transaction)
|
||||
}
|
||||
|
||||
// UpdateTransaction
|
||||
// @Title UpdateTransaction
|
||||
// @Tag Transaction API
|
||||
// @Description update transaction
|
||||
// @Param id query string true "The id ( owner/name ) of the transaction"
|
||||
// @Param body body object.Transaction true "The details of the transaction"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-transaction [post]
|
||||
func (c *ApiController) UpdateTransaction() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
var transaction object.Transaction
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &transaction)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateTransaction(id, &transaction, c.GetAcceptLanguage()))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddTransaction
|
||||
// @Title AddTransaction
|
||||
// @Tag Transaction API
|
||||
// @Description add transaction
|
||||
// @Param body body object.Transaction true "The details of the transaction"
|
||||
// @Param dryRun query string false "Dry run mode: set to 'true' or '1' to validate without committing"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-transaction [post]
|
||||
func (c *ApiController) AddTransaction() {
|
||||
var transaction object.Transaction
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &transaction)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
dryRunParam := c.Ctx.Input.Query("dryRun")
|
||||
dryRun := dryRunParam != ""
|
||||
|
||||
affected, transactionId, err := object.AddTransaction(&transaction, c.GetAcceptLanguage(), dryRun)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if !affected {
|
||||
c.Data["json"] = wrapActionResponse(false)
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(transactionId)
|
||||
}
|
||||
|
||||
// DeleteTransaction
|
||||
// @Title DeleteTransaction
|
||||
// @Tag Transaction API
|
||||
// @Description delete transaction
|
||||
// @Param body body object.Transaction true "The details of the transaction"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-transaction [post]
|
||||
func (c *ApiController) DeleteTransaction() {
|
||||
var transaction object.Transaction
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &transaction)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteTransaction(&transaction, c.GetAcceptLanguage()))
|
||||
c.ServeJSON()
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
type TokenRequest struct {
|
||||
Assertion string `json:"assertion"`
|
||||
ClientId string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
ClientAssertion string `json:"client_assertion"`
|
||||
ClientAssertionType string `json:"client_assertion_type"`
|
||||
GrantType string `json:"grant_type"`
|
||||
Code string `json:"code"`
|
||||
Verifier string `json:"code_verifier"`
|
||||
Scope string `json:"scope"`
|
||||
Nonce string `json:"nonce"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Tag string `json:"tag"`
|
||||
Avatar string `json:"avatar"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
SubjectToken string `json:"subject_token"`
|
||||
SubjectTokenType string `json:"subject_token_type"`
|
||||
Audience string `json:"audience"`
|
||||
Resource string `json:"resource"` // RFC 8707 Resource Indicator
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,87 +0,0 @@
|
||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func saveFile(path string, file *multipart.File) (err error) {
|
||||
f, err := os.Create(filepath.Clean(path))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = io.Copy(f, *file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ApiController) UploadUsers() {
|
||||
if !c.IsAdmin() {
|
||||
c.ResponseError(c.T("auth:Unauthorized operation"))
|
||||
return
|
||||
}
|
||||
|
||||
userObj := c.getCurrentUser()
|
||||
if userObj == nil {
|
||||
c.ResponseError(c.T("auth:Unauthorized operation"))
|
||||
return
|
||||
}
|
||||
|
||||
userId := c.GetSessionUsername()
|
||||
owner, user, err := util.GetOwnerAndNameFromIdWithError(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
file, header, err := c.Ctx.Request.FormFile("file")
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fileId := fmt.Sprintf("%s_%s_%s", owner, user, util.RemoveExt(header.Filename))
|
||||
path := util.GetUploadXlsxPath(fileId)
|
||||
defer os.Remove(path)
|
||||
err = saveFile(path, &file)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
affected, err := object.UploadUsers(owner, path, userObj, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if affected {
|
||||
c.ResponseOk()
|
||||
} else {
|
||||
c.ResponseError(c.T("general:Failed to import users"))
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -15,18 +15,17 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"strconv"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/i18n"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/util"
|
||||
)
|
||||
|
||||
// ResponseJsonData ...
|
||||
func (c *ApiController) ResponseJsonData(resp *Response, data ...interface{}) {
|
||||
// ResponseOk ...
|
||||
func (c *ApiController) ResponseOk(data ...interface{}) {
|
||||
resp := Response{Status: "ok"}
|
||||
switch len(data) {
|
||||
case 2:
|
||||
resp.Data2 = data[1]
|
||||
@@ -38,280 +37,66 @@ func (c *ApiController) ResponseJsonData(resp *Response, data ...interface{}) {
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// ResponseOk ...
|
||||
func (c *ApiController) ResponseOk(data ...interface{}) {
|
||||
resp := &Response{Status: "ok"}
|
||||
c.ResponseJsonData(resp, data...)
|
||||
}
|
||||
|
||||
// ResponseError ...
|
||||
func (c *ApiController) ResponseError(error string, data ...interface{}) {
|
||||
enableErrorMask2 := conf.GetConfigBool("enableErrorMask2")
|
||||
if enableErrorMask2 {
|
||||
error = c.T("subscription:Error")
|
||||
|
||||
resp := &Response{Status: "error", Msg: error}
|
||||
c.ResponseJsonData(resp, data...)
|
||||
return
|
||||
}
|
||||
|
||||
resp := &Response{Status: "error", Msg: error}
|
||||
c.ResponseJsonData(resp, data...)
|
||||
}
|
||||
|
||||
func (c *ApiController) T(error string) string {
|
||||
return i18n.Translate(c.GetAcceptLanguage(), error)
|
||||
}
|
||||
|
||||
// GetAcceptLanguage ...
|
||||
func (c *ApiController) GetAcceptLanguage() string {
|
||||
language := c.Ctx.Request.Header.Get("Accept-Language")
|
||||
if len(language) > 2 {
|
||||
language = language[0:2]
|
||||
}
|
||||
return conf.GetLanguage(language)
|
||||
}
|
||||
|
||||
// SetTokenErrorHttpStatus ...
|
||||
func (c *ApiController) SetTokenErrorHttpStatus() {
|
||||
_, ok := c.Data["json"].(*object.TokenError)
|
||||
if ok {
|
||||
if c.Data["json"].(*object.TokenError).Error == object.InvalidClient {
|
||||
c.Ctx.Output.SetStatus(401)
|
||||
c.Ctx.Output.Header("WWW-Authenticate", "Basic realm=\"OAuth2\"")
|
||||
} else {
|
||||
c.Ctx.Output.SetStatus(400)
|
||||
}
|
||||
}
|
||||
_, ok = c.Data["json"].(*object.TokenWrapper)
|
||||
if ok {
|
||||
c.Ctx.Output.SetStatus(200)
|
||||
resp := Response{Status: "error", Msg: error}
|
||||
switch len(data) {
|
||||
case 2:
|
||||
resp.Data2 = data[1]
|
||||
fallthrough
|
||||
case 1:
|
||||
resp.Data = data[0]
|
||||
}
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// RequireSignedIn ...
|
||||
func (c *ApiController) RequireSignedIn() (string, bool) {
|
||||
userId := c.GetSessionUsername()
|
||||
if userId == "" {
|
||||
c.ResponseError(c.T("general:Please login first"), "Please login first")
|
||||
c.ResponseError("Please sign in first")
|
||||
return "", false
|
||||
}
|
||||
return userId, true
|
||||
}
|
||||
|
||||
// RequireSignedInUser ...
|
||||
func (c *ApiController) RequireSignedInUser() (*object.User, bool) {
|
||||
userId, ok := c.RequireSignedIn()
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if object.IsAppUser(userId) {
|
||||
tmpUserId := c.Ctx.Input.Query("userId")
|
||||
if tmpUserId != "" {
|
||||
userId = tmpUserId
|
||||
}
|
||||
}
|
||||
|
||||
user, err := object.GetUser(userId)
|
||||
func getInitScore() int {
|
||||
score, err := strconv.Atoi(beego.AppConfig.String("initScore"))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return nil, false
|
||||
}
|
||||
if user == nil {
|
||||
c.ClearUserSession()
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), userId))
|
||||
return nil, false
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return user, true
|
||||
return score
|
||||
}
|
||||
|
||||
// RequireAdmin ...
|
||||
func (c *ApiController) RequireAdmin() (string, bool) {
|
||||
user, ok := c.RequireSignedInUser()
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
|
||||
if user.Owner == "built-in" {
|
||||
return "", true
|
||||
}
|
||||
|
||||
if !user.IsAdmin {
|
||||
c.ResponseError(c.T("general:this operation requires administrator to perform"))
|
||||
return "", false
|
||||
}
|
||||
|
||||
return user.Owner, true
|
||||
}
|
||||
|
||||
func (c *ApiController) IsOrgAdmin() (bool, bool) {
|
||||
userId, ok := c.RequireSignedIn()
|
||||
if !ok {
|
||||
return false, true
|
||||
}
|
||||
|
||||
if object.IsAppUser(userId) {
|
||||
return true, true
|
||||
}
|
||||
|
||||
user, err := object.GetUser(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return false, false
|
||||
}
|
||||
if user == nil {
|
||||
c.ClearUserSession()
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), userId))
|
||||
return false, false
|
||||
}
|
||||
|
||||
return user.IsAdmin, true
|
||||
}
|
||||
|
||||
// IsMaskedEnabled ...
|
||||
func (c *ApiController) IsMaskedEnabled() (bool, bool) {
|
||||
isMaskEnabled := true
|
||||
withSecret := c.Ctx.Input.Query("withSecret")
|
||||
if withSecret == "1" {
|
||||
isMaskEnabled = false
|
||||
|
||||
if conf.IsDemoMode() {
|
||||
c.ResponseError(c.T("general:this operation is not allowed in demo mode"))
|
||||
return false, isMaskEnabled
|
||||
}
|
||||
|
||||
_, ok := c.RequireAdmin()
|
||||
if !ok {
|
||||
return false, isMaskEnabled
|
||||
}
|
||||
}
|
||||
|
||||
return true, isMaskEnabled
|
||||
}
|
||||
|
||||
func refineFullFilePath(fullFilePath string) (string, string) {
|
||||
tokens := strings.Split(fullFilePath, "/")
|
||||
if len(tokens) >= 2 && tokens[0] == "Direct" && tokens[1] != "" {
|
||||
providerName := tokens[1]
|
||||
res := strings.Join(tokens[2:], "/")
|
||||
return providerName, "/" + res
|
||||
} else {
|
||||
return "", fullFilePath
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ApiController) GetProviderFromContext(category string) (*object.Provider, error) {
|
||||
providerName := c.Ctx.Input.Query("provider")
|
||||
if providerName == "" {
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
if field == "provider" && value != "" {
|
||||
providerName = value
|
||||
} else {
|
||||
fullFilePath := c.Ctx.Input.Query("fullFilePath")
|
||||
providerName, _ = refineFullFilePath(fullFilePath)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ApiController) GetProviderFromContext(category string) (*object.Provider, *object.User, bool) {
|
||||
providerName := c.Input().Get("provider")
|
||||
if providerName != "" {
|
||||
provider, err := object.GetProvider(util.GetId("admin", providerName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
provider := object.GetProvider(util.GetId(providerName))
|
||||
if provider == nil {
|
||||
err = fmt.Errorf(c.T("util:The provider: %s is not found"), providerName)
|
||||
return nil, err
|
||||
c.ResponseError(fmt.Sprintf("The provider: %s is not found", providerName))
|
||||
return nil, nil, false
|
||||
}
|
||||
|
||||
return provider, nil
|
||||
return provider, nil, true
|
||||
}
|
||||
|
||||
userId, ok := c.RequireSignedIn()
|
||||
if !ok {
|
||||
return nil, errors.New(c.T("general:Please login first"))
|
||||
}
|
||||
|
||||
application, err := object.GetApplicationByUserId(userId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, false
|
||||
}
|
||||
|
||||
application, user := object.GetApplicationByUserId(userId)
|
||||
if application == nil {
|
||||
return nil, fmt.Errorf(c.T("util:No application is found for userId: %s"), userId)
|
||||
}
|
||||
|
||||
provider, err := application.GetProviderByCategory(category)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
c.ResponseError(fmt.Sprintf("No application is found for userId: \"%s\"", userId))
|
||||
return nil, nil, false
|
||||
}
|
||||
|
||||
provider := application.GetProviderByCategory(category)
|
||||
if provider == nil {
|
||||
return nil, fmt.Errorf(c.T("util:No provider for category: %s is found for application: %s"), category, application.Name)
|
||||
c.ResponseError(fmt.Sprintf("No provider for category: \"%s\" is found for application: %s", category, application.Name))
|
||||
return nil, nil, false
|
||||
}
|
||||
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
func checkQuotaForApplication(count int) error {
|
||||
quota := conf.GetConfigQuota().Application
|
||||
if quota == -1 {
|
||||
return nil
|
||||
}
|
||||
if count >= quota {
|
||||
return fmt.Errorf("application quota is exceeded")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkQuotaForOrganization(count int) error {
|
||||
quota := conf.GetConfigQuota().Organization
|
||||
if quota == -1 {
|
||||
return nil
|
||||
}
|
||||
if count >= quota {
|
||||
return fmt.Errorf("organization quota is exceeded")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkQuotaForProvider(count int) error {
|
||||
quota := conf.GetConfigQuota().Provider
|
||||
if quota == -1 {
|
||||
return nil
|
||||
}
|
||||
if count >= quota {
|
||||
return fmt.Errorf("provider quota is exceeded")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkQuotaForUser() error {
|
||||
quota := conf.GetConfigQuota().User
|
||||
if quota == -1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
count, err := object.GetUserCount("", "", "", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if int(count) >= quota {
|
||||
return fmt.Errorf("user quota is exceeded")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getInvalidSmsReceivers(smsForm SmsForm) []string {
|
||||
var invalidReceivers []string
|
||||
for _, receiver := range smsForm.Receivers {
|
||||
// The receiver phone format: E164 like +8613854673829 +441932567890
|
||||
if !util.IsPhoneValid(receiver, "") {
|
||||
invalidReceivers = append(invalidReceivers, receiver)
|
||||
}
|
||||
}
|
||||
return invalidReceivers
|
||||
return provider, user, true
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -15,606 +15,138 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/beego/beego/v2/core/utils/pagination"
|
||||
"github.com/casdoor/casdoor/captcha"
|
||||
"github.com/casdoor/casdoor/form"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/util"
|
||||
)
|
||||
|
||||
const (
|
||||
SignupVerification = "signup"
|
||||
ResetVerification = "reset"
|
||||
LoginVerification = "login"
|
||||
ForgetVerification = "forget"
|
||||
MfaSetupVerification = "mfaSetup"
|
||||
MfaAuthVerification = "mfaAuth"
|
||||
)
|
||||
|
||||
// GetVerifications
|
||||
// @Title GetVerifications
|
||||
// @Tag Verification API
|
||||
// @Description get payments
|
||||
// @Param owner query string true "The owner of payments"
|
||||
// @Success 200 {array} object.Verification The Response object
|
||||
// @router /get-payments [get]
|
||||
func (c *ApiController) GetVerifications() {
|
||||
organization, ok := c.RequireAdmin()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
limit := c.Ctx.Input.Query("pageSize")
|
||||
page := c.Ctx.Input.Query("p")
|
||||
field := c.Ctx.Input.Query("field")
|
||||
value := c.Ctx.Input.Query("value")
|
||||
sortField := c.Ctx.Input.Query("sortField")
|
||||
sortOrder := c.Ctx.Input.Query("sortOrder")
|
||||
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
// For global admin with organizationName parameter, use it to filter
|
||||
// For org admin, use their organization
|
||||
if c.IsGlobalAdmin() && owner != "" {
|
||||
organization = owner
|
||||
}
|
||||
|
||||
if limit == "" || page == "" {
|
||||
payments, err := object.GetVerifications(organization)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(payments)
|
||||
func (c *ApiController) getCurrentUser() *object.User {
|
||||
var user *object.User
|
||||
userId := c.GetSessionUsername()
|
||||
if userId == "" {
|
||||
user = nil
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetVerificationCount(organization, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.NewPaginator(c.Ctx.Request, limit, count)
|
||||
payments, err := object.GetPaginationVerifications(organization, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(payments, paginator.Nums())
|
||||
user = object.GetUser(userId)
|
||||
}
|
||||
}
|
||||
|
||||
// GetUserVerifications
|
||||
// @Title GetUserVerifications
|
||||
// @Tag Verification API
|
||||
// @Description get payments for a user
|
||||
// @Param owner query string true "The owner of payments"
|
||||
// @Param organization query string true "The organization of the user"
|
||||
// @Param user query string true "The username of the user"
|
||||
// @Success 200 {array} object.Verification The Response object
|
||||
// @router /get-user-payments [get]
|
||||
func (c *ApiController) GetUserVerifications() {
|
||||
owner := c.Ctx.Input.Query("owner")
|
||||
user := c.Ctx.Input.Query("user")
|
||||
|
||||
payments, err := object.GetUserVerifications(owner, user)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(payments)
|
||||
}
|
||||
|
||||
// GetVerification
|
||||
// @Title GetVerification
|
||||
// @Tag Verification API
|
||||
// @Description get payment
|
||||
// @Param id query string true "The id ( owner/name ) of the payment"
|
||||
// @Success 200 {object} object.Verification The Response object
|
||||
// @router /get-payment [get]
|
||||
func (c *ApiController) GetVerification() {
|
||||
id := c.Ctx.Input.Query("id")
|
||||
|
||||
payment, err := object.GetVerification(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(payment)
|
||||
return user
|
||||
}
|
||||
|
||||
// SendVerificationCode ...
|
||||
// @Title SendVerificationCode
|
||||
// @Tag Verification API
|
||||
// @router /send-verification-code [post]
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
func (c *ApiController) SendVerificationCode() {
|
||||
var vform form.VerificationForm
|
||||
err := c.ParseForm(&vform)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
destType := c.Ctx.Request.Form.Get("type")
|
||||
dest := c.Ctx.Request.Form.Get("dest")
|
||||
orgId := c.Ctx.Request.Form.Get("organizationId")
|
||||
checkType := c.Ctx.Request.Form.Get("checkType")
|
||||
checkId := c.Ctx.Request.Form.Get("checkId")
|
||||
checkKey := c.Ctx.Request.Form.Get("checkKey")
|
||||
remoteAddr := util.GetIPFromRequest(c.Ctx.Request)
|
||||
|
||||
if len(destType) == 0 || len(dest) == 0 || len(orgId) == 0 || !strings.Contains(orgId, "/") || len(checkType) == 0 || len(checkId) == 0 || len(checkKey) == 0 {
|
||||
c.ResponseError("Missing parameter.")
|
||||
return
|
||||
}
|
||||
|
||||
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
|
||||
isHuman := false
|
||||
captchaProvider := object.GetDefaultHumanCheckProvider()
|
||||
if captchaProvider == nil {
|
||||
isHuman = object.VerifyCaptcha(checkId, checkKey)
|
||||
}
|
||||
|
||||
if msg := vform.CheckParameter(form.SendVerifyCode, c.GetAcceptLanguage()); msg != "" {
|
||||
c.ResponseError(msg)
|
||||
if !isHuman {
|
||||
c.ResponseError("Turing test failed.")
|
||||
return
|
||||
}
|
||||
|
||||
application, err := object.GetApplication(vform.ApplicationId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), vform.ApplicationId))
|
||||
return
|
||||
}
|
||||
|
||||
// Check if "Forgot password?" signin item is visible when using forget verification
|
||||
if vform.Method == ForgetVerification {
|
||||
isForgotPasswordEnabled := false
|
||||
for _, item := range application.SigninItems {
|
||||
if item.Name == "Forgot password?" {
|
||||
isForgotPasswordEnabled = item.Visible
|
||||
break
|
||||
}
|
||||
}
|
||||
// Block access if the signin item is not found or is explicitly hidden
|
||||
if !isForgotPasswordEnabled {
|
||||
c.ResponseError(c.T("verification:The forgot password feature is disabled"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
organization, err := object.GetOrganization(util.GetId(application.Owner, application.Organization))
|
||||
if err != nil {
|
||||
c.ResponseError(c.T(err.Error()))
|
||||
}
|
||||
|
||||
if organization == nil {
|
||||
c.ResponseError(c.T("check:Organization does not exist"))
|
||||
return
|
||||
}
|
||||
|
||||
var user *object.User
|
||||
// Try to resolve user for CAPTCHA rule checking
|
||||
// checkUser != "", means method is ForgetVerification
|
||||
if vform.CheckUser != "" {
|
||||
owner := application.Organization
|
||||
user, err = object.GetUser(util.GetId(owner, vform.CheckUser))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if user == nil || user.IsDeleted {
|
||||
c.ResponseError(c.T("verification:the user does not exist, please sign up first"))
|
||||
return
|
||||
}
|
||||
|
||||
if user.IsForbidden {
|
||||
c.ResponseError(c.T("check:The user is forbidden to sign in, please contact the administrator"))
|
||||
return
|
||||
}
|
||||
} else if mfaUserSession := c.getMfaUserSession(); mfaUserSession != "" {
|
||||
// mfaUserSession != "", means method is MfaAuthVerification
|
||||
user, err = object.GetUser(mfaUserSession)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
} else if vform.Method == ResetVerification {
|
||||
// For reset verification, get the current logged-in user
|
||||
user = c.getCurrentUser()
|
||||
} else if vform.Method == LoginVerification {
|
||||
// For login verification, try to find user by email/phone for CAPTCHA check
|
||||
// This is a preliminary lookup; the actual validation happens later in the switch statement
|
||||
if vform.Type == object.VerifyTypeEmail && util.IsEmailValid(vform.Dest) {
|
||||
user, err = object.GetUserByEmail(organization.Name, vform.Dest)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
} else if vform.Type == object.VerifyTypePhone {
|
||||
// Prefer resolving the user directly by phone, consistent with the later login switch,
|
||||
// so that Dynamic CAPTCHA is not skipped due to missing/invalid country code.
|
||||
user, err = object.GetUserByPhone(organization.Name, vform.Dest)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine username for CAPTCHA check
|
||||
username := ""
|
||||
if user != nil {
|
||||
username = user.Name
|
||||
} else if vform.CheckUser != "" {
|
||||
username = vform.CheckUser
|
||||
}
|
||||
|
||||
// Check if CAPTCHA should be enabled based on the rule (Dynamic/Always/Internet-Only)
|
||||
enableCaptcha, err := object.CheckToEnableCaptcha(application, organization.Name, username, clientIp)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if vform.CaptchaToken != "" {
|
||||
enableCaptcha = true
|
||||
}
|
||||
|
||||
// Only verify CAPTCHA if it should be enabled
|
||||
if enableCaptcha {
|
||||
captchaProvider, err := object.GetCaptchaProviderByApplication(vform.ApplicationId, "false", c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if captchaProvider != nil {
|
||||
if vform.CaptchaType != captchaProvider.Type {
|
||||
c.ResponseError(c.T("verification:Turing test failed."))
|
||||
return
|
||||
}
|
||||
|
||||
if captchaProvider.Type != "Default" {
|
||||
vform.ClientSecret = captchaProvider.ClientSecret
|
||||
}
|
||||
|
||||
if vform.CaptchaType != "none" {
|
||||
if captchaService := captcha.GetCaptchaProvider(vform.CaptchaType); captchaService == nil {
|
||||
c.ResponseError(c.T("general:don't support captchaProvider: ") + vform.CaptchaType)
|
||||
return
|
||||
} else if isHuman, err := captchaService.VerifyCaptcha(vform.CaptchaToken, captchaProvider.ClientId, vform.ClientSecret, captchaProvider.ClientId2); err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
} else if !isHuman {
|
||||
c.ResponseError(c.T("verification:Turing test failed."))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sendResp := errors.New("invalid dest type")
|
||||
var provider *object.Provider
|
||||
|
||||
switch vform.Type {
|
||||
case object.VerifyTypeEmail:
|
||||
if !util.IsEmailValid(vform.Dest) {
|
||||
c.ResponseError(c.T("check:Email is invalid"))
|
||||
return
|
||||
}
|
||||
|
||||
if vform.Method == LoginVerification || vform.Method == ForgetVerification {
|
||||
if user != nil && util.GetMaskedEmail(user.Email) == vform.Dest {
|
||||
vform.Dest = user.Email
|
||||
}
|
||||
|
||||
user, err = object.GetUserByEmail(organization.Name, vform.Dest)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
c.ResponseError(c.T("verification:the user does not exist, please sign up first"))
|
||||
return
|
||||
}
|
||||
} else if vform.Method == ResetVerification {
|
||||
user = c.getCurrentUser()
|
||||
} else if vform.Method == MfaAuthVerification {
|
||||
mfaProps := user.GetMfaProps(object.EmailType, false)
|
||||
if user != nil && util.GetMaskedEmail(mfaProps.Secret) == vform.Dest {
|
||||
vform.Dest = mfaProps.Secret
|
||||
}
|
||||
}
|
||||
|
||||
provider, err = application.GetEmailProvider(vform.Method)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if provider == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("verification:please add an Email provider to the \"Providers\" list for the application: %s"), application.Name))
|
||||
return
|
||||
}
|
||||
|
||||
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, clientIp, vform.Dest, vform.Method, c.Ctx.Request.Host, application.Name, application)
|
||||
case object.VerifyTypePhone:
|
||||
if vform.Method == LoginVerification || vform.Method == ForgetVerification {
|
||||
if user != nil && util.GetMaskedPhone(user.Phone) == vform.Dest {
|
||||
vform.Dest = user.Phone
|
||||
}
|
||||
|
||||
if user, err = object.GetUserByPhone(organization.Name, vform.Dest); err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
} else if user == nil {
|
||||
c.ResponseError(c.T("verification:the user does not exist, please sign up first"))
|
||||
return
|
||||
}
|
||||
|
||||
vform.CountryCode = user.GetCountryCode(vform.CountryCode)
|
||||
} else if vform.Method == ResetVerification || vform.Method == MfaSetupVerification {
|
||||
if vform.CountryCode == "" {
|
||||
if user = c.getCurrentUser(); user != nil {
|
||||
vform.CountryCode = user.GetCountryCode(vform.CountryCode)
|
||||
}
|
||||
}
|
||||
} else if vform.Method == MfaAuthVerification {
|
||||
mfaProps := user.GetMfaProps(object.SmsType, false)
|
||||
if user != nil && util.GetMaskedPhone(mfaProps.Secret) == vform.Dest {
|
||||
vform.Dest = mfaProps.Secret
|
||||
}
|
||||
|
||||
vform.CountryCode = mfaProps.CountryCode
|
||||
vform.CountryCode = user.GetCountryCode(vform.CountryCode)
|
||||
}
|
||||
|
||||
provider, err = application.GetSmsProvider(vform.Method, vform.CountryCode)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if provider == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("verification:please add a SMS provider to the \"Providers\" list for the application: %s"), application.Name))
|
||||
return
|
||||
}
|
||||
|
||||
if phone, ok := util.GetE164Number(vform.Dest, vform.CountryCode); !ok {
|
||||
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), vform.CountryCode))
|
||||
return
|
||||
} else {
|
||||
sendResp = object.SendVerificationCodeToPhone(organization, user, provider, clientIp, phone, application)
|
||||
}
|
||||
user := c.getCurrentUser()
|
||||
organization := object.GetOrganization(orgId)
|
||||
application := object.GetApplicationByOrganizationName(organization.Name)
|
||||
|
||||
sendResp := errors.New("Invalid dest type.")
|
||||
switch destType {
|
||||
case "email":
|
||||
if !util.IsEmailValid(dest) {
|
||||
c.ResponseError("Invalid Email address")
|
||||
return
|
||||
}
|
||||
|
||||
provider := application.GetEmailProvider()
|
||||
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest)
|
||||
case "phone":
|
||||
if !util.IsPhoneCnValid(dest) {
|
||||
c.ResponseError("Invalid phone number")
|
||||
return
|
||||
}
|
||||
org := object.GetOrganization(orgId)
|
||||
if org == nil {
|
||||
c.ResponseError("Missing parameter.")
|
||||
return
|
||||
}
|
||||
|
||||
dest = fmt.Sprintf("+%s%s", org.PhonePrefix, dest)
|
||||
provider := application.GetSmsProvider()
|
||||
sendResp = object.SendVerificationCodeToPhone(organization, user, provider, remoteAddr, dest)
|
||||
}
|
||||
|
||||
status := "ok"
|
||||
if sendResp != nil {
|
||||
c.ResponseError(sendResp.Error())
|
||||
} else {
|
||||
c.ResponseOk()
|
||||
}
|
||||
}
|
||||
|
||||
// VerifyCaptcha ...
|
||||
// @Title VerifyCaptcha
|
||||
// @Tag Verification API
|
||||
// @router /verify-captcha [post]
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
func (c *ApiController) VerifyCaptcha() {
|
||||
var vform form.VerificationForm
|
||||
err := c.ParseForm(&vform)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
status = "error"
|
||||
}
|
||||
|
||||
if msg := vform.CheckParameter(form.VerifyCaptcha, c.GetAcceptLanguage()); msg != "" {
|
||||
c.ResponseError(msg)
|
||||
return
|
||||
}
|
||||
|
||||
captchaProvider, err := object.GetCaptchaProviderByOwnerName(vform.ApplicationId, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if captchaProvider.Type != "Default" {
|
||||
vform.ClientSecret = captchaProvider.ClientSecret
|
||||
}
|
||||
|
||||
provider := captcha.GetCaptchaProvider(vform.CaptchaType)
|
||||
if provider == nil {
|
||||
c.ResponseError(c.T("verification:Invalid captcha provider."))
|
||||
return
|
||||
}
|
||||
|
||||
isValid, err := provider.VerifyCaptcha(vform.CaptchaToken, captchaProvider.ClientId, vform.ClientSecret, captchaProvider.ClientId2)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(isValid)
|
||||
c.Data["json"] = Response{Status: status, Msg: sendResp.Error()}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// ResetEmailOrPhone ...
|
||||
// @Tag Account API
|
||||
// @Title ResetEmailOrPhone
|
||||
// @router /reset-email-or-phone [post]
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
func (c *ApiController) ResetEmailOrPhone() {
|
||||
user, ok := c.RequireSignedInUser()
|
||||
userId, ok := c.RequireSignedIn()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
|
||||
user := object.GetUser(userId)
|
||||
if user == nil {
|
||||
c.ResponseError("No such user.")
|
||||
return
|
||||
}
|
||||
|
||||
destType := c.Ctx.Request.Form.Get("type")
|
||||
dest := c.Ctx.Request.Form.Get("dest")
|
||||
code := c.Ctx.Request.Form.Get("code")
|
||||
|
||||
if util.IsStringsEmpty(destType, dest, code) {
|
||||
c.ResponseError(c.T("general:Missing parameter"))
|
||||
if len(dest) == 0 || len(code) == 0 || len(destType) == 0 {
|
||||
c.ResponseError("Missing parameter.")
|
||||
return
|
||||
}
|
||||
|
||||
checkDest := dest
|
||||
organization, err := object.GetOrganizationByUser(user)
|
||||
if err != nil {
|
||||
c.ResponseError(c.T(err.Error()))
|
||||
return
|
||||
if destType == "phone" {
|
||||
org := object.GetOrganizationByUser(user)
|
||||
phonePrefix := "86"
|
||||
if org != nil && org.PhonePrefix != "" {
|
||||
phonePrefix = org.PhonePrefix
|
||||
}
|
||||
checkDest = fmt.Sprintf("+%s%s", phonePrefix, dest)
|
||||
}
|
||||
|
||||
if destType == object.VerifyTypePhone {
|
||||
if object.HasUserByField(user.Owner, "phone", dest) {
|
||||
c.ResponseError(c.T("check:Phone already exists"))
|
||||
return
|
||||
}
|
||||
|
||||
phoneItem := object.GetAccountItemByName("Phone", organization)
|
||||
if phoneItem == nil {
|
||||
c.ResponseError(c.T("verification:Unable to get the phone modify rule."))
|
||||
return
|
||||
}
|
||||
|
||||
if pass, errMsg := object.CheckAccountItemModifyRule(phoneItem, user.IsAdminUser(), c.GetAcceptLanguage()); !pass {
|
||||
c.ResponseError(errMsg)
|
||||
return
|
||||
}
|
||||
if checkDest, ok = util.GetE164Number(dest, user.GetCountryCode("")); !ok {
|
||||
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), user.CountryCode))
|
||||
return
|
||||
}
|
||||
} else if destType == object.VerifyTypeEmail {
|
||||
if object.HasUserByField(user.Owner, "email", dest) {
|
||||
c.ResponseError(c.T("check:Email already exists"))
|
||||
return
|
||||
}
|
||||
|
||||
emailItem := object.GetAccountItemByName("Email", organization)
|
||||
if emailItem == nil {
|
||||
c.ResponseError(c.T("verification:Unable to get the email modify rule."))
|
||||
return
|
||||
}
|
||||
|
||||
if pass, errMsg := object.CheckAccountItemModifyRule(emailItem, user.IsAdminUser(), c.GetAcceptLanguage()); !pass {
|
||||
c.ResponseError(errMsg)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = object.CheckVerifyCodeWithLimitAndIp(user, clientIp, checkDest, code, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
if ret := object.CheckVerificationCode(checkDest, code); len(ret) != 0 {
|
||||
c.ResponseError(ret)
|
||||
return
|
||||
}
|
||||
|
||||
switch destType {
|
||||
case object.VerifyTypeEmail:
|
||||
id := user.GetId()
|
||||
case "email":
|
||||
user.Email = dest
|
||||
user.EmailVerified = true
|
||||
columns := []string{"email", "email_verified"}
|
||||
if organization.UseEmailAsUsername {
|
||||
user.Name = user.Email
|
||||
columns = append(columns, "name")
|
||||
}
|
||||
_, err = object.UpdateUser(id, user, columns, false)
|
||||
case object.VerifyTypePhone:
|
||||
object.SetUserField(user, "email", user.Email)
|
||||
case "phone":
|
||||
user.Phone = dest
|
||||
_, err = object.SetUserField(user, "phone", user.Phone)
|
||||
object.SetUserField(user, "phone", user.Phone)
|
||||
default:
|
||||
c.ResponseError(c.T("verification:Unknown type"))
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if organization.UseEmailAsUsername {
|
||||
c.SetSessionUsername(user.GetId())
|
||||
}
|
||||
|
||||
err = object.DisableVerificationCode(checkDest)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
c.ResponseError("Unknown type.")
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk()
|
||||
}
|
||||
|
||||
// VerifyCode
|
||||
// @Tag Verification API
|
||||
// @Title VerifyCode
|
||||
// @router /verify-code [post]
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
func (c *ApiController) VerifyCode() {
|
||||
var authForm form.AuthForm
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &authForm)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var user *object.User
|
||||
if authForm.Name != "" {
|
||||
user, err = object.GetUserByFields(authForm.Organization, authForm.Name)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var checkDest string
|
||||
if strings.Contains(authForm.Username, "@") {
|
||||
if user != nil && util.GetMaskedEmail(user.Email) == authForm.Username {
|
||||
authForm.Username = user.Email
|
||||
}
|
||||
checkDest = authForm.Username
|
||||
} else {
|
||||
if user != nil && util.GetMaskedPhone(user.Phone) == authForm.Username {
|
||||
authForm.Username = user.Phone
|
||||
}
|
||||
}
|
||||
|
||||
if user, err = object.GetUserByFields(authForm.Organization, authForm.Username); err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
} else if user == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(authForm.Organization, authForm.Username)))
|
||||
return
|
||||
}
|
||||
|
||||
verificationCodeType := object.GetVerifyType(authForm.Username)
|
||||
if verificationCodeType == object.VerifyTypePhone {
|
||||
authForm.CountryCode = user.GetCountryCode(authForm.CountryCode)
|
||||
var ok bool
|
||||
if checkDest, ok = util.GetE164Number(authForm.Username, authForm.CountryCode); !ok {
|
||||
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), authForm.CountryCode))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
passed, err := c.checkOrgMasterVerificationCode(user, authForm.Code)
|
||||
if err != nil {
|
||||
c.ResponseError(c.T(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if !passed {
|
||||
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
|
||||
err = object.CheckVerifyCodeWithLimitAndIp(user, clientIp, checkDest, authForm.Code, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = object.DisableVerificationCode(checkDest)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.SetSession("verifiedCode", authForm.Code)
|
||||
c.SetSession("verifiedUserId", user.GetId())
|
||||
c.ResponseOk()
|
||||
object.DisableVerificationCode(checkDest)
|
||||
c.Data["json"] = Response{Status: "ok"}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
// Copyright 2025 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
)
|
||||
|
||||
func (c *ApiController) checkOrgMasterVerificationCode(user *object.User, code string) (bool, error) {
|
||||
organization, err := object.GetOrganizationByUser(user)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if organization == nil {
|
||||
return false, fmt.Errorf("The organization: %s does not exist", user.Owner)
|
||||
}
|
||||
|
||||
if organization.MasterVerificationCode != "" && organization.MasterVerificationCode == code {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
@@ -1,236 +0,0 @@
|
||||
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/casdoor/casdoor/form"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/go-webauthn/webauthn/protocol"
|
||||
"github.com/go-webauthn/webauthn/webauthn"
|
||||
)
|
||||
|
||||
// WebAuthnSignupBegin
|
||||
// @Title WebAuthnSignupBegin
|
||||
// @Tag User API
|
||||
// @Description WebAuthn Registration Flow 1st stage
|
||||
// @Success 200 {object} protocol.CredentialCreation The CredentialCreationOptions object
|
||||
// @router /webauthn/signup/begin [get]
|
||||
func (c *ApiController) WebAuthnSignupBegin() {
|
||||
webauthnObj, err := object.GetWebAuthnObject(c.Ctx.Request.Host)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
user := c.getCurrentUser()
|
||||
if user == nil {
|
||||
c.ResponseError(c.T("general:Please login first"))
|
||||
return
|
||||
}
|
||||
|
||||
registerOptions := func(credCreationOpts *protocol.PublicKeyCredentialCreationOptions) {
|
||||
credCreationOpts.CredentialExcludeList = user.CredentialExcludeList()
|
||||
credCreationOpts.AuthenticatorSelection.ResidentKey = "preferred"
|
||||
credCreationOpts.Attestation = "none"
|
||||
|
||||
ext := map[string]interface{}{
|
||||
"credProps": true,
|
||||
}
|
||||
credCreationOpts.Extensions = ext
|
||||
}
|
||||
options, sessionData, err := webauthnObj.BeginRegistration(
|
||||
user,
|
||||
registerOptions,
|
||||
)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
c.SetSession("registration", *sessionData)
|
||||
c.Data["json"] = options
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// WebAuthnSignupFinish
|
||||
// @Title WebAuthnSignupFinish
|
||||
// @Tag User API
|
||||
// @Description WebAuthn Registration Flow 2nd stage
|
||||
// @Param body body protocol.CredentialCreationResponse true "authenticator attestation Response"
|
||||
// @Success 200 {object} controllers.Response "The Response object"
|
||||
// @router /webauthn/signup/finish [post]
|
||||
func (c *ApiController) WebAuthnSignupFinish() {
|
||||
webauthnObj, err := object.GetWebAuthnObject(c.Ctx.Request.Host)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
user := c.getCurrentUser()
|
||||
if user == nil {
|
||||
c.ResponseError(c.T("general:Please login first"))
|
||||
return
|
||||
}
|
||||
sessionObj := c.GetSession("registration")
|
||||
sessionData, ok := sessionObj.(webauthn.SessionData)
|
||||
if !ok {
|
||||
c.ResponseError(c.T("webauthn:Please call WebAuthnSigninBegin first"))
|
||||
return
|
||||
}
|
||||
c.Ctx.Request.Body = io.NopCloser(bytes.NewBuffer(c.Ctx.Input.RequestBody))
|
||||
|
||||
credential, err := webauthnObj.FinishRegistration(user, sessionData, c.Ctx.Request)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
isGlobalAdmin := c.IsGlobalAdmin()
|
||||
_, err = user.AddCredentials(*credential, isGlobalAdmin)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk()
|
||||
}
|
||||
|
||||
// WebAuthnSigninBegin
|
||||
// @Title WebAuthnSigninBegin
|
||||
// @Tag Login API
|
||||
// @Description WebAuthn Login Flow 1st stage
|
||||
// @Param owner query string true "owner"
|
||||
// @Param name query string true "name"
|
||||
// @Success 200 {object} protocol.CredentialAssertion The CredentialAssertion object
|
||||
// @router /webauthn/signin/begin [get]
|
||||
func (c *ApiController) WebAuthnSigninBegin() {
|
||||
webauthnObj, err := object.GetWebAuthnObject(c.Ctx.Request.Host)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
userOwner := c.Ctx.Input.Query("owner")
|
||||
userName := c.Ctx.Input.Query("name")
|
||||
|
||||
var options *protocol.CredentialAssertion
|
||||
var sessionData *webauthn.SessionData
|
||||
|
||||
if userName == "" {
|
||||
options, sessionData, err = webauthnObj.BeginDiscoverableLogin()
|
||||
} else {
|
||||
var user *object.User
|
||||
user, err = object.GetUserByFields(userOwner, userName)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(userOwner, userName)))
|
||||
return
|
||||
}
|
||||
if len(user.WebauthnCredentials) == 0 {
|
||||
c.ResponseError(c.T("webauthn:Found no credentials for this user"))
|
||||
return
|
||||
}
|
||||
|
||||
options, sessionData, err = webauthnObj.BeginLogin(user)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
c.SetSession("authentication", *sessionData)
|
||||
c.Data["json"] = options
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// WebAuthnSigninFinish
|
||||
// @Title WebAuthnSigninFinish
|
||||
// @Tag Login API
|
||||
// @Description WebAuthn Login Flow 2nd stage
|
||||
// @Param body body protocol.CredentialAssertionResponse true "authenticator assertion Response"
|
||||
// @Success 200 {object} controllers.Response "The Response object"
|
||||
// @router /webauthn/signin/finish [post]
|
||||
func (c *ApiController) WebAuthnSigninFinish() {
|
||||
responseType := c.Ctx.Input.Query("responseType")
|
||||
clientId := c.Ctx.Input.Query("clientId")
|
||||
webauthnObj, err := object.GetWebAuthnObject(c.Ctx.Request.Host)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
sessionObj := c.GetSession("authentication")
|
||||
sessionData, ok := sessionObj.(webauthn.SessionData)
|
||||
if !ok {
|
||||
c.ResponseError(c.T("webauthn:Please call WebAuthnSigninBegin first"))
|
||||
return
|
||||
}
|
||||
c.Ctx.Request.Body = io.NopCloser(bytes.NewBuffer(c.Ctx.Input.RequestBody))
|
||||
|
||||
var user *object.User
|
||||
if sessionData.UserID != nil {
|
||||
userId := string(sessionData.UserID)
|
||||
user, err = object.GetUser(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
_, err = webauthnObj.FinishLogin(user, sessionData, c.Ctx.Request)
|
||||
} else {
|
||||
handler := func(rawID, userHandle []byte) (webauthn.User, error) {
|
||||
user, err = object.GetUserByWebauthID(base64.StdEncoding.EncodeToString(rawID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
_, err = webauthnObj.FinishDiscoverableLogin(handler, sessionData, c.Ctx.Request)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
c.SetSessionUsername(user.GetId())
|
||||
util.LogInfo(c.Ctx, "API: [%s] signed in", user.GetId())
|
||||
|
||||
var application *object.Application
|
||||
|
||||
if clientId != "" && (responseType == ResponseTypeCode) {
|
||||
application, err = object.GetApplicationByClientId(clientId)
|
||||
} else {
|
||||
application, err = object.GetApplicationByUser(user)
|
||||
}
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var authForm form.AuthForm
|
||||
authForm.Type = responseType
|
||||
resp := c.HandleLoggedIn(application, user, &authForm)
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user