mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-17 16:53:50 +08:00
Compare commits
110 Commits
Author | SHA1 | Date | |
---|---|---|---|
9a324b2cca | |||
919eaf1df4 | |||
cd902a21ba | |||
fe0ab0aa6f | |||
a0e11cc8a0 | |||
8a66448365 | |||
477d386f3c | |||
339c6c2dd0 | |||
7c9370ef90 | |||
31b586e391 | |||
249f83e764 | |||
16f5569e50 | |||
f99c1f44e8 | |||
c8c4dfbfb8 | |||
d9c6ff2507 | |||
e1664f2f60 | |||
460a4d4969 | |||
376bac15dc | |||
8d0e92edef | |||
0075b7af52 | |||
2c57bece39 | |||
2e42511bc4 | |||
ae4ab9902b | |||
065b235dc5 | |||
63c09a879f | |||
61c80e790f | |||
be91ff47aa | |||
b4c18eb7a4 | |||
0f483fb65b | |||
ebe9889d58 | |||
ee42fcac8e | |||
6187b48f61 | |||
2020955270 | |||
1b5a8f8e57 | |||
ff94e5164a | |||
15a6fd2b52 | |||
37b6b50751 | |||
efe5431f54 | |||
e9159902eb | |||
604e2757c8 | |||
88c5aae9e9 | |||
3d0cf8788b | |||
e78ea2546f | |||
f7705931f7 | |||
5d8b710bf7 | |||
b85ad896bf | |||
42c2210178 | |||
d52caed3a9 | |||
27d8cd758d | |||
98f77960de | |||
e5b71a08ae | |||
3ad4b7a43c | |||
c5c3a08aa9 | |||
8efd964835 | |||
5dac87a4c3 | |||
49c3266400 | |||
39548d5d72 | |||
1c949e415e | |||
1b840a2e9f | |||
c9849d8b55 | |||
b747f5e27c | |||
8b340105c1 | |||
43b1006f11 | |||
78efc9c2d0 | |||
c4089eacb7 | |||
4acba2d493 | |||
fc0ca4cceb | |||
912d9d0c01 | |||
8e48bddf5f | |||
c05fb77224 | |||
9af9ead939 | |||
f5590c42f7 | |||
5597f99e3c | |||
ea005aaf4d | |||
e5c1f560c5 | |||
20fc7d1b58 | |||
cf3b46130b | |||
cab51fae9c | |||
b867872da4 | |||
305867f49a | |||
3f90c18a19 | |||
9e5a64c021 | |||
4263af6f2c | |||
3e92d761b9 | |||
0e41568f62 | |||
fb7e2729c6 | |||
28b9154d7e | |||
b0b3eb0805 | |||
73bd9dd517 | |||
0bc8c2d15f | |||
7b78e60265 | |||
7464f9a8ad | |||
d3a7a062d3 | |||
67a0264411 | |||
a6a055cc83 | |||
a89a7f9eb7 | |||
287f60353c | |||
530330bd66 | |||
70a1428972 | |||
1d183decea | |||
b92d03e2bb | |||
9877174780 | |||
b178be9aef | |||
7236cca8cf | |||
15daf5dbfe | |||
0b546bba5e | |||
938cdbccf4 | |||
801302c6e7 | |||
91602d2b21 | |||
86b3a078ef |
5
.github/workflows/build.yml
vendored
5
.github/workflows/build.yml
vendored
@ -70,7 +70,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Fetch Previous version
|
- name: Fetch Previous version
|
||||||
id: get-previous-tag
|
id: get-previous-tag
|
||||||
uses: actions-ecosystem/action-get-latest-tag@v1
|
uses: actions-ecosystem/action-get-latest-tag@v1.6.0
|
||||||
|
|
||||||
- name: Release
|
- name: Release
|
||||||
run: yarn global add semantic-release@17.4.4 && semantic-release
|
run: yarn global add semantic-release@17.4.4 && semantic-release
|
||||||
@ -79,7 +79,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Fetch Current version
|
- name: Fetch Current version
|
||||||
id: get-current-tag
|
id: get-current-tag
|
||||||
uses: actions-ecosystem/action-get-latest-tag@v1
|
uses: actions-ecosystem/action-get-latest-tag@v1.6.0
|
||||||
|
|
||||||
- name: Decide Should_Push Or Not
|
- name: Decide Should_Push Or Not
|
||||||
id: should_push
|
id: should_push
|
||||||
@ -114,6 +114,7 @@ jobs:
|
|||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v2
|
||||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||||
with:
|
with:
|
||||||
|
target: STANDARD
|
||||||
push: true
|
push: true
|
||||||
tags: casbin/casdoor:${{steps.get-current-tag.outputs.tag }},casbin/casdoor:latest
|
tags: casbin/casdoor:${{steps.get-current-tag.outputs.tag }},casbin/casdoor:latest
|
||||||
|
|
||||||
|
2
.github/workflows/sync.yml
vendored
2
.github/workflows/sync.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
|||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: crowdin action
|
- name: crowdin action
|
||||||
uses: crowdin/github-action@1.2.0
|
uses: crowdin/github-action@1.4.8
|
||||||
with:
|
with:
|
||||||
upload_translations: true
|
upload_translations: true
|
||||||
|
|
||||||
|
63
Dockerfile
63
Dockerfile
@ -1,9 +1,3 @@
|
|||||||
FROM golang:1.17.5 AS BACK
|
|
||||||
WORKDIR /go/src/casdoor
|
|
||||||
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
|
|
||||||
|
|
||||||
FROM node:16.13.0 AS FRONT
|
FROM node:16.13.0 AS FRONT
|
||||||
WORKDIR /web
|
WORKDIR /web
|
||||||
COPY ./web .
|
COPY ./web .
|
||||||
@ -11,28 +5,43 @@ RUN yarn config set registry https://registry.npmmirror.com
|
|||||||
RUN yarn install && yarn run build
|
RUN yarn install && yarn run build
|
||||||
|
|
||||||
|
|
||||||
FROM debian:latest AS ALLINONE
|
FROM golang:1.17.5 AS BACK
|
||||||
RUN apt update
|
WORKDIR /go/src/casdoor
|
||||||
RUN apt install -y ca-certificates && update-ca-certificates
|
COPY . .
|
||||||
RUN apt install -y mariadb-server mariadb-client && mkdir -p web/build && chmod 777 /tmp
|
RUN ./build.sh
|
||||||
LABEL MAINTAINER="https://casdoor.org/"
|
|
||||||
COPY --from=BACK /go/src/casdoor/ ./
|
|
||||||
COPY --from=BACK /usr/bin/wait-for-it ./
|
|
||||||
COPY --from=FRONT /web/build /web/build
|
|
||||||
CMD chmod 777 /tmp && service mariadb start&&\
|
|
||||||
if [ "${MYSQL_ROOT_PASSWORD}" = "" ] ;then MYSQL_ROOT_PASSWORD=123456 ; fi&&\
|
|
||||||
mysqladmin -u root password ${MYSQL_ROOT_PASSWORD} &&\
|
|
||||||
./wait-for-it localhost:3306 -- ./server --createDatabase=true
|
|
||||||
|
|
||||||
|
|
||||||
FROM alpine:latest
|
FROM alpine:latest AS STANDARD
|
||||||
RUN sed -i 's/https/http/' /etc/apk/repositories
|
|
||||||
RUN apk add curl
|
|
||||||
RUN apk add ca-certificates && update-ca-certificates
|
|
||||||
LABEL MAINTAINER="https://casdoor.org/"
|
LABEL MAINTAINER="https://casdoor.org/"
|
||||||
|
|
||||||
COPY --from=BACK /go/src/casdoor/ ./
|
WORKDIR /app
|
||||||
COPY --from=BACK /usr/bin/wait-for-it ./
|
COPY --from=BACK /go/src/casdoor/server ./server
|
||||||
RUN mkdir -p web/build && apk add --no-cache bash coreutils
|
COPY --from=BACK /go/src/casdoor/swagger ./swagger
|
||||||
COPY --from=FRONT /web/build /web/build
|
COPY --from=BACK /go/src/casdoor/conf/app.conf ./conf/app.conf
|
||||||
CMD ./server
|
COPY --from=FRONT /web/build ./web/build
|
||||||
|
VOLUME /app/files /app/logs
|
||||||
|
ENTRYPOINT ["/app/server"]
|
||||||
|
|
||||||
|
|
||||||
|
FROM debian:latest AS db
|
||||||
|
RUN apt update \
|
||||||
|
&& apt install -y \
|
||||||
|
mariadb-server \
|
||||||
|
mariadb-client \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
|
||||||
|
FROM db AS ALLINONE
|
||||||
|
LABEL MAINTAINER="https://casdoor.org/"
|
||||||
|
|
||||||
|
ENV MYSQL_ROOT_PASSWORD=123456
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=BACK /go/src/casdoor/server ./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"]
|
||||||
|
152
README.md
152
README.md
@ -42,166 +42,66 @@
|
|||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Online demo
|
## Online demo
|
||||||
|
|
||||||
Deployed site: https://door.casdoor.com/
|
- International: https://door.casdoor.org (read-only)
|
||||||
|
- Asian mirror: https://door.casdoor.com (read-only)
|
||||||
|
- Asian mirror: https://demo.casdoor.com (read-write, will restore for every 5 minutes)
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
Run your own casdoor program in a few minutes.
|
|
||||||
|
|
||||||
### Download
|
|
||||||
|
|
||||||
There are two methods, get code via go subcommand `get`:
|
## Documentation
|
||||||
|
|
||||||
```shell
|
- International: https://casdoor.org
|
||||||
go get github.com/casdoor/casdoor
|
- Asian mirror: https://docs.casdoor.cn
|
||||||
```
|
|
||||||
|
|
||||||
or `git`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/casdoor/casdoor
|
|
||||||
```
|
|
||||||
|
|
||||||
Finally, change directory:
|
## Install
|
||||||
|
|
||||||
```bash
|
- By source code: https://casdoor.org/docs/basic/server-installation
|
||||||
cd casdoor/
|
- By Docker: https://casdoor.org/docs/basic/try-with-docker
|
||||||
```
|
|
||||||
|
|
||||||
We provide two start up methods for all kinds of users.
|
|
||||||
|
|
||||||
### Manual
|
|
||||||
|
|
||||||
#### Simple configuration
|
## How to connect to Casdoor?
|
||||||
Casdoor requires a running Relational database to be operational.Thus you need to modify configuration to point out the location of database.
|
|
||||||
|
|
||||||
Edit `conf/app.conf`, modify `dataSourceName` to correct database info, which follows this format:
|
https://casdoor.org/docs/how-to-connect/overview
|
||||||
|
|
||||||
```bash
|
|
||||||
username:password@tcp(database_ip:database_port)/
|
|
||||||
```
|
|
||||||
|
|
||||||
Then create an empty schema (database) named `casdoor` in your relational database. After the program runs for the first time, it will automatically create tables in this schema.
|
|
||||||
|
|
||||||
You can also edit `main.go`, modify `false` to `true`. It will automatically create the schema (database) named `casdoor` in this database.
|
## Casdoor Public API
|
||||||
|
|
||||||
```bash
|
- Docs: https://casdoor.org/docs/basic/public-api
|
||||||
createDatabase := flag.Bool("createDatabase", false, "true if you need casdoor to create database")
|
- Swagger: https://door.casdoor.com/swagger
|
||||||
```
|
|
||||||
|
|
||||||
#### Run
|
|
||||||
|
|
||||||
Casdoor provides two run modes, the difference is binary size and user prompt.
|
|
||||||
|
|
||||||
##### Dev Mode
|
## Integrations
|
||||||
|
|
||||||
Edit `conf/app.conf`, set `runmode=dev`. Firstly build front-end files:
|
https://casdoor.org/docs/integration/apisix
|
||||||
|
|
||||||
```bash
|
|
||||||
cd web/ && yarn && yarn run start
|
|
||||||
```
|
|
||||||
*❗ A word of caution ❗: Casdoor's front-end is built using yarn. You should use `yarn` instead of `npm`. It has a potential failure during building the files if you use `npm`.*
|
|
||||||
|
|
||||||
Then build back-end binary file, change directory to root(Relative to casdoor):
|
## How to contact?
|
||||||
|
|
||||||
```bash
|
- Gitter: https://gitter.im/casbin/casdoor
|
||||||
go run main.go
|
- Forum: https://forum.casbin.com
|
||||||
```
|
- Contact: https://tawk.to/chat/623352fea34c2456412b8c51/1fuc7od6e
|
||||||
|
|
||||||
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/ && yarn && yarn 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
|
|
||||||
|
|
||||||
Casdoor provide 2 kinds of image:
|
|
||||||
- casbin/casdoor-all-in-one, in which casdoor binary, a mysql database and all necessary configurations are packed up. This image is for new user to have a trial on casdoor quickly. **With this image you can start a casdoor immediately with one single command (or two) without any complex configuration**. **Note: we DO NOT recommend you to use this image in productive environment**
|
|
||||||
|
|
||||||
- casbin/casdoor: normal & graceful casdoor image with only casdoor and environment installed.
|
|
||||||
|
|
||||||
This method requires [docker](https://docs.docker.com/get-docker/) and [docker-compose](https://docs.docker.com/compose/install/) to be installed first.
|
|
||||||
|
|
||||||
### Start casdoor with casbin/casdoor-all-in-one
|
|
||||||
if the image is not pulled, pull it from dockerhub
|
|
||||||
```shell
|
|
||||||
docker pull casbin/casdoor-all-in-one
|
|
||||||
```
|
|
||||||
Start it with
|
|
||||||
```shell
|
|
||||||
docker run -p 8000:8000 casbin/casdoor-all-in-one
|
|
||||||
```
|
|
||||||
Now you can visit http://localhost:8000 and have a try. Default account and password is 'admin' and '123'. Go for it!
|
|
||||||
|
|
||||||
### Start casdoor with casbin/casdoor
|
|
||||||
#### modify the configurations
|
|
||||||
For the convenience of your first attempt, docker-compose.yml contains commands to start a database via docker.
|
|
||||||
|
|
||||||
Thus edit `conf/app.conf` to point out the location of database(db:3306), modify `dataSourceName` to the fixed content:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
dataSourceName = root:123456@tcp(db:3306)/
|
|
||||||
```
|
|
||||||
|
|
||||||
> If you need to modify `conf/app.conf`, you need to re-run `docker-compose up`.
|
|
||||||
|
|
||||||
#### Run
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker-compose up
|
|
||||||
```
|
|
||||||
|
|
||||||
### K8S
|
|
||||||
You could use helm to deploy casdoor in k8s. At first, you should modify the [configmap](./manifests/casdoor/templates/configmap.yaml) for your application.
|
|
||||||
And then run bellow command to deploy it.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
IMG_TAG=latest make deploy
|
|
||||||
```
|
|
||||||
|
|
||||||
And undeploy it with:
|
|
||||||
```bash
|
|
||||||
make undeploy
|
|
||||||
```
|
|
||||||
|
|
||||||
That's it! Try to visit http://localhost:8000/. :small_airplane:
|
|
||||||
|
|
||||||
## 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
|
## 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).
|
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 notice
|
### 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.
|
||||||
|
|
||||||
|
|
||||||
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
|
## License
|
||||||
|
|
||||||
[Apache-2.0](https://github.com/casdoor/casdoor/blob/master/LICENSE)
|
[Apache-2.0](https://github.com/casdoor/casdoor/blob/master/LICENSE)
|
||||||
|
|
||||||
|
@ -80,27 +80,30 @@ p, *, *, GET, /api/get-app-login, *, *
|
|||||||
p, *, *, POST, /api/logout, *, *
|
p, *, *, POST, /api/logout, *, *
|
||||||
p, *, *, GET, /api/get-account, *, *
|
p, *, *, GET, /api/get-account, *, *
|
||||||
p, *, *, GET, /api/userinfo, *, *
|
p, *, *, GET, /api/userinfo, *, *
|
||||||
p, *, *, POST, /api/login/oauth/access_token, *, *
|
p, *, *, *, /api/login/oauth, *, *
|
||||||
p, *, *, POST, /api/login/oauth/refresh_token, *, *
|
|
||||||
p, *, *, GET, /api/login/oauth/logout, *, *
|
|
||||||
p, *, *, GET, /api/get-application, *, *
|
p, *, *, GET, /api/get-application, *, *
|
||||||
|
p, *, *, GET, /api/get-applications, *, *
|
||||||
p, *, *, GET, /api/get-user, *, *
|
p, *, *, GET, /api/get-user, *, *
|
||||||
p, *, *, GET, /api/get-user-application, *, *
|
p, *, *, GET, /api/get-user-application, *, *
|
||||||
p, *, *, GET, /api/get-resources, *, *
|
p, *, *, GET, /api/get-resources, *, *
|
||||||
p, *, *, GET, /api/get-product, *, *
|
p, *, *, GET, /api/get-product, *, *
|
||||||
p, *, *, POST, /api/buy-product, *, *
|
p, *, *, POST, /api/buy-product, *, *
|
||||||
p, *, *, GET, /api/get-payment, *, *
|
p, *, *, GET, /api/get-payment, *, *
|
||||||
|
p, *, *, POST, /api/update-payment, *, *
|
||||||
|
p, *, *, POST, /api/invoice-payment, *, *
|
||||||
p, *, *, GET, /api/get-providers, *, *
|
p, *, *, GET, /api/get-providers, *, *
|
||||||
p, *, *, POST, /api/unlink, *, *
|
p, *, *, POST, /api/unlink, *, *
|
||||||
p, *, *, POST, /api/set-password, *, *
|
p, *, *, POST, /api/set-password, *, *
|
||||||
p, *, *, POST, /api/send-verification-code, *, *
|
p, *, *, POST, /api/send-verification-code, *, *
|
||||||
p, *, *, GET, /api/get-human-check, *, *
|
p, *, *, GET, /api/get-captcha, *, *
|
||||||
|
p, *, *, POST, /api/verify-captcha, *, *
|
||||||
p, *, *, POST, /api/reset-email-or-phone, *, *
|
p, *, *, POST, /api/reset-email-or-phone, *, *
|
||||||
p, *, *, POST, /api/upload-resource, *, *
|
p, *, *, POST, /api/upload-resource, *, *
|
||||||
p, *, *, GET, /.well-known/openid-configuration, *, *
|
p, *, *, GET, /.well-known/openid-configuration, *, *
|
||||||
p, *, *, *, /.well-known/jwks, *, *
|
p, *, *, *, /.well-known/jwks, *, *
|
||||||
p, *, *, GET, /api/get-saml-login, *, *
|
p, *, *, GET, /api/get-saml-login, *, *
|
||||||
p, *, *, POST, /api/acs, *, *
|
p, *, *, POST, /api/acs, *, *
|
||||||
|
p, *, *, GET, /api/saml/metadata, *, *
|
||||||
p, *, *, *, /cas, *, *
|
p, *, *, *, /cas, *, *
|
||||||
`
|
`
|
||||||
|
|
||||||
|
11
build.sh
Executable file
11
build.sh
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
#!/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"
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o server .
|
||||||
|
else
|
||||||
|
echo "Google is blocked, Go proxy is enabled: GOPROXY=https://goproxy.cn,direct"
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOPROXY=https://goproxy.cn,direct go build -ldflags="-w -s" -o server .
|
||||||
|
fi
|
105
captcha/aliyun.go
Normal file
105
captcha/aliyun.go
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
// 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/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const AliyunCaptchaVerifyUrl = "http://afs.aliyuncs.com"
|
||||||
|
|
||||||
|
type AliyunCaptchaProvider struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAliyunCaptchaProvider() *AliyunCaptchaProvider {
|
||||||
|
captcha := &AliyunCaptchaProvider{}
|
||||||
|
return captcha
|
||||||
|
}
|
||||||
|
|
||||||
|
func contentEscape(str string) string {
|
||||||
|
str = strings.Replace(str, " ", "%20", -1)
|
||||||
|
str = url.QueryEscape(str)
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
func (captcha *AliyunCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
|
||||||
|
pathData, err := url.ParseQuery(token)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pathData["Action"] = []string{"AuthenticateSig"}
|
||||||
|
pathData["Format"] = []string{"json"}
|
||||||
|
pathData["SignatureMethod"] = []string{"HMAC-SHA1"}
|
||||||
|
pathData["SignatureNonce"] = []string{strconv.FormatInt(time.Now().UnixNano(), 10)}
|
||||||
|
pathData["SignatureVersion"] = []string{"1.0"}
|
||||||
|
pathData["Timestamp"] = []string{time.Now().UTC().Format("2006-01-02T15:04:05Z")}
|
||||||
|
pathData["Version"] = []string{"2018-01-12"}
|
||||||
|
|
||||||
|
var keys []string
|
||||||
|
for k := range pathData {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
sortQuery := ""
|
||||||
|
for _, k := range keys {
|
||||||
|
sortQuery += k + "=" + contentEscape(pathData[k][0]) + "&"
|
||||||
|
}
|
||||||
|
sortQuery = strings.TrimSuffix(sortQuery, "&")
|
||||||
|
|
||||||
|
stringToSign := fmt.Sprintf("GET&%s&%s", url.QueryEscape("/"), url.QueryEscape(sortQuery))
|
||||||
|
|
||||||
|
signature := util.GetHmacSha1(clientSecret+"&", stringToSign)
|
||||||
|
|
||||||
|
resp, err := http.Get(fmt.Sprintf("%s?%s&Signature=%s", AliyunCaptchaVerifyUrl, sortQuery, url.QueryEscape(signature)))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type captchaResponse struct {
|
||||||
|
Code int `json:"Code"`
|
||||||
|
Msg string `json:"Msg"`
|
||||||
|
}
|
||||||
|
captchaResp := &captchaResponse{}
|
||||||
|
|
||||||
|
err = json.Unmarshal(body, captchaResp)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if captchaResp.Code != 100 {
|
||||||
|
return false, errors.New(captchaResp.Msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
29
captcha/default.go
Normal file
29
captcha/default.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// 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, clientSecret string) (bool, error) {
|
||||||
|
return object.VerifyCaptcha(clientSecret, token), nil
|
||||||
|
}
|
67
captcha/hcaptcha.go
Normal file
67
captcha/hcaptcha.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// 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/ioutil"
|
||||||
|
"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, clientSecret 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 := ioutil.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
|
||||||
|
}
|
32
captcha/provider.go
Normal file
32
captcha/provider.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
type CaptchaProvider interface {
|
||||||
|
VerifyCaptcha(token, clientSecret string) (bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCaptchaProvider(captchaType string) CaptchaProvider {
|
||||||
|
if captchaType == "Default" {
|
||||||
|
return NewDefaultCaptchaProvider()
|
||||||
|
} else if captchaType == "reCAPTCHA" {
|
||||||
|
return NewReCaptchaProvider()
|
||||||
|
} else if captchaType == "hCaptcha" {
|
||||||
|
return NewHCaptchaProvider()
|
||||||
|
} else if captchaType == "Aliyun Captcha" {
|
||||||
|
return NewAliyunCaptchaProvider()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
67
captcha/recaptcha.go
Normal file
67
captcha/recaptcha.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// 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/ioutil"
|
||||||
|
"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, clientSecret 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 := ioutil.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
|
||||||
|
}
|
@ -17,6 +17,7 @@ package conf
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -61,8 +62,13 @@ func GetBeegoConfDataSourceName() string {
|
|||||||
|
|
||||||
runningInDocker := os.Getenv("RUNNING_IN_DOCKER")
|
runningInDocker := os.Getenv("RUNNING_IN_DOCKER")
|
||||||
if runningInDocker == "true" {
|
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")
|
dataSourceName = strings.ReplaceAll(dataSourceName, "localhost", "host.docker.internal")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return dataSourceName
|
return dataSourceName
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ const (
|
|||||||
ResponseTypeCode = "code"
|
ResponseTypeCode = "code"
|
||||||
ResponseTypeToken = "token"
|
ResponseTypeToken = "token"
|
||||||
ResponseTypeIdToken = "id_token"
|
ResponseTypeIdToken = "id_token"
|
||||||
|
ResponseTypeSaml = "saml"
|
||||||
ResponseTypeCas = "cas"
|
ResponseTypeCas = "cas"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -61,6 +62,7 @@ type RequestForm struct {
|
|||||||
AutoSignin bool `json:"autoSignin"`
|
AutoSignin bool `json:"autoSignin"`
|
||||||
|
|
||||||
RelayState string `json:"relayState"`
|
RelayState string `json:"relayState"`
|
||||||
|
SamlRequest string `json:"samlRequest"`
|
||||||
SamlResponse string `json:"samlResponse"`
|
SamlResponse string `json:"samlResponse"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,12 +75,17 @@ type Response struct {
|
|||||||
Data2 interface{} `json:"data2"`
|
Data2 interface{} `json:"data2"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HumanCheck struct {
|
type Captcha struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
AppKey string `json:"appKey"`
|
AppKey string `json:"appKey"`
|
||||||
Scene string `json:"scene"`
|
Scene string `json:"scene"`
|
||||||
CaptchaId string `json:"captchaId"`
|
CaptchaId string `json:"captchaId"`
|
||||||
CaptchaImage interface{} `json:"captchaImage"`
|
CaptchaImage []byte `json:"captchaImage"`
|
||||||
|
ClientId string `json:"clientId"`
|
||||||
|
ClientSecret string `json:"clientSecret"`
|
||||||
|
ClientId2 string `json:"clientId2"`
|
||||||
|
ClientSecret2 string `json:"clientSecret2"`
|
||||||
|
SubType string `json:"subType"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signup
|
// Signup
|
||||||
@ -114,7 +121,7 @@ func (c *ApiController) Signup() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if application.IsSignupItemVisible("Email") && form.Email != "" {
|
if application.IsSignupItemVisible("Email") && application.GetSignupItemRule("Email") != "No verification" && form.Email != "" {
|
||||||
checkResult := object.CheckVerificationCode(form.Email, form.EmailCode)
|
checkResult := object.CheckVerificationCode(form.Email, form.EmailCode)
|
||||||
if len(checkResult) != 0 {
|
if len(checkResult) != 0 {
|
||||||
c.ResponseError(fmt.Sprintf("Email: %s", checkResult))
|
c.ResponseError(fmt.Sprintf("Email: %s", checkResult))
|
||||||
@ -208,7 +215,7 @@ func (c *ApiController) Signup() {
|
|||||||
record := object.NewRecord(c.Ctx)
|
record := object.NewRecord(c.Ctx)
|
||||||
record.Organization = application.Organization
|
record.Organization = application.Organization
|
||||||
record.User = user.Name
|
record.User = user.Name
|
||||||
go object.AddRecord(record)
|
util.SafeGoroutine(func() { object.AddRecord(record) })
|
||||||
|
|
||||||
userId := fmt.Sprintf("%s/%s", user.Owner, user.Name)
|
userId := fmt.Sprintf("%s/%s", user.Owner, user.Name)
|
||||||
util.LogInfo(c.Ctx, "API: [%s] is signed up as new user", userId)
|
util.LogInfo(c.Ctx, "API: [%s] is signed up as new user", userId)
|
||||||
@ -283,25 +290,43 @@ func (c *ApiController) GetUserinfo() {
|
|||||||
resp, err := object.GetUserInfo(userId, scope, aud, host)
|
resp, err := object.GetUserInfo(userId, scope, aud, host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
}
|
}
|
||||||
c.Data["json"] = resp
|
c.Data["json"] = resp
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetHumanCheck ...
|
// GetCaptcha ...
|
||||||
// @Tag Login API
|
// @Tag Login API
|
||||||
// @Title GetHumancheck
|
// @Title GetCaptcha
|
||||||
// @router /api/get-human-check [get]
|
// @router /api/get-captcha [get]
|
||||||
func (c *ApiController) GetHumanCheck() {
|
func (c *ApiController) GetCaptcha() {
|
||||||
c.Data["json"] = HumanCheck{Type: "none"}
|
applicationId := c.Input().Get("applicationId")
|
||||||
|
isCurrentProvider := c.Input().Get("isCurrentProvider")
|
||||||
|
|
||||||
provider := object.GetDefaultHumanCheckProvider()
|
captchaProvider, err := object.GetCaptchaProviderByApplication(applicationId, isCurrentProvider)
|
||||||
if provider == nil {
|
if err != nil {
|
||||||
id, img := object.GetCaptcha()
|
c.ResponseError(err.Error())
|
||||||
c.Data["json"] = HumanCheck{Type: "captcha", CaptchaId: id, CaptchaImage: img}
|
|
||||||
c.ServeJSON()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ServeJSON()
|
if captchaProvider != nil {
|
||||||
|
if captchaProvider.Type == "Default" {
|
||||||
|
id, img := object.GetCaptcha()
|
||||||
|
c.ResponseOk(Captcha{Type: captchaProvider.Type, CaptchaId: id, CaptchaImage: img})
|
||||||
|
return
|
||||||
|
} else if captchaProvider.Type != "" {
|
||||||
|
c.ResponseOk(Captcha{
|
||||||
|
Type: captchaProvider.Type,
|
||||||
|
SubType: captchaProvider.SubType,
|
||||||
|
ClientId: captchaProvider.ClientId,
|
||||||
|
ClientSecret: captchaProvider.ClientSecret,
|
||||||
|
ClientId2: captchaProvider.ClientId2,
|
||||||
|
ClientSecret2: captchaProvider.ClientSecret2,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(Captcha{Type: "none"})
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ import (
|
|||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casdoor/casdoor/proxy"
|
"github.com/casdoor/casdoor/proxy"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
func codeToResponse(code *object.Code) *Response {
|
func codeToResponse(code *object.Code) *Response {
|
||||||
@ -83,6 +84,13 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
|||||||
resp = tokenToResponse(token)
|
resp = tokenToResponse(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} else if form.Type == ResponseTypeSaml { // saml flow
|
||||||
|
res, redirectUrl, err := object.GetSamlResponse(application, user, form.SamlRequest, c.Ctx.Request.Host)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error(), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp = &Response{Status: "ok", Msg: "", Data: res, Data2: redirectUrl}
|
||||||
} else if form.Type == ResponseTypeCas {
|
} else if form.Type == ResponseTypeCas {
|
||||||
//not oauth but CAS SSO protocol
|
//not oauth but CAS SSO protocol
|
||||||
service := c.Input().Get("service")
|
service := c.Input().Get("service")
|
||||||
@ -125,8 +133,8 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
|||||||
// @Param redirectUri query string true "redirect uri"
|
// @Param redirectUri query string true "redirect uri"
|
||||||
// @Param scope query string true "scope"
|
// @Param scope query string true "scope"
|
||||||
// @Param state query string true "state"
|
// @Param state query string true "state"
|
||||||
// @Success 200 {object} controllers.api_controller.Response The Response object
|
// @Success 200 {object} Response The Response object
|
||||||
// @router /update-application [get]
|
// @router /get-app-login [get]
|
||||||
func (c *ApiController) GetApplicationLogin() {
|
func (c *ApiController) GetApplicationLogin() {
|
||||||
clientId := c.Input().Get("clientId")
|
clientId := c.Input().Get("clientId")
|
||||||
responseType := c.Input().Get("responseType")
|
responseType := c.Input().Get("responseType")
|
||||||
@ -155,9 +163,16 @@ func setHttpClient(idProvider idp.IdProvider, providerType string) {
|
|||||||
// @Title Login
|
// @Title Login
|
||||||
// @Tag Login API
|
// @Tag Login API
|
||||||
// @Description login
|
// @Description login
|
||||||
// @Param oAuthParams query string true "oAuth parameters"
|
// @Param clientId query string true clientId
|
||||||
// @Param body body RequestForm true "Login information"
|
// @Param responseType query string true responseType
|
||||||
// @Success 200 {object} controllers.api_controller.Response The Response object
|
// @Param redirectUri query string true redirectUri
|
||||||
|
// @Param scope query string false scope
|
||||||
|
// @Param state query string false state
|
||||||
|
// @Param nonce query string false nonce
|
||||||
|
// @Param code_challenge_method query string false code_challenge_method
|
||||||
|
// @Param code_challenge query string false code_challenge
|
||||||
|
// @Param form body controllers.RequestForm true "Login information"
|
||||||
|
// @Success 200 {object} Response The Response object
|
||||||
// @router /login [post]
|
// @router /login [post]
|
||||||
func (c *ApiController) Login() {
|
func (c *ApiController) Login() {
|
||||||
resp := &Response{}
|
resp := &Response{}
|
||||||
@ -215,7 +230,11 @@ func (c *ApiController) Login() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// disable the verification code
|
// disable the verification code
|
||||||
|
if strings.Contains(form.Username, "@") {
|
||||||
object.DisableVerificationCode(form.Username)
|
object.DisableVerificationCode(form.Username)
|
||||||
|
} else {
|
||||||
|
object.DisableVerificationCode(fmt.Sprintf("+%s%s", form.PhonePrefix, form.Username))
|
||||||
|
}
|
||||||
|
|
||||||
user = object.GetUserByFields(form.Organization, form.Username)
|
user = object.GetUserByFields(form.Organization, form.Username)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
@ -241,7 +260,7 @@ func (c *ApiController) Login() {
|
|||||||
record := object.NewRecord(c.Ctx)
|
record := object.NewRecord(c.Ctx)
|
||||||
record.Organization = application.Organization
|
record.Organization = application.Organization
|
||||||
record.User = user.Name
|
record.User = user.Name
|
||||||
go object.AddRecord(record)
|
util.SafeGoroutine(func() { object.AddRecord(record) })
|
||||||
}
|
}
|
||||||
} else if form.Provider != "" {
|
} else if form.Provider != "" {
|
||||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||||
@ -276,7 +295,7 @@ func (c *ApiController) Login() {
|
|||||||
clientSecret = provider.ClientSecret2
|
clientSecret = provider.ClientSecret2
|
||||||
}
|
}
|
||||||
|
|
||||||
idProvider := idp.GetIdProvider(provider.Type, provider.SubType, clientId, clientSecret, provider.AppId, form.RedirectUri, provider.Domain)
|
idProvider := idp.GetIdProvider(provider.Type, provider.SubType, clientId, clientSecret, provider.AppId, form.RedirectUri, provider.Domain, provider.CustomAuthUrl, provider.CustomTokenUrl, provider.CustomUserInfoUrl)
|
||||||
if idProvider == nil {
|
if idProvider == nil {
|
||||||
c.ResponseError(fmt.Sprintf("The provider type: %s is not supported", provider.Type))
|
c.ResponseError(fmt.Sprintf("The provider type: %s is not supported", provider.Type))
|
||||||
return
|
return
|
||||||
@ -314,12 +333,6 @@ func (c *ApiController) Login() {
|
|||||||
user = object.GetUser(fmt.Sprintf("%s/%s", application.Organization, userInfo.Id))
|
user = object.GetUser(fmt.Sprintf("%s/%s", application.Organization, userInfo.Id))
|
||||||
} else if provider.Category == "OAuth" {
|
} else if provider.Category == "OAuth" {
|
||||||
user = object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
|
user = object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
|
||||||
if user == nil {
|
|
||||||
user = object.GetUserByField(application.Organization, provider.Type, userInfo.Username)
|
|
||||||
}
|
|
||||||
if user == nil {
|
|
||||||
user = object.GetUserByField(application.Organization, "name", userInfo.Username)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if user != nil && user.IsDeleted == false {
|
if user != nil && user.IsDeleted == false {
|
||||||
@ -334,7 +347,7 @@ func (c *ApiController) Login() {
|
|||||||
record := object.NewRecord(c.Ctx)
|
record := object.NewRecord(c.Ctx)
|
||||||
record.Organization = application.Organization
|
record.Organization = application.Organization
|
||||||
record.User = user.Name
|
record.User = user.Name
|
||||||
go object.AddRecord(record)
|
util.SafeGoroutine(func() { object.AddRecord(record) })
|
||||||
} else if provider.Category == "OAuth" {
|
} else if provider.Category == "OAuth" {
|
||||||
// Sign up via OAuth
|
// Sign up via OAuth
|
||||||
if !application.EnableSignUp {
|
if !application.EnableSignUp {
|
||||||
@ -347,6 +360,19 @@ func (c *ApiController) Login() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle username conflicts
|
||||||
|
tmpUser := object.GetUser(fmt.Sprintf("%s/%s", application.Organization, userInfo.Username))
|
||||||
|
if tmpUser != nil {
|
||||||
|
uid, err := uuid.NewRandom()
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uidStr := strings.Split(uid.String(), "-")
|
||||||
|
userInfo.Username = fmt.Sprintf("%s_%s", userInfo.Username, uidStr[1])
|
||||||
|
}
|
||||||
|
|
||||||
properties := map[string]string{}
|
properties := map[string]string{}
|
||||||
properties["no"] = strconv.Itoa(len(object.GetUsers(application.Organization)) + 2)
|
properties["no"] = strconv.Itoa(len(object.GetUsers(application.Organization)) + 2)
|
||||||
user = &object.User{
|
user = &object.User{
|
||||||
@ -383,7 +409,13 @@ func (c *ApiController) Login() {
|
|||||||
record := object.NewRecord(c.Ctx)
|
record := object.NewRecord(c.Ctx)
|
||||||
record.Organization = application.Organization
|
record.Organization = application.Organization
|
||||||
record.User = user.Name
|
record.User = user.Name
|
||||||
go object.AddRecord(record)
|
util.SafeGoroutine(func() { object.AddRecord(record) })
|
||||||
|
|
||||||
|
record2 := object.NewRecord(c.Ctx)
|
||||||
|
record2.Action = "signup"
|
||||||
|
record2.Organization = application.Organization
|
||||||
|
record2.User = user.Name
|
||||||
|
util.SafeGoroutine(func() { object.AddRecord(record2) })
|
||||||
} else if provider.Category == "SAML" {
|
} else if provider.Category == "SAML" {
|
||||||
resp = &Response{Status: "error", Msg: "The account does not exist"}
|
resp = &Response{Status: "error", Msg: "The account does not exist"}
|
||||||
}
|
}
|
||||||
@ -396,9 +428,6 @@ func (c *ApiController) Login() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
oldUser := object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
|
oldUser := object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
|
||||||
if oldUser == nil {
|
|
||||||
oldUser = object.GetUserByField(application.Organization, provider.Type, userInfo.Username)
|
|
||||||
}
|
|
||||||
if oldUser != nil {
|
if oldUser != nil {
|
||||||
c.ResponseError(fmt.Sprintf("The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)", provider.Type, userInfo.Username, userInfo.DisplayName, oldUser.Name, oldUser.DisplayName))
|
c.ResponseError(fmt.Sprintf("The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)", provider.Type, userInfo.Username, userInfo.DisplayName, oldUser.Name, oldUser.DisplayName))
|
||||||
return
|
return
|
||||||
@ -427,6 +456,11 @@ func (c *ApiController) Login() {
|
|||||||
|
|
||||||
user := c.getCurrentUser()
|
user := c.getCurrentUser()
|
||||||
resp = c.HandleLoggedIn(application, user, &form)
|
resp = c.HandleLoggedIn(application, user, &form)
|
||||||
|
|
||||||
|
record := object.NewRecord(c.Ctx)
|
||||||
|
record.Organization = application.Organization
|
||||||
|
record.User = user.Name
|
||||||
|
util.SafeGoroutine(func() { object.AddRecord(record) })
|
||||||
} else {
|
} else {
|
||||||
c.ResponseError(fmt.Sprintf("unknown authentication type (not password or provider), form = %s", util.StructToJson(form)))
|
c.ResponseError(fmt.Sprintf("unknown authentication type (not password or provider), form = %s", util.StructToJson(form)))
|
||||||
return
|
return
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -42,7 +43,7 @@ func (c *RootController) CasValidate() {
|
|||||||
c.Ctx.Output.Body([]byte("no\n"))
|
c.Ctx.Output.Body([]byte("no\n"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if ok, response, issuedService := object.GetCasTokenByTicket(ticket); ok {
|
if ok, response, issuedService, _ := object.GetCasTokenByTicket(ticket); ok {
|
||||||
//check whether service is the one for which we previously issued token
|
//check whether service is the one for which we previously issued token
|
||||||
if issuedService == service {
|
if issuedService == service {
|
||||||
c.Ctx.Output.Body([]byte(fmt.Sprintf("yes\n%s\n", response.User)))
|
c.Ctx.Output.Body([]byte(fmt.Sprintf("yes\n%s\n", response.User)))
|
||||||
@ -54,7 +55,25 @@ func (c *RootController) CasValidate() {
|
|||||||
c.Ctx.Output.Body([]byte("no\n"))
|
c.Ctx.Output.Body([]byte("no\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RootController) CasServiceAndProxyValidate() {
|
func (c *RootController) CasServiceValidate() {
|
||||||
|
ticket := c.Input().Get("ticket")
|
||||||
|
format := c.Input().Get("format")
|
||||||
|
if !strings.HasPrefix(ticket, "ST") {
|
||||||
|
c.sendCasAuthenticationResponseErr(InvalidTicket, fmt.Sprintf("Ticket %s not recognized", ticket), format)
|
||||||
|
}
|
||||||
|
c.CasP3ServiceAndProxyValidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RootController) CasProxyValidate() {
|
||||||
|
ticket := c.Input().Get("ticket")
|
||||||
|
format := c.Input().Get("format")
|
||||||
|
if !strings.HasPrefix(ticket, "PT") {
|
||||||
|
c.sendCasAuthenticationResponseErr(InvalidTicket, fmt.Sprintf("Ticket %s not recognized", ticket), format)
|
||||||
|
}
|
||||||
|
c.CasP3ServiceAndProxyValidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RootController) CasP3ServiceAndProxyValidate() {
|
||||||
ticket := c.Input().Get("ticket")
|
ticket := c.Input().Get("ticket")
|
||||||
format := c.Input().Get("format")
|
format := c.Input().Get("format")
|
||||||
service := c.Input().Get("service")
|
service := c.Input().Get("service")
|
||||||
@ -69,10 +88,9 @@ func (c *RootController) CasServiceAndProxyValidate() {
|
|||||||
c.sendCasAuthenticationResponseErr(InvalidRequest, "service and ticket must exist", format)
|
c.sendCasAuthenticationResponseErr(InvalidRequest, "service and ticket must exist", format)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
ok, response, issuedService, userId := object.GetCasTokenByTicket(ticket)
|
||||||
//find the token
|
//find the token
|
||||||
if ok, response, issuedService := object.GetCasTokenByTicket(ticket); ok {
|
if ok {
|
||||||
|
|
||||||
//check whether service is the one for which we previously issued token
|
//check whether service is the one for which we previously issued token
|
||||||
if strings.HasPrefix(service, issuedService) {
|
if strings.HasPrefix(service, issuedService) {
|
||||||
serviceResponse.Success = response
|
serviceResponse.Success = response
|
||||||
@ -89,7 +107,7 @@ func (c *RootController) CasServiceAndProxyValidate() {
|
|||||||
|
|
||||||
if pgtUrl != "" && serviceResponse.Failure == nil {
|
if pgtUrl != "" && serviceResponse.Failure == nil {
|
||||||
//that means we are in proxy web flow
|
//that means we are in proxy web flow
|
||||||
pgt := object.StoreCasTokenForPgt(serviceResponse.Success, service)
|
pgt := object.StoreCasTokenForPgt(serviceResponse.Success, service, userId)
|
||||||
pgtiou := serviceResponse.Success.ProxyGrantingTicket
|
pgtiou := serviceResponse.Success.ProxyGrantingTicket
|
||||||
//todo: check whether it is https
|
//todo: check whether it is https
|
||||||
pgtUrlObj, err := url.Parse(pgtUrl)
|
pgtUrlObj, err := url.Parse(pgtUrl)
|
||||||
@ -139,7 +157,7 @@ func (c *RootController) CasProxy() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ok, authenticationSuccess, issuedService := object.GetCasTokenByPgt(pgt)
|
ok, authenticationSuccess, issuedService, userId := object.GetCasTokenByPgt(pgt)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.sendCasProxyResponseErr(UnauthorizedService, "service not authorized", format)
|
c.sendCasProxyResponseErr(UnauthorizedService, "service not authorized", format)
|
||||||
return
|
return
|
||||||
@ -150,7 +168,7 @@ func (c *RootController) CasProxy() {
|
|||||||
newAuthenticationSuccess.Proxies = &object.CasProxies{}
|
newAuthenticationSuccess.Proxies = &object.CasProxies{}
|
||||||
}
|
}
|
||||||
newAuthenticationSuccess.Proxies.Proxies = append(newAuthenticationSuccess.Proxies.Proxies, issuedService)
|
newAuthenticationSuccess.Proxies.Proxies = append(newAuthenticationSuccess.Proxies.Proxies, issuedService)
|
||||||
proxyTicket := object.StoreCasTokenForProxyTicket(&newAuthenticationSuccess, targetService)
|
proxyTicket := object.StoreCasTokenForProxyTicket(&newAuthenticationSuccess, targetService, userId)
|
||||||
|
|
||||||
serviceResponse := object.CasServiceResponse{
|
serviceResponse := object.CasServiceResponse{
|
||||||
Xmlns: "http://www.yale.edu/tp/cas",
|
Xmlns: "http://www.yale.edu/tp/cas",
|
||||||
@ -168,6 +186,55 @@ func (c *RootController) CasProxy() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *RootController) SamlValidate() {
|
||||||
|
c.Ctx.Output.Header("Content-Type", "text/xml; charset=utf-8")
|
||||||
|
target := c.Input().Get("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("service %s and %s do not match", target, service))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
envelopReponse := 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"`
|
||||||
|
}
|
||||||
|
}{}
|
||||||
|
envelopReponse.Xmlns = "http://schemas.xmlsoap.org/soap/envelope/"
|
||||||
|
envelopReponse.Body.Content = response
|
||||||
|
|
||||||
|
data, err := xml.Marshal(envelopReponse)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Ctx.Output.Body([]byte(data))
|
||||||
|
}
|
||||||
|
|
||||||
func (c *RootController) sendCasProxyResponseErr(code, msg, format string) {
|
func (c *RootController) sendCasProxyResponseErr(code, msg, format string) {
|
||||||
serviceResponse := object.CasServiceResponse{
|
serviceResponse := object.CasServiceResponse{
|
||||||
Xmlns: "http://www.yale.edu/tp/cas",
|
Xmlns: "http://www.yale.edu/tp/cas",
|
||||||
|
@ -215,7 +215,7 @@ func (c *ApiController) SyncLdapUsers() {
|
|||||||
|
|
||||||
object.UpdateLdapSyncTime(ldapId)
|
object.UpdateLdapSyncTime(ldapId)
|
||||||
|
|
||||||
exist, failed := object.SyncLdapUsers(owner, users)
|
exist, failed := object.SyncLdapUsers(owner, users, ldapId)
|
||||||
c.Data["json"] = &Response{Status: "ok", Data: &LdapSyncResp{
|
c.Data["json"] = &Response{Status: "ok", Data: &LdapSyncResp{
|
||||||
Exist: *exist,
|
Exist: *exist,
|
||||||
Failed: *failed,
|
Failed: *failed,
|
||||||
|
120
controllers/model.go
Normal file
120
controllers/model.go
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
// 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/astaxie/beego/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.Input().Get("owner")
|
||||||
|
limit := c.Input().Get("pageSize")
|
||||||
|
page := c.Input().Get("p")
|
||||||
|
field := c.Input().Get("field")
|
||||||
|
value := c.Input().Get("value")
|
||||||
|
sortField := c.Input().Get("sortField")
|
||||||
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
if limit == "" || page == "" {
|
||||||
|
c.Data["json"] = object.GetModels(owner)
|
||||||
|
c.ServeJSON()
|
||||||
|
} else {
|
||||||
|
limit := util.ParseInt(limit)
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetModelCount(owner, field, value)))
|
||||||
|
models := object.GetPaginationModels(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
c.ResponseOk(models, paginator.Nums())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetModel
|
||||||
|
// @Title GetModel
|
||||||
|
// @Tag Model API
|
||||||
|
// @Description get model
|
||||||
|
// @Param id query string true "The id of the model"
|
||||||
|
// @Success 200 {object} object.Model The Response object
|
||||||
|
// @router /get-model [get]
|
||||||
|
func (c *ApiController) GetModel() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
c.Data["json"] = object.GetModel(id)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateModel
|
||||||
|
// @Title UpdateModel
|
||||||
|
// @Tag Model API
|
||||||
|
// @Description update model
|
||||||
|
// @Param id query string true "The id 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.Input().Get("id")
|
||||||
|
|
||||||
|
var model object.Model
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &model)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.UpdateModel(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 {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.DeleteModel(&model))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
@ -18,6 +18,8 @@ import "github.com/casdoor/casdoor/object"
|
|||||||
|
|
||||||
// @Title GetOidcDiscovery
|
// @Title GetOidcDiscovery
|
||||||
// @Tag OIDC API
|
// @Tag OIDC API
|
||||||
|
// @Description Get Oidc Discovery
|
||||||
|
// @Success 200 {object} object.OidcDiscovery
|
||||||
// @router /.well-known/openid-configuration [get]
|
// @router /.well-known/openid-configuration [get]
|
||||||
func (c *RootController) GetOidcDiscovery() {
|
func (c *RootController) GetOidcDiscovery() {
|
||||||
host := c.Ctx.Request.Host
|
host := c.Ctx.Request.Host
|
||||||
@ -27,6 +29,7 @@ func (c *RootController) GetOidcDiscovery() {
|
|||||||
|
|
||||||
// @Title GetJwks
|
// @Title GetJwks
|
||||||
// @Tag OIDC API
|
// @Tag OIDC API
|
||||||
|
// @Success 200 {object} jose.JSONWebKey
|
||||||
// @router /.well-known/jwks [get]
|
// @router /.well-known/jwks [get]
|
||||||
func (c *RootController) GetJwks() {
|
func (c *RootController) GetJwks() {
|
||||||
jwks, err := object.GetJsonWebKeySet()
|
jwks, err := object.GetJsonWebKeySet()
|
||||||
|
@ -158,3 +158,20 @@ func (c *ApiController) NotifyPayment() {
|
|||||||
panic(fmt.Errorf("NotifyPayment() failed: %v", ok))
|
panic(fmt.Errorf("NotifyPayment() failed: %v", ok))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Title InvoicePayment
|
||||||
|
// @Tag Payment API
|
||||||
|
// @Description invoice payment
|
||||||
|
// @Param id query string true "The id of the payment"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /invoice-payment [post]
|
||||||
|
func (c *ApiController) InvoicePayment() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
payment := object.GetPayment(id)
|
||||||
|
invoiceUrl, err := object.InvoicePayment(payment)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
}
|
||||||
|
c.ResponseOk(invoiceUrl)
|
||||||
|
}
|
||||||
|
@ -26,7 +26,7 @@ import (
|
|||||||
// @Description get all records
|
// @Description get all records
|
||||||
// @Param pageSize query string true "The size of each page"
|
// @Param pageSize query string true "The size of each page"
|
||||||
// @Param p query string true "The number of the page"
|
// @Param p query string true "The number of the page"
|
||||||
// @Success 200 {array} object.Records The Response object
|
// @Success 200 {object} object.Record The Response object
|
||||||
// @router /get-records [get]
|
// @router /get-records [get]
|
||||||
func (c *ApiController) GetRecords() {
|
func (c *ApiController) GetRecords() {
|
||||||
limit := c.Input().Get("pageSize")
|
limit := c.Input().Get("pageSize")
|
||||||
@ -50,8 +50,8 @@ func (c *ApiController) GetRecords() {
|
|||||||
// @Tag Record API
|
// @Tag Record API
|
||||||
// @Title GetRecordsByFilter
|
// @Title GetRecordsByFilter
|
||||||
// @Description get records by filter
|
// @Description get records by filter
|
||||||
// @Param body body object.Records true "filter Record message"
|
// @Param filter body string true "filter Record message"
|
||||||
// @Success 200 {array} object.Records The Response object
|
// @Success 200 {object} object.Record The Response object
|
||||||
// @router /get-records-filter [post]
|
// @router /get-records-filter [post]
|
||||||
func (c *ApiController) GetRecordsByFilter() {
|
func (c *ApiController) GetRecordsByFilter() {
|
||||||
body := string(c.Ctx.Input.RequestBody)
|
body := string(c.Ctx.Input.RequestBody)
|
||||||
|
34
controllers/saml.go
Normal file
34
controllers/saml.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// 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"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *ApiController) GetSamlMeta() {
|
||||||
|
host := c.Ctx.Request.Host
|
||||||
|
paramApp := c.Input().Get("application")
|
||||||
|
application := object.GetApplication(paramApp)
|
||||||
|
if application == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf("err: application %s not found", paramApp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
metadata, _ := object.GetSamlMeta(application, host)
|
||||||
|
c.Data["xml"] = metadata
|
||||||
|
c.ServeXML()
|
||||||
|
}
|
@ -25,33 +25,61 @@ import (
|
|||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SmsForm struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
Receivers []string `json:"receivers"`
|
||||||
|
OrgId string `json:"organizationId"` // e.g. "admin/built-in"
|
||||||
|
}
|
||||||
|
|
||||||
// SendEmail
|
// SendEmail
|
||||||
// @Title SendEmail
|
// @Title SendEmail
|
||||||
// @Tag Service API
|
// @Tag Service API
|
||||||
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
|
// @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 clientId query string true "The clientId of the application"
|
||||||
// @Param clientSecret query string true "The clientSecret of the application"
|
// @Param clientSecret query string true "The clientSecret of the application"
|
||||||
// @Param body body emailForm true "Details of the email request"
|
// @Param from body controllers.EmailForm true "Details of the email request"
|
||||||
// @Success 200 {object} Response object
|
// @Success 200 {object} Response object
|
||||||
// @router /api/send-email [post]
|
// @router /api/send-email [post]
|
||||||
func (c *ApiController) SendEmail() {
|
func (c *ApiController) SendEmail() {
|
||||||
provider, _, ok := c.GetProviderFromContext("Email")
|
var emailForm EmailForm
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &emailForm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var provider *object.Provider
|
||||||
|
if emailForm.Provider != "" {
|
||||||
|
// called by frontend's TestEmailWidget, provider name is set by frontend
|
||||||
|
provider = object.GetProvider(fmt.Sprintf("admin/%s", emailForm.Provider))
|
||||||
|
} 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
|
||||||
|
var ok bool
|
||||||
|
provider, _, ok = c.GetProviderFromContext("Email")
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.DailSmtpServer(provider)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.ResponseOk()
|
||||||
|
}
|
||||||
|
|
||||||
if util.IsStrsEmpty(emailForm.Title, emailForm.Content, emailForm.Sender) {
|
if util.IsStrsEmpty(emailForm.Title, emailForm.Content, emailForm.Sender) {
|
||||||
c.ResponseError(fmt.Sprintf("Empty parameters for emailForm: %v", emailForm))
|
c.ResponseError(fmt.Sprintf("Empty parameters for emailForm: %v", emailForm))
|
||||||
return
|
return
|
||||||
@ -86,7 +114,7 @@ func (c *ApiController) SendEmail() {
|
|||||||
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
|
// @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 clientId query string true "The clientId of the application"
|
||||||
// @Param clientSecret query string true "The clientSecret of the application"
|
// @Param clientSecret query string true "The clientSecret of the application"
|
||||||
// @Param body body smsForm true "Details of the sms request"
|
// @Param from body controllers.SmsForm true "Details of the sms request"
|
||||||
// @Success 200 {object} Response object
|
// @Success 200 {object} Response object
|
||||||
// @router /api/send-sms [post]
|
// @router /api/send-sms [post]
|
||||||
func (c *ApiController) SendSms() {
|
func (c *ApiController) SendSms() {
|
||||||
@ -95,11 +123,7 @@ func (c *ApiController) SendSms() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var smsForm struct {
|
var smsForm SmsForm
|
||||||
Content string `json:"content"`
|
|
||||||
Receivers []string `json:"receivers"`
|
|
||||||
OrgId string `json:"organizationId"` // e.g. "admin/built-in"
|
|
||||||
}
|
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &smsForm)
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &smsForm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
|
@ -16,7 +16,6 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/astaxie/beego/utils/pagination"
|
"github.com/astaxie/beego/utils/pagination"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
@ -114,3 +113,18 @@ func (c *ApiController) DeleteSyncer() {
|
|||||||
c.Data["json"] = wrapActionResponse(object.DeleteSyncer(&syncer))
|
c.Data["json"] = wrapActionResponse(object.DeleteSyncer(&syncer))
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @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.Input().Get("id")
|
||||||
|
syncer := object.GetSyncer(id)
|
||||||
|
|
||||||
|
object.RunSyncer(syncer)
|
||||||
|
|
||||||
|
c.ResponseOk()
|
||||||
|
}
|
||||||
|
@ -175,6 +175,8 @@ func (c *ApiController) GetOAuthToken() {
|
|||||||
scope := c.Input().Get("scope")
|
scope := c.Input().Get("scope")
|
||||||
username := c.Input().Get("username")
|
username := c.Input().Get("username")
|
||||||
password := c.Input().Get("password")
|
password := c.Input().Get("password")
|
||||||
|
tag := c.Input().Get("tag")
|
||||||
|
avatar := c.Input().Get("avatar")
|
||||||
|
|
||||||
if clientId == "" && clientSecret == "" {
|
if clientId == "" && clientSecret == "" {
|
||||||
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
|
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
|
||||||
@ -191,11 +193,13 @@ func (c *ApiController) GetOAuthToken() {
|
|||||||
scope = tokenRequest.Scope
|
scope = tokenRequest.Scope
|
||||||
username = tokenRequest.Username
|
username = tokenRequest.Username
|
||||||
password = tokenRequest.Password
|
password = tokenRequest.Password
|
||||||
|
tag = tokenRequest.Tag
|
||||||
|
avatar = tokenRequest.Avatar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
host := c.Ctx.Request.Host
|
host := c.Ctx.Request.Host
|
||||||
|
|
||||||
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host)
|
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host, tag, avatar)
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,7 +230,7 @@ func (c *ApiController) RefreshToken() {
|
|||||||
clientSecret = tokenRequest.ClientSecret
|
clientSecret = tokenRequest.ClientSecret
|
||||||
grantType = tokenRequest.GrantType
|
grantType = tokenRequest.GrantType
|
||||||
scope = tokenRequest.Scope
|
scope = tokenRequest.Scope
|
||||||
|
refreshToken = tokenRequest.RefreshToken
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,21 +275,20 @@ func (c *ApiController) IntrospectToken() {
|
|||||||
tokenValue := c.Input().Get("token")
|
tokenValue := c.Input().Get("token")
|
||||||
clientId, clientSecret, ok := c.Ctx.Request.BasicAuth()
|
clientId, clientSecret, ok := c.Ctx.Request.BasicAuth()
|
||||||
if !ok {
|
if !ok {
|
||||||
util.LogWarning(c.Ctx, "Basic Authorization parses failed")
|
clientId = c.Input().Get("client_id")
|
||||||
c.Data["json"] = Response{Status: "error", Msg: "Unauthorized operation"}
|
clientSecret = c.Input().Get("client_secret")
|
||||||
c.ServeJSON()
|
if clientId == "" || clientSecret == "" {
|
||||||
|
c.ResponseError("empty clientId or clientSecret")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
application := object.GetApplicationByClientId(clientId)
|
application := object.GetApplicationByClientId(clientId)
|
||||||
if application == nil || application.ClientSecret != clientSecret {
|
if application == nil || application.ClientSecret != clientSecret {
|
||||||
util.LogWarning(c.Ctx, "Basic Authorization failed")
|
c.ResponseError("invalid application or wrong clientSecret")
|
||||||
c.Data["json"] = Response{Status: "error", Msg: "Unauthorized operation"}
|
|
||||||
c.ServeJSON()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
token := object.GetTokenByTokenAndApplication(tokenValue, application.Name)
|
token := object.GetTokenByTokenAndApplication(tokenValue, application.Name)
|
||||||
if token == nil {
|
if token == nil {
|
||||||
util.LogWarning(c.Ctx, "application: %s can not find token", application.Name)
|
|
||||||
c.Data["json"] = &object.IntrospectionResponse{Active: false}
|
c.Data["json"] = &object.IntrospectionResponse{Active: false}
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
return
|
return
|
||||||
@ -295,7 +298,6 @@ func (c *ApiController) IntrospectToken() {
|
|||||||
// and token revoked case. but we not implement
|
// and token revoked case. but we not implement
|
||||||
// TODO: 2022-03-03 add token revoked check, when we implemented the Token Revocation(rfc7009) Specs.
|
// TODO: 2022-03-03 add token revoked check, when we implemented the Token Revocation(rfc7009) Specs.
|
||||||
// refs: https://tools.ietf.org/html/rfc7009
|
// refs: https://tools.ietf.org/html/rfc7009
|
||||||
util.LogWarning(c.Ctx, "token invalid")
|
|
||||||
c.Data["json"] = &object.IntrospectionResponse{Active: false}
|
c.Data["json"] = &object.IntrospectionResponse{Active: false}
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
return
|
return
|
||||||
|
@ -23,4 +23,7 @@ type TokenRequest struct {
|
|||||||
Scope string `json:"scope"`
|
Scope string `json:"scope"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
|
Tag string `json:"tag"`
|
||||||
|
Avatar string `json:"avatar"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
}
|
}
|
||||||
|
@ -87,6 +87,17 @@ func (c *ApiController) GetUser() {
|
|||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
owner := c.Input().Get("owner")
|
owner := c.Input().Get("owner")
|
||||||
email := c.Input().Get("email")
|
email := c.Input().Get("email")
|
||||||
|
userOwner, _ := util.GetOwnerAndNameFromId(id)
|
||||||
|
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", userOwner))
|
||||||
|
|
||||||
|
if !organization.IsProfilePublic {
|
||||||
|
requestUserId := c.GetSessionUsername()
|
||||||
|
hasPermission, err := object.CheckUserPermission(requestUserId, id, false)
|
||||||
|
if !hasPermission {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var user *object.User
|
var user *object.User
|
||||||
if email == "" {
|
if email == "" {
|
||||||
@ -111,6 +122,10 @@ func (c *ApiController) UpdateUser() {
|
|||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
columnsStr := c.Input().Get("columns")
|
columnsStr := c.Input().Get("columns")
|
||||||
|
|
||||||
|
if id == "" {
|
||||||
|
id = c.GetSessionUsername()
|
||||||
|
}
|
||||||
|
|
||||||
var user object.User
|
var user object.User
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -229,39 +244,15 @@ func (c *ApiController) SetPassword() {
|
|||||||
newPassword := c.Ctx.Request.Form.Get("newPassword")
|
newPassword := c.Ctx.Request.Form.Get("newPassword")
|
||||||
|
|
||||||
requestUserId := c.GetSessionUsername()
|
requestUserId := c.GetSessionUsername()
|
||||||
if requestUserId == "" {
|
|
||||||
c.ResponseError("Please login first")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
userId := fmt.Sprintf("%s/%s", userOwner, userName)
|
userId := fmt.Sprintf("%s/%s", userOwner, userName)
|
||||||
targetUser := object.GetUser(userId)
|
|
||||||
if targetUser == nil {
|
hasPermission, err := object.CheckUserPermission(requestUserId, userId, true)
|
||||||
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId))
|
if !hasPermission {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
hasPermission := false
|
targetUser := object.GetUser(userId)
|
||||||
if strings.HasPrefix(requestUserId, "app/") {
|
|
||||||
hasPermission = true
|
|
||||||
} else {
|
|
||||||
requestUser := object.GetUser(requestUserId)
|
|
||||||
if requestUser == nil {
|
|
||||||
c.ResponseError("Session outdated. Please login again.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if requestUser.IsGlobalAdmin {
|
|
||||||
hasPermission = true
|
|
||||||
} else if requestUserId == userId {
|
|
||||||
hasPermission = true
|
|
||||||
} else if targetUser.Owner == requestUser.Owner && requestUser.IsAdmin {
|
|
||||||
hasPermission = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !hasPermission {
|
|
||||||
c.ResponseError("You don't have the permission to do this.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if oldPassword != "" {
|
if oldPassword != "" {
|
||||||
msg := object.CheckPassword(targetUser, oldPassword)
|
msg := object.CheckPassword(targetUser, oldPassword)
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/captcha"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
@ -48,21 +49,29 @@ func (c *ApiController) SendVerificationCode() {
|
|||||||
checkUser := c.Ctx.Request.Form.Get("checkUser")
|
checkUser := c.Ctx.Request.Form.Get("checkUser")
|
||||||
remoteAddr := util.GetIPFromRequest(c.Ctx.Request)
|
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 {
|
if len(destType) == 0 || len(dest) == 0 || len(orgId) == 0 || !strings.Contains(orgId, "/") || len(checkType) == 0 {
|
||||||
c.ResponseError("Missing parameter.")
|
c.ResponseError("Missing parameter.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
isHuman := false
|
captchaProvider := captcha.GetCaptchaProvider(checkType)
|
||||||
captchaProvider := object.GetDefaultHumanCheckProvider()
|
|
||||||
if captchaProvider == nil {
|
if captchaProvider != nil {
|
||||||
isHuman = object.VerifyCaptcha(checkId, checkKey)
|
if checkKey == "" {
|
||||||
|
c.ResponseError("Missing parameter: checkKey.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isHuman, err := captchaProvider.VerifyCaptcha(checkKey, checkId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isHuman {
|
if !isHuman {
|
||||||
c.ResponseError("Turing test failed.")
|
c.ResponseError("Turing test failed.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
user := c.getCurrentUser()
|
user := c.getCurrentUser()
|
||||||
organization := object.GetOrganization(orgId)
|
organization := object.GetOrganization(orgId)
|
||||||
@ -173,3 +182,36 @@ func (c *ApiController) ResetEmailOrPhone() {
|
|||||||
c.Data["json"] = Response{Status: "ok"}
|
c.Data["json"] = Response{Status: "ok"}
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VerifyCaptcha ...
|
||||||
|
// @Title VerifyCaptcha
|
||||||
|
// @Tag Verification API
|
||||||
|
// @router /verify-captcha [post]
|
||||||
|
func (c *ApiController) VerifyCaptcha() {
|
||||||
|
captchaType := c.Ctx.Request.Form.Get("captchaType")
|
||||||
|
|
||||||
|
captchaToken := c.Ctx.Request.Form.Get("captchaToken")
|
||||||
|
clientSecret := c.Ctx.Request.Form.Get("clientSecret")
|
||||||
|
if captchaToken == "" {
|
||||||
|
c.ResponseError("Missing parameter: captchaToken.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if clientSecret == "" {
|
||||||
|
c.ResponseError("Missing parameter: clientSecret.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
provider := captcha.GetCaptchaProvider(captchaType)
|
||||||
|
if provider == nil {
|
||||||
|
c.ResponseError("Invalid captcha provider.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isValid, err := provider.VerifyCaptcha(captchaToken, clientSecret)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(isValid)
|
||||||
|
}
|
||||||
|
38
cred/argon2id.go
Normal file
38
cred/argon2id.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// 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 cred
|
||||||
|
|
||||||
|
import "github.com/alexedwards/argon2id"
|
||||||
|
|
||||||
|
type Argon2idCredManager struct{}
|
||||||
|
|
||||||
|
func NewArgon2idCredManager() *Argon2idCredManager {
|
||||||
|
cm := &Argon2idCredManager{}
|
||||||
|
return cm
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cm *Argon2idCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
||||||
|
|
||||||
|
hash, err := argon2id.CreateHash(password, argon2id.DefaultParams)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return hash
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cm *Argon2idCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
|
||||||
|
match, _ := argon2id.ComparePasswordAndHash(plainPwd, hashedPwd)
|
||||||
|
return match
|
||||||
|
}
|
@ -28,6 +28,10 @@ func GetCredManager(passwordType string) CredManager {
|
|||||||
return NewMd5UserSaltCredManager()
|
return NewMd5UserSaltCredManager()
|
||||||
} else if passwordType == "bcrypt" {
|
} else if passwordType == "bcrypt" {
|
||||||
return NewBcryptCredManager()
|
return NewBcryptCredManager()
|
||||||
|
} else if passwordType == "pbkdf2-salt" {
|
||||||
|
return NewPbkdf2SaltCredManager()
|
||||||
|
} else if passwordType == "argon2id" {
|
||||||
|
return NewArgon2idCredManager()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -32,14 +32,16 @@ func getMd5HexDigest(s string) string {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMd5UserSaltCredManager() *Sha256SaltCredManager {
|
func NewMd5UserSaltCredManager() *Md5UserSaltCredManager {
|
||||||
cm := &Sha256SaltCredManager{}
|
cm := &Md5UserSaltCredManager{}
|
||||||
return cm
|
return cm
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *Md5UserSaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
func (cm *Md5UserSaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
||||||
hash := getMd5HexDigest(password)
|
res := getMd5HexDigest(password)
|
||||||
res := getMd5HexDigest(hash + userSalt)
|
if userSalt != "" {
|
||||||
|
res = getMd5HexDigest(res + userSalt)
|
||||||
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
39
cred/pbkdf2-salt.go
Normal file
39
cred/pbkdf2-salt.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// 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 cred
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"golang.org/x/crypto/pbkdf2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Pbkdf2SaltCredManager struct{}
|
||||||
|
|
||||||
|
func NewPbkdf2SaltCredManager() *Pbkdf2SaltCredManager {
|
||||||
|
cm := &Pbkdf2SaltCredManager{}
|
||||||
|
return cm
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cm *Pbkdf2SaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
||||||
|
// https://www.keycloak.org/docs/latest/server_admin/index.html#password-database-compromised
|
||||||
|
decodedSalt, _ := base64.StdEncoding.DecodeString(userSalt)
|
||||||
|
res := pbkdf2.Key([]byte(password), decodedSalt, 27500, 64, sha256.New)
|
||||||
|
return base64.StdEncoding.EncodeToString(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cm *Pbkdf2SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
|
||||||
|
return hashedPwd == cm.GetHashedPassword(plainPwd, userSalt, organizationSalt)
|
||||||
|
}
|
@ -38,8 +38,10 @@ func NewSha256SaltCredManager() *Sha256SaltCredManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cm *Sha256SaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
func (cm *Sha256SaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
||||||
hash := getSha256HexDigest(password)
|
res := getSha256HexDigest(password)
|
||||||
res := getSha256HexDigest(hash + organizationSalt)
|
if organizationSalt != "" {
|
||||||
|
res = getSha256HexDigest(res + organizationSalt)
|
||||||
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,3 +25,10 @@ func TestGetSaltedPassword(t *testing.T) {
|
|||||||
cm := NewSha256SaltCredManager()
|
cm := NewSha256SaltCredManager()
|
||||||
fmt.Printf("%s -> %s\n", password, cm.GetHashedPassword(password, "", salt))
|
fmt.Printf("%s -> %s\n", password, cm.GetHashedPassword(password, "", salt))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetPassword(t *testing.T) {
|
||||||
|
password := "123456"
|
||||||
|
cm := NewSha256SaltCredManager()
|
||||||
|
// https://passwordsgenerator.net/sha256-hash-generator/
|
||||||
|
fmt.Printf("%s -> %s\n", "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92", cm.GetHashedPassword(password, "", ""))
|
||||||
|
}
|
||||||
|
@ -5,14 +5,14 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ./
|
context: ./
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
target: STANDARD
|
||||||
|
entrypoint: /bin/sh -c './server --createDatabase=true'
|
||||||
ports:
|
ports:
|
||||||
- "8000:8000"
|
- "8000:8000"
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
environment:
|
environment:
|
||||||
RUNNING_IN_DOCKER: "true"
|
RUNNING_IN_DOCKER: "true"
|
||||||
extra_hosts:
|
|
||||||
- "host.docker.internal:host-gateway"
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./conf:/conf/
|
- ./conf:/conf/
|
||||||
db:
|
db:
|
||||||
|
7
docker-entrypoint.sh
Normal file
7
docker-entrypoint.sh
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
service mariadb start
|
||||||
|
|
||||||
|
mysqladmin -u root password ${MYSQL_ROOT_PASSWORD}
|
||||||
|
|
||||||
|
exec /app/server --createDatabase=true
|
18
go.mod
18
go.mod
@ -3,14 +3,16 @@ module github.com/casdoor/casdoor
|
|||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible // indirect
|
github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b
|
||||||
|
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387
|
||||||
github.com/astaxie/beego v1.12.3
|
github.com/astaxie/beego v1.12.3
|
||||||
github.com/aws/aws-sdk-go v1.37.30
|
github.com/aws/aws-sdk-go v1.44.4
|
||||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
|
github.com/beevik/etree v1.1.0
|
||||||
github.com/casbin/casbin/v2 v2.30.1
|
github.com/casbin/casbin/v2 v2.30.1
|
||||||
github.com/casbin/xorm-adapter/v2 v2.5.1
|
github.com/casbin/xorm-adapter/v2 v2.5.1
|
||||||
github.com/casdoor/go-sms-sender v0.2.0
|
github.com/casdoor/go-sms-sender v0.2.0
|
||||||
github.com/casdoor/goth v1.69.0-FIX1
|
github.com/casdoor/goth v1.69.0-FIX1
|
||||||
|
github.com/casdoor/oss v1.2.0
|
||||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||||
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
|
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
|
||||||
github.com/go-ldap/ldap/v3 v3.3.0
|
github.com/go-ldap/ldap/v3 v3.3.0
|
||||||
@ -18,26 +20,26 @@ require (
|
|||||||
github.com/go-sql-driver/mysql v1.5.0
|
github.com/go-sql-driver/mysql v1.5.0
|
||||||
github.com/golang-jwt/jwt/v4 v4.2.0
|
github.com/golang-jwt/jwt/v4 v4.2.0
|
||||||
github.com/google/uuid v1.2.0
|
github.com/google/uuid v1.2.0
|
||||||
github.com/jinzhu/configor v1.2.1 // indirect
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||||
github.com/lestrrat-go/jwx v0.9.0
|
github.com/lestrrat-go/jwx v0.9.0
|
||||||
|
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
||||||
github.com/qiangmzsx/string-adapter/v2 v2.1.0
|
github.com/qiangmzsx/string-adapter/v2 v2.1.0
|
||||||
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76
|
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/russellhaering/gosaml2 v0.6.0
|
github.com/russellhaering/gosaml2 v0.6.0
|
||||||
github.com/russellhaering/goxmldsig v1.1.1
|
github.com/russellhaering/goxmldsig v1.1.1
|
||||||
|
github.com/satori/go.uuid v1.2.0
|
||||||
github.com/smartystreets/goconvey v1.6.4 // indirect
|
github.com/smartystreets/goconvey v1.6.4 // indirect
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.0
|
||||||
github.com/tealeg/xlsx v1.0.5
|
github.com/tealeg/xlsx v1.0.5
|
||||||
github.com/thanhpk/randstr v1.0.4
|
github.com/thanhpk/randstr v1.0.4
|
||||||
golang.org/x/crypto v0.0.0-20220208233918-bba287dce954
|
golang.org/x/crypto v0.0.0-20220208233918-bba287dce954
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
|
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd
|
||||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
|
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
|
||||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
|
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
|
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
|
||||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0
|
gopkg.in/square/go-jose.v2 v2.6.0
|
||||||
gopkg.in/yaml.v2 v2.3.0 // indirect
|
gopkg.in/yaml.v2 v2.3.0 // indirect
|
||||||
xorm.io/core v0.7.2
|
xorm.io/core v0.7.2
|
||||||
xorm.io/xorm v1.0.3
|
xorm.io/xorm v1.0.4
|
||||||
)
|
)
|
||||||
|
80
go.sum
80
go.sum
@ -35,6 +35,21 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
|
|||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
||||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
||||||
|
github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U=
|
||||||
|
github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
|
||||||
|
github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk=
|
||||||
|
github.com/Azure/azure-storage-blob-go v0.15.0/go.mod h1:vbjsVbX0dlxnRc4FFMPsS9BsJWPcne7GB7onqlPvz58=
|
||||||
|
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
|
||||||
|
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||||
|
github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q=
|
||||||
|
github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
|
||||||
|
github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
|
||||||
|
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
|
||||||
|
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
|
||||||
|
github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
|
||||||
|
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
|
||||||
|
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
|
||||||
|
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||||
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
|
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
|
||||||
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||||
@ -44,22 +59,26 @@ github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8L
|
|||||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=
|
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=
|
||||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||||
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
||||||
|
github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b h1:EgJ6N2S0h1WfFIjU5/VVHWbMSVYXAluop97Qxpr/lfQ=
|
||||||
|
github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b/go.mod h1:3SAoF0F5EbcOuBD5WT9nYkbIJieBS84cUQXADbXeBsU=
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
|
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387 h1:loy0fjI90vF44BPW4ZYOkE3tDkGTy7yHURusOJimt+I=
|
||||||
|
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387/go.mod h1:GuR5j/NW7AU7tDAQUDGCtpiPxWIOy/c3kiRDnlwiCHc=
|
||||||
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||||
github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
|
github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
|
||||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1075 h1:Z0SzZttfYI/raZ5O9WF3cezZJTSW4Yz4Kow9uWdyRwg=
|
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1075 h1:Z0SzZttfYI/raZ5O9WF3cezZJTSW4Yz4Kow9uWdyRwg=
|
||||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1075/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA=
|
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1075/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA=
|
||||||
github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible h1:Ft+KeWIJxFP76LqgJbvtOA1qBIoC8vGkTV3QeCOeJC4=
|
github.com/aliyun/aliyun-oss-go-sdk v2.2.2+incompatible h1:9gWa46nstkJ9miBReJcN8Gq34cBFbzSpQZVVT9N09TM=
|
||||||
github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
github.com/aliyun/aliyun-oss-go-sdk v2.2.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||||
github.com/astaxie/beego v1.12.3 h1:SAQkdD2ePye+v8Gn1r4X6IKZM1wd28EyUOVQ3PDSOOQ=
|
github.com/astaxie/beego v1.12.3 h1:SAQkdD2ePye+v8Gn1r4X6IKZM1wd28EyUOVQ3PDSOOQ=
|
||||||
github.com/astaxie/beego v1.12.3/go.mod h1:p3qIm0Ryx7zeBHLljmd7omloyca1s4yu1a8kM1FkpIA=
|
github.com/astaxie/beego v1.12.3/go.mod h1:p3qIm0Ryx7zeBHLljmd7omloyca1s4yu1a8kM1FkpIA=
|
||||||
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
|
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
|
||||||
github.com/aws/aws-sdk-go v1.37.30 h1:fZeVg3QuTkWE/dEvPQbK6AL32+3G9ofJfGFSPS1XLH0=
|
github.com/aws/aws-sdk-go v1.44.4 h1:ePN0CVJMdiz2vYUcJH96eyxRrtKGSDMgyhP6rah2OgE=
|
||||||
github.com/aws/aws-sdk-go v1.37.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
github.com/aws/aws-sdk-go v1.44.4/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
||||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA=
|
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA=
|
||||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
|
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
|
||||||
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
|
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
|
||||||
@ -83,6 +102,8 @@ github.com/casdoor/go-sms-sender v0.2.0 h1:52bin4EBOPzOee64s9UK7jxd22FODvT9/+Y/Z
|
|||||||
github.com/casdoor/go-sms-sender v0.2.0/go.mod h1:fsZsNnALvFIo+HFcE1U/oCQv4ZT42FdglXKMsEm3WSk=
|
github.com/casdoor/go-sms-sender v0.2.0/go.mod h1:fsZsNnALvFIo+HFcE1U/oCQv4ZT42FdglXKMsEm3WSk=
|
||||||
github.com/casdoor/goth v1.69.0-FIX1 h1:24Y3tfaJxWGJbxickGe3F9y2c8X1PgsQynhxGXV1f9Q=
|
github.com/casdoor/goth v1.69.0-FIX1 h1:24Y3tfaJxWGJbxickGe3F9y2c8X1PgsQynhxGXV1f9Q=
|
||||||
github.com/casdoor/goth v1.69.0-FIX1/go.mod h1:Om55nRo8CkeDkPSNBbzXW4G5uI28ZUkSk5S69dPek3s=
|
github.com/casdoor/goth v1.69.0-FIX1/go.mod h1:Om55nRo8CkeDkPSNBbzXW4G5uI28ZUkSk5S69dPek3s=
|
||||||
|
github.com/casdoor/oss v1.2.0 h1:ozLAE+nnNdFQBWbzH8U9spzaO8h8NrB57lBcdyMUUQ8=
|
||||||
|
github.com/casdoor/oss v1.2.0/go.mod h1:qii35VBuxnR/uEuYSKpS0aJ8htQFOcCVsZ4FHgHLuss=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
@ -111,6 +132,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
|
|||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
|
||||||
|
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw=
|
github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw=
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8=
|
github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8=
|
||||||
@ -128,6 +151,12 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
|
|||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
github.com/go-pay/gopay v1.5.72 h1:3zm64xMBhJBa8rXbm//q5UiGgOa4WO5XYEnU394N2Zw=
|
github.com/go-pay/gopay v1.5.72 h1:3zm64xMBhJBa8rXbm//q5UiGgOa4WO5XYEnU394N2Zw=
|
||||||
github.com/go-pay/gopay v1.5.72/go.mod h1:0qOGIJuFW7PKDOjmecwKyW0mgsVImgwB9yPJj0ilpn8=
|
github.com/go-pay/gopay v1.5.72/go.mod h1:0qOGIJuFW7PKDOjmecwKyW0mgsVImgwB9yPJj0ilpn8=
|
||||||
|
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||||
|
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||||
|
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||||
|
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||||
|
github.com/go-playground/validator/v10 v10.8.0/go.mod h1:9JhgTzTaE31GZDpH/HSvHiRJrJ3iKAgqqH0Bl/Ocjdk=
|
||||||
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||||
@ -235,6 +264,8 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
|
|||||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
|
||||||
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
@ -248,6 +279,7 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ=
|
github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ=
|
||||||
|
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||||
github.com/lestrrat-go/jwx v0.9.0 h1:Fnd0EWzTm0kFrBPzE/PEPp9nzllES5buMkksPMjEKpM=
|
github.com/lestrrat-go/jwx v0.9.0 h1:Fnd0EWzTm0kFrBPzE/PEPp9nzllES5buMkksPMjEKpM=
|
||||||
github.com/lestrrat-go/jwx v0.9.0/go.mod h1:iEoxlYfZjvoGpuWwxUz+eR5e6KTJGsaRcy/YNA/UnBk=
|
github.com/lestrrat-go/jwx v0.9.0/go.mod h1:iEoxlYfZjvoGpuWwxUz+eR5e6KTJGsaRcy/YNA/UnBk=
|
||||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
@ -258,6 +290,8 @@ github.com/markbates/going v1.0.0 h1:DQw0ZP7NbNlFGcKbcE/IVSOAFzScxRtLpd0rLMzLhq0
|
|||||||
github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA=
|
github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA=
|
||||||
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911 h1:erppMjjp69Rertg1zlgRbLJH1u+eCmRPxKjMZ5I8/Ro=
|
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911 h1:erppMjjp69Rertg1zlgRbLJH1u+eCmRPxKjMZ5I8/Ro=
|
||||||
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To=
|
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To=
|
||||||
|
github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI=
|
||||||
|
github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
|
||||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
||||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
||||||
@ -274,6 +308,8 @@ github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c h1:3wkDRdxK92dF+c1ke
|
|||||||
github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=
|
github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
|
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
|
||||||
|
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=
|
github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=
|
||||||
@ -309,8 +345,9 @@ github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFB
|
|||||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||||
github.com/qiangmzsx/string-adapter/v2 v2.1.0 h1:q0y8TPa/sTwtriJPRe8gWL++PuZ+XbOUuvKU+hvtTYs=
|
github.com/qiangmzsx/string-adapter/v2 v2.1.0 h1:q0y8TPa/sTwtriJPRe8gWL++PuZ+XbOUuvKU+hvtTYs=
|
||||||
github.com/qiangmzsx/string-adapter/v2 v2.1.0/go.mod h1:PElPB7b7HnGKTsuADAffFpOQXHqjEGJz1+U1a6yR5wA=
|
github.com/qiangmzsx/string-adapter/v2 v2.1.0/go.mod h1:PElPB7b7HnGKTsuADAffFpOQXHqjEGJz1+U1a6yR5wA=
|
||||||
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76 h1:J2Xj92efYLxPl3BiibgEDEUiMsCBzwTurE/8JjD8CG4=
|
github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk=
|
||||||
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76/go.mod h1:JhtPzUhP5KGtCB2yksmxuYAD4hEWw4qGQJpucjsm3U0=
|
github.com/qiniu/go-sdk/v7 v7.12.1/go.mod h1:btsaOc8CA3hdVloULfFdDgDc+g4f3TDZEFsDY0BLE+w=
|
||||||
|
github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
@ -379,6 +416,11 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
|
|||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.0.0-20220208233918-bba287dce954 h1:BkypuErRT9A9I/iljuaG3/zdMjd/J6m8tKKJQtGfSdA=
|
golang.org/x/crypto v0.0.0-20220208233918-bba287dce954 h1:BkypuErRT9A9I/iljuaG3/zdMjd/J6m8tKKJQtGfSdA=
|
||||||
golang.org/x/crypto v0.0.0-20220208233918-bba287dce954/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220208233918-bba287dce954/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
@ -427,6 +469,7 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
@ -443,9 +486,11 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
|
|||||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
|
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
|
||||||
|
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@ -462,6 +507,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@ -476,6 +522,7 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -495,24 +542,28 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211020174200-9d6173849985/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
|
||||||
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
|
golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w=
|
||||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
@ -688,5 +739,6 @@ xorm.io/builder v0.3.7 h1:2pETdKRK+2QG4mLX4oODHEhn5Z8j1m8sXa7jfu+/SZI=
|
|||||||
xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
||||||
xorm.io/core v0.7.2 h1:mEO22A2Z7a3fPaZMk6gKL/jMD80iiyNwRrX5HOv3XLw=
|
xorm.io/core v0.7.2 h1:mEO22A2Z7a3fPaZMk6gKL/jMD80iiyNwRrX5HOv3XLw=
|
||||||
xorm.io/core v0.7.2/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM=
|
xorm.io/core v0.7.2/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM=
|
||||||
xorm.io/xorm v1.0.3 h1:3dALAohvINu2mfEix5a5x5ZmSVGSljinoSGgvGbaZp0=
|
|
||||||
xorm.io/xorm v1.0.3/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4=
|
xorm.io/xorm v1.0.3/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4=
|
||||||
|
xorm.io/xorm v1.0.4 h1:UBXA4I3NhiyjXfPqxXUkS2t5hMta9SSPATeMMaZg9oA=
|
||||||
|
xorm.io/xorm v1.0.4/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4=
|
||||||
|
221
idp/bilibili.go
Normal file
221
idp/bilibili.go
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
// 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 idp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BilibiliIdProvider struct {
|
||||||
|
Client *http.Client
|
||||||
|
Config *oauth2.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBilibiliIdProvider(clientId string, clientSecret string, redirectUrl string) *BilibiliIdProvider {
|
||||||
|
idp := &BilibiliIdProvider{}
|
||||||
|
|
||||||
|
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||||
|
idp.Config = config
|
||||||
|
|
||||||
|
return idp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *BilibiliIdProvider) SetHttpClient(client *http.Client) {
|
||||||
|
idp.Client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
|
||||||
|
func (idp *BilibiliIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||||
|
var endpoint = oauth2.Endpoint{
|
||||||
|
TokenURL: "https://api.bilibili.com/x/account-oauth2/v1/token",
|
||||||
|
AuthURL: "http://member.bilibili.com/arcopen/fn/user/account/info",
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = &oauth2.Config{
|
||||||
|
Scopes: []string{"", ""},
|
||||||
|
Endpoint: endpoint,
|
||||||
|
ClientID: clientId,
|
||||||
|
ClientSecret: clientSecret,
|
||||||
|
RedirectURL: redirectUrl,
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
type BilibiliProviderToken struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BilibiliIdProviderTokenResponse struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
TTL int `json:"ttl"`
|
||||||
|
Data BilibiliProviderToken `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "0",
|
||||||
|
"ttl": 1,
|
||||||
|
"data": {
|
||||||
|
"access_token": "d30bedaa4d8eb3128cf35ddc1030e27d",
|
||||||
|
"expires_in": 1630220614,
|
||||||
|
"refresh_token": "WxFDKwqScZIQDm4iWmKDvetyFugM6HkX"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
// GetToken use code get access_token (*operation of getting code ought to be done in front)
|
||||||
|
// get more detail via: https://openhome.bilibili.com/doc/4/eaf0e2b5-bde9-b9a0-9be1-019bb455701c
|
||||||
|
func (idp *BilibiliIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||||
|
pTokenParams := &struct {
|
||||||
|
ClientId string `json:"client_id"`
|
||||||
|
ClientSecret string `json:"client_secret"`
|
||||||
|
GrantType string `json:"grant_type"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
}{
|
||||||
|
idp.Config.ClientID,
|
||||||
|
idp.Config.ClientSecret,
|
||||||
|
"authorization_code",
|
||||||
|
code,
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := idp.postWithBody(pTokenParams, idp.Config.Endpoint.TokenURL)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &BilibiliIdProviderTokenResponse{}
|
||||||
|
err = json.Unmarshal(data, response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.Code != 0 {
|
||||||
|
return nil, fmt.Errorf("pToken.Errcode = %d, pToken.Errmsg = %s", response.Code, response.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
token := &oauth2.Token{
|
||||||
|
AccessToken: response.Data.AccessToken,
|
||||||
|
Expiry: time.Unix(time.Now().Unix()+int64(response.Data.ExpiresIn), 0),
|
||||||
|
RefreshToken: response.Data.RefreshToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "0",
|
||||||
|
"ttl": 1,
|
||||||
|
"data": {
|
||||||
|
"name":"bilibili",
|
||||||
|
"face":"http://i0.hdslb.com/bfs/face/e1c99895a9f9df4f260a70dc7e227bcb46cf319c.jpg",
|
||||||
|
"openid":"9205eeaa1879skxys969ed47874f225c3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
type BilibiliUserInfo struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Face string `json:"face"`
|
||||||
|
OpenId string `json:"openid`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BilibiliUserInfoResponse struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
TTL int `json:"ttl"`
|
||||||
|
Data BilibiliUserInfo `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserInfo Use access_token to get UserInfo
|
||||||
|
// get more detail via: https://openhome.bilibili.com/doc/4/feb66f99-7d87-c206-00e7-d84164cd701c
|
||||||
|
func (idp *BilibiliIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||||
|
accessToken := token.AccessToken
|
||||||
|
clientId := idp.Config.ClientID
|
||||||
|
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("client_id", clientId)
|
||||||
|
params.Add("access_token", accessToken)
|
||||||
|
|
||||||
|
userInfoUrl := fmt.Sprintf("%s?%s", idp.Config.Endpoint.AuthURL, params.Encode())
|
||||||
|
|
||||||
|
resp, err := idp.Client.Get(userInfoUrl)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bUserInfoResponse := &BilibiliUserInfoResponse{}
|
||||||
|
if err = json.Unmarshal(data, bUserInfoResponse); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if bUserInfoResponse.Code != 0 {
|
||||||
|
return nil, fmt.Errorf("userinfo.Errcode = %d, userinfo.Errmsg = %s", bUserInfoResponse.Code, bUserInfoResponse.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo := &UserInfo{
|
||||||
|
Id: bUserInfoResponse.Data.OpenId,
|
||||||
|
Username: bUserInfoResponse.Data.Name,
|
||||||
|
DisplayName: bUserInfoResponse.Data.Name,
|
||||||
|
AvatarUrl: bUserInfoResponse.Data.Face,
|
||||||
|
}
|
||||||
|
|
||||||
|
return userInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *BilibiliIdProvider) postWithBody(body interface{}, url string) ([]byte, error) {
|
||||||
|
bs, err := json.Marshal(body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r := strings.NewReader(string(bs))
|
||||||
|
resp, err := idp.Client.Post(url, "application/json;charset=UTF-8", r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func(Body io.ReadCloser) {
|
||||||
|
err := Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}(resp.Body)
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
@ -131,6 +131,7 @@ func (idp *CasdoorIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
data, err := ioutil.ReadAll(resp.Body)
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
109
idp/custom.go
Normal file
109
idp/custom.go
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
// 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 idp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
_ "net/url"
|
||||||
|
_ "time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CustomIdProvider struct {
|
||||||
|
Client *http.Client
|
||||||
|
Config *oauth2.Config
|
||||||
|
UserInfoUrl string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCustomIdProvider(clientId string, clientSecret string, redirectUrl string, authUrl string, tokenUrl string, userInfoUrl string) *CustomIdProvider {
|
||||||
|
idp := &CustomIdProvider{}
|
||||||
|
idp.UserInfoUrl = userInfoUrl
|
||||||
|
|
||||||
|
var config = &oauth2.Config{
|
||||||
|
ClientID: clientId,
|
||||||
|
ClientSecret: clientSecret,
|
||||||
|
RedirectURL: redirectUrl,
|
||||||
|
Endpoint: oauth2.Endpoint{
|
||||||
|
AuthURL: authUrl,
|
||||||
|
TokenURL: tokenUrl,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
idp.Config = config
|
||||||
|
|
||||||
|
return idp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *CustomIdProvider) SetHttpClient(client *http.Client) {
|
||||||
|
idp.Client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *CustomIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||||
|
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, idp.Client)
|
||||||
|
return idp.Config.Exchange(ctx, code)
|
||||||
|
}
|
||||||
|
|
||||||
|
type CustomUserInfo struct {
|
||||||
|
Id string `json:"sub"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
DisplayName string `json:"preferred_username"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
AvatarUrl string `json:"picture"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *CustomIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||||
|
ctUserinfo := &CustomUserInfo{}
|
||||||
|
accessToken := token.AccessToken
|
||||||
|
request, err := http.NewRequest("GET", idp.UserInfoUrl, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
//add accessToken to request header
|
||||||
|
request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", accessToken))
|
||||||
|
resp, err := idp.Client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(data, ctUserinfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctUserinfo.Status != "" {
|
||||||
|
return nil, fmt.Errorf("err: %s", ctUserinfo.Msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo := &UserInfo{
|
||||||
|
Id: ctUserinfo.Id,
|
||||||
|
Username: ctUserinfo.Name,
|
||||||
|
DisplayName: ctUserinfo.DisplayName,
|
||||||
|
Email: ctUserinfo.Email,
|
||||||
|
AvatarUrl: ctUserinfo.AvatarUrl,
|
||||||
|
}
|
||||||
|
return userInfo, nil
|
||||||
|
}
|
@ -143,6 +143,7 @@ func (idp *DingTalkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
data, err := ioutil.ReadAll(resp.Body)
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
198
idp/douyin.go
Normal file
198
idp/douyin.go
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
// 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 idp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DouyinIdProvider struct {
|
||||||
|
Client *http.Client
|
||||||
|
Config *oauth2.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDouyinIdProvider(clientId string, clientSecret string, redirectUrl string) *DouyinIdProvider {
|
||||||
|
idp := &DouyinIdProvider{}
|
||||||
|
idp.Config = idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||||
|
return idp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *DouyinIdProvider) SetHttpClient(client *http.Client) {
|
||||||
|
idp.Client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *DouyinIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||||
|
var endpoint = oauth2.Endpoint{
|
||||||
|
TokenURL: "https://open.douyin.com/oauth/access_token",
|
||||||
|
AuthURL: "https://open.douyin.com/platform/oauth/connect",
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = &oauth2.Config{
|
||||||
|
Scopes: []string{"user_info"},
|
||||||
|
Endpoint: endpoint,
|
||||||
|
ClientID: clientId,
|
||||||
|
ClientSecret: clientSecret,
|
||||||
|
RedirectURL: redirectUrl,
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
// get more details via: https://open.douyin.com/platform/doc?doc=docs/openapi/account-permission/get-access-token
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"access_token": "access_token",
|
||||||
|
"description": "",
|
||||||
|
"error_code": "0",
|
||||||
|
"expires_in": "86400",
|
||||||
|
"open_id": "aaa-bbb-ccc",
|
||||||
|
"refresh_expires_in": "86400",
|
||||||
|
"refresh_token": "refresh_token",
|
||||||
|
"scope": "user_info"
|
||||||
|
},
|
||||||
|
"message": "<nil>"
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
type DouyinTokenResp struct {
|
||||||
|
Data struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
ExpiresIn int64 `json:"expires_in"`
|
||||||
|
OpenId string `json:"open_id"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
Scope string `json:"scope"`
|
||||||
|
} `json:"data"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetToken use code to get access_token
|
||||||
|
// get more details via: https://open.douyin.com/platform/doc?doc=docs/openapi/account-permission/get-access-token
|
||||||
|
func (idp *DouyinIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||||
|
payload := url.Values{}
|
||||||
|
payload.Set("code", code)
|
||||||
|
payload.Set("grant_type", "authorization_code")
|
||||||
|
payload.Set("client_key", idp.Config.ClientID)
|
||||||
|
payload.Set("client_secret", idp.Config.ClientSecret)
|
||||||
|
resp, err := idp.Client.PostForm(idp.Config.Endpoint.TokenURL, payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tokenResp := &DouyinTokenResp{}
|
||||||
|
err = json.Unmarshal(data, tokenResp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("fail to unmarshal token response: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
token := &oauth2.Token{
|
||||||
|
AccessToken: tokenResp.Data.AccessToken,
|
||||||
|
RefreshToken: tokenResp.Data.RefreshToken,
|
||||||
|
Expiry: time.Unix(time.Now().Unix()+tokenResp.Data.ExpiresIn, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
raw := make(map[string]interface{})
|
||||||
|
raw["open_id"] = tokenResp.Data.OpenId
|
||||||
|
token = token.WithExtra(raw)
|
||||||
|
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// get more details via: https://open.douyin.com/platform/doc?doc=docs/openapi/account-management/get-account-open-info
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"avatar": "https://example.com/x.jpeg",
|
||||||
|
"city": "上海",
|
||||||
|
"country": "中国",
|
||||||
|
"description": "",
|
||||||
|
"e_account_role": "<nil>",
|
||||||
|
"error_code": "0",
|
||||||
|
"gender": "<nil>",
|
||||||
|
"nickname": "张伟",
|
||||||
|
"open_id": "0da22181-d833-447f-995f-1beefea5bef3",
|
||||||
|
"province": "上海",
|
||||||
|
"union_id": "1ad4e099-4a0c-47d1-a410-bffb4f2f64a4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
type DouyinUserInfo struct {
|
||||||
|
Data struct {
|
||||||
|
Avatar string `json:"avatar"`
|
||||||
|
City string `json:"city"`
|
||||||
|
Country string `json:"country"`
|
||||||
|
// 0->unknown, 1->male, 2->female
|
||||||
|
Gender int64 `json:"gender"`
|
||||||
|
Nickname string `json:"nickname"`
|
||||||
|
OpenId string `json:"open_id"`
|
||||||
|
Province string `json:"province"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserInfo use token to get user profile
|
||||||
|
func (idp *DouyinIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||||
|
body := &struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
OpenId string `json:"open_id"`
|
||||||
|
}{token.AccessToken, token.Extra("open_id").(string)}
|
||||||
|
data, err := json.Marshal(body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest("GET", "https://open.douyin.com/oauth/userinfo/", bytes.NewReader(data))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Add("access-token", token.AccessToken)
|
||||||
|
req.Header.Add("Accept", "application/json")
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
|
resp, err := idp.Client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
respBody, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var douyinUserInfo DouyinUserInfo
|
||||||
|
err = json.Unmarshal(respBody, &douyinUserInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo := UserInfo{
|
||||||
|
Id: douyinUserInfo.Data.OpenId,
|
||||||
|
Username: douyinUserInfo.Data.Nickname,
|
||||||
|
DisplayName: douyinUserInfo.Data.Nickname,
|
||||||
|
AvatarUrl: douyinUserInfo.Data.Avatar,
|
||||||
|
}
|
||||||
|
return &userInfo, nil
|
||||||
|
}
|
@ -169,8 +169,11 @@ func (idp *LarkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
|||||||
req.Header.Set("Authorization", "Bearer "+token.AccessToken)
|
req.Header.Set("Authorization", "Bearer "+token.AccessToken)
|
||||||
|
|
||||||
resp, err := idp.Client.Do(req)
|
resp, err := idp.Client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
data, err = ioutil.ReadAll(resp.Body)
|
data, err = ioutil.ReadAll(resp.Body)
|
||||||
err = resp.Body.Close()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
200
idp/okta.go
Normal file
200
idp/okta.go
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
// 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 idp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OktaIdProvider struct {
|
||||||
|
Client *http.Client
|
||||||
|
Config *oauth2.Config
|
||||||
|
Host string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOktaIdProvider(clientId string, clientSecret string, redirectUrl string, hostUrl string) *OktaIdProvider {
|
||||||
|
idp := &OktaIdProvider{}
|
||||||
|
|
||||||
|
config := idp.getConfig(hostUrl, clientId, clientSecret, redirectUrl)
|
||||||
|
config.ClientID = clientId
|
||||||
|
config.ClientSecret = clientSecret
|
||||||
|
config.RedirectURL = redirectUrl
|
||||||
|
idp.Config = config
|
||||||
|
idp.Host = hostUrl
|
||||||
|
return idp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *OktaIdProvider) SetHttpClient(client *http.Client) {
|
||||||
|
idp.Client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *OktaIdProvider) getConfig(hostUrl string, clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||||
|
var endpoint = oauth2.Endpoint{
|
||||||
|
TokenURL: fmt.Sprintf("%s/v1/token", hostUrl),
|
||||||
|
AuthURL: fmt.Sprintf("%s/v1/authorize", hostUrl),
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = &oauth2.Config{
|
||||||
|
// openid is required for authentication requests
|
||||||
|
// get more details via: https://developer.okta.com/docs/reference/api/oidc/#reserved-scopes
|
||||||
|
Scopes: []string{"openid", "profile", "email"},
|
||||||
|
Endpoint: endpoint,
|
||||||
|
ClientID: clientId,
|
||||||
|
ClientSecret: clientSecret,
|
||||||
|
RedirectURL: redirectUrl,
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
// get more details via: https://developer.okta.com/docs/reference/api/oidc/#token
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"access_token" : "eyJhbGciOiJSUzI1NiJ9.eyJ2ZXIiOjEsImlzcyI6Imh0dHA6Ly9yYWluLm9rdGExLmNvbToxODAyIiwiaWF0IjoxNDQ5Nj
|
||||||
|
I0MDI2LCJleHAiOjE0NDk2Mjc2MjYsImp0aSI6IlVmU0lURzZCVVNfdHA3N21BTjJxIiwic2NvcGVzIjpbIm9wZW5pZCIsI
|
||||||
|
mVtYWlsIl0sImNsaWVudF9pZCI6InVBYXVub2ZXa2FESnh1a0NGZUJ4IiwidXNlcl9pZCI6IjAwdWlkNEJ4WHc2STZUVjRt
|
||||||
|
MGczIn0.HaBu5oQxdVCIvea88HPgr2O5evqZlCT4UXH4UKhJnZ5px-ArNRqwhxXWhHJisslswjPpMkx1IgrudQIjzGYbtLF
|
||||||
|
jrrg2ueiU5-YfmKuJuD6O2yPWGTsV7X6i7ABT6P-t8PRz_RNbk-U1GXWIEkNnEWbPqYDAm_Ofh7iW0Y8WDA5ez1jbtMvd-o
|
||||||
|
XMvJLctRiACrTMLJQ2e5HkbUFxgXQ_rFPNHJbNSUBDLqdi2rg_ND64DLRlXRY7hupNsvWGo0gF4WEUk8IZeaLjKw8UoIs-E
|
||||||
|
TEwJlAMcvkhoVVOsN5dPAaEKvbyvPC1hUGXb4uuThlwdD3ECJrtwgKqLqcWonNtiw",
|
||||||
|
"token_type" : "Bearer",
|
||||||
|
"expires_in" : 3600,
|
||||||
|
"scope" : "openid email",
|
||||||
|
"refresh_token" : "a9VpZDRCeFh3Nkk2VdY",
|
||||||
|
"id_token" : "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIwMHVpZDRCeFh3Nkk2VFY0bTBnMyIsImVtYWlsIjoid2VibWFzdGVyQGNsb3VkaXR1ZG
|
||||||
|
UubmV0IiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInZlciI6MSwiaXNzIjoiaHR0cDovL3JhaW4ub2t0YTEuY29tOjE4MDIiLCJsb
|
||||||
|
2dpbiI6ImFkbWluaXN0cmF0b3IxQGNsb3VkaXR1ZGUubmV0IiwiYXVkIjoidUFhdW5vZldrYURKeHVrQ0ZlQngiLCJpYXQiOjE0
|
||||||
|
NDk2MjQwMjYsImV4cCI6MTQ0OTYyNzYyNiwiYW1yIjpbInB3ZCJdLCJqdGkiOiI0ZUFXSk9DTUIzU1g4WGV3RGZWUiIsImF1dGh
|
||||||
|
fdGltZSI6MTQ0OTYyNDAyNiwiYXRfaGFzaCI6ImNwcUtmZFFBNWVIODkxRmY1b0pyX1EifQ.Btw6bUbZhRa89DsBb8KmL9rfhku
|
||||||
|
--_mbNC2pgC8yu8obJnwO12nFBepui9KzbpJhGM91PqJwi_AylE6rp-ehamfnUAO4JL14PkemF45Pn3u_6KKwxJnxcWxLvMuuis
|
||||||
|
nvIs7NScKpOAab6ayZU0VL8W6XAijQmnYTtMWQfSuaaR8rYOaWHrffh3OypvDdrQuYacbkT0csxdrayXfBG3UF5-ZAlhfch1fhF
|
||||||
|
T3yZFdWwzkSDc0BGygfiFyNhCezfyT454wbciSZgrA9ROeHkfPCaX7KCFO8GgQEkGRoQntFBNjluFhNLJIUkEFovEDlfuB4tv_M
|
||||||
|
8BM75celdy3jkpOurg"
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
type OktaToken struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
Scope string `json:"scope"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
IdToken string `json:"id_token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetToken use code to get access_token
|
||||||
|
// get more details via: https://developer.okta.com/docs/reference/api/oidc/#token
|
||||||
|
func (idp *OktaIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||||
|
payload := url.Values{}
|
||||||
|
payload.Set("code", code)
|
||||||
|
payload.Set("grant_type", "authorization_code")
|
||||||
|
payload.Set("client_id", idp.Config.ClientID)
|
||||||
|
payload.Set("client_secret", idp.Config.ClientSecret)
|
||||||
|
payload.Set("redirect_uri", idp.Config.RedirectURL)
|
||||||
|
resp, err := idp.Client.PostForm(idp.Config.Endpoint.TokenURL, payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pToken := &OktaToken{}
|
||||||
|
err = json.Unmarshal(data, pToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("fail to unmarshal token response: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
token := &oauth2.Token{
|
||||||
|
AccessToken: pToken.AccessToken,
|
||||||
|
TokenType: "Bearer",
|
||||||
|
RefreshToken: pToken.RefreshToken,
|
||||||
|
Expiry: time.Unix(time.Now().Unix()+int64(pToken.ExpiresIn), 0),
|
||||||
|
}
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// get more details via: https://developer.okta.com/docs/reference/api/oidc/#userinfo
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"sub": "00uid4BxXw6I6TV4m0g3",
|
||||||
|
"name" :"John Doe",
|
||||||
|
"nickname":"Jimmy",
|
||||||
|
"given_name":"John",
|
||||||
|
"middle_name":"James",
|
||||||
|
"family_name":"Doe",
|
||||||
|
"profile":"https://example.com/john.doe",
|
||||||
|
"zoneinfo":"America/Los_Angeles",
|
||||||
|
"locale":"en-US",
|
||||||
|
"updated_at":1311280970,
|
||||||
|
"email":"john.doe@example.com",
|
||||||
|
"email_verified":true,
|
||||||
|
"address" : { "street_address":"123 Hollywood Blvd.", "locality":"Los Angeles", "region":"CA", "postal_code":"90210", "country":"US" },
|
||||||
|
"phone_number":"+1 (425) 555-1212"
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
type OktaUserInfo struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
PreferredUsername string `json:"preferred_username"`
|
||||||
|
Picture string `json:"picture"`
|
||||||
|
Sub string `json:"sub"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserInfo use token to get user profile
|
||||||
|
// get more details via: https://developer.okta.com/docs/reference/api/oidc/#userinfo
|
||||||
|
func (idp *OktaIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||||
|
req, err := http.NewRequest("GET", fmt.Sprintf("%s/v1/userinfo", idp.Host), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token.AccessToken))
|
||||||
|
req.Header.Add("Accept", "application/json")
|
||||||
|
resp, err := idp.Client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var oktaUserInfo OktaUserInfo
|
||||||
|
err = json.Unmarshal(body, &oktaUserInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo := UserInfo{
|
||||||
|
Id: oktaUserInfo.Sub,
|
||||||
|
Username: oktaUserInfo.PreferredUsername,
|
||||||
|
DisplayName: oktaUserInfo.Name,
|
||||||
|
Email: oktaUserInfo.Email,
|
||||||
|
AvatarUrl: oktaUserInfo.Picture,
|
||||||
|
}
|
||||||
|
return &userInfo, nil
|
||||||
|
}
|
@ -35,7 +35,7 @@ type IdProvider interface {
|
|||||||
GetUserInfo(token *oauth2.Token) (*UserInfo, error)
|
GetUserInfo(token *oauth2.Token) (*UserInfo, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetIdProvider(typ string, subType string, clientId string, clientSecret string, appId string, redirectUrl string, hostUrl string) IdProvider {
|
func GetIdProvider(typ string, subType string, clientId string, clientSecret string, appId string, redirectUrl string, hostUrl string, authUrl string, tokenUrl string, userInfoUrl string) IdProvider {
|
||||||
if typ == "GitHub" {
|
if typ == "GitHub" {
|
||||||
return NewGithubIdProvider(clientId, clientSecret, redirectUrl)
|
return NewGithubIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
} else if typ == "Google" {
|
} else if typ == "Google" {
|
||||||
@ -72,6 +72,8 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
|
|||||||
return NewBaiduIdProvider(clientId, clientSecret, redirectUrl)
|
return NewBaiduIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
} else if typ == "Alipay" {
|
} else if typ == "Alipay" {
|
||||||
return NewAlipayIdProvider(clientId, clientSecret, redirectUrl)
|
return NewAlipayIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
|
} else if typ == "Custom" {
|
||||||
|
return NewCustomIdProvider(clientId, clientSecret, redirectUrl, authUrl, tokenUrl, userInfoUrl)
|
||||||
} else if typ == "Infoflow" {
|
} else if typ == "Infoflow" {
|
||||||
if subType == "Internal" {
|
if subType == "Internal" {
|
||||||
return NewInfoflowInternalIdProvider(clientId, clientSecret, appId, redirectUrl)
|
return NewInfoflowInternalIdProvider(clientId, clientSecret, appId, redirectUrl)
|
||||||
@ -82,8 +84,14 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
|
|||||||
}
|
}
|
||||||
} else if typ == "Casdoor" {
|
} else if typ == "Casdoor" {
|
||||||
return NewCasdoorIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
|
return NewCasdoorIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
|
||||||
|
} else if typ == "Okta" {
|
||||||
|
return NewOktaIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
|
||||||
|
} else if typ == "Douyin" {
|
||||||
|
return NewDouyinIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
} else if isGothSupport(typ) {
|
} else if isGothSupport(typ) {
|
||||||
return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl)
|
return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl)
|
||||||
|
} else if typ == "Bilibili" {
|
||||||
|
return NewBilibiliIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
82
idp/wechat_miniprogram.go
Normal file
82
idp/wechat_miniprogram.go
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
// 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 idp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WeChatMiniProgramIdProvider struct {
|
||||||
|
Client *http.Client
|
||||||
|
Config *oauth2.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWeChatMiniProgramIdProvider(clientId string, clientSecret string) *WeChatMiniProgramIdProvider {
|
||||||
|
idp := &WeChatMiniProgramIdProvider{}
|
||||||
|
|
||||||
|
config := idp.getConfig(clientId, clientSecret)
|
||||||
|
idp.Config = config
|
||||||
|
idp.Client = &http.Client{}
|
||||||
|
return idp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *WeChatMiniProgramIdProvider) SetHttpClient(client *http.Client) {
|
||||||
|
idp.Client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *WeChatMiniProgramIdProvider) getConfig(clientId string, clientSecret string) *oauth2.Config {
|
||||||
|
var config = &oauth2.Config{
|
||||||
|
ClientID: clientId,
|
||||||
|
ClientSecret: clientSecret,
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
type WeChatMiniProgramSessionResponse struct {
|
||||||
|
Openid string `json:"openid"`
|
||||||
|
SessionKey string `json:"session_key"`
|
||||||
|
Unionid string `json:"unionid"`
|
||||||
|
Errcode int `json:"errcode"`
|
||||||
|
Errmsg string `json:"errmsg"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *WeChatMiniProgramIdProvider) GetSessionByCode(code string) (*WeChatMiniProgramSessionResponse, error) {
|
||||||
|
sessionUri := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code", idp.Config.ClientID, idp.Config.ClientSecret, code)
|
||||||
|
sessionResponse, err := idp.Client.Get(sessionUri)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer sessionResponse.Body.Close()
|
||||||
|
data, err := ioutil.ReadAll(sessionResponse.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var session WeChatMiniProgramSessionResponse
|
||||||
|
err = json.Unmarshal(data, &session)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if session.Errcode != 0 {
|
||||||
|
return nil, fmt.Errorf("err: %s", session.Errmsg)
|
||||||
|
}
|
||||||
|
return &session, nil
|
||||||
|
|
||||||
|
}
|
6
main.go
6
main.go
@ -27,10 +27,11 @@ import (
|
|||||||
"github.com/casdoor/casdoor/proxy"
|
"github.com/casdoor/casdoor/proxy"
|
||||||
"github.com/casdoor/casdoor/routers"
|
"github.com/casdoor/casdoor/routers"
|
||||||
_ "github.com/casdoor/casdoor/routers"
|
_ "github.com/casdoor/casdoor/routers"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
createDatabase := flag.Bool("createDatabase", false, "true if you need casdoor to create database")
|
createDatabase := flag.Bool("createDatabase", false, "true if you need Casdoor to create database")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
object.InitAdapter(*createDatabase)
|
object.InitAdapter(*createDatabase)
|
||||||
@ -40,7 +41,7 @@ func main() {
|
|||||||
proxy.InitHttpClient()
|
proxy.InitHttpClient()
|
||||||
authz.InitAuthz()
|
authz.InitAuthz()
|
||||||
|
|
||||||
go object.RunSyncUsersJob()
|
util.SafeGoroutine(func() {object.RunSyncUsersJob()})
|
||||||
|
|
||||||
//beego.DelStaticPath("/static")
|
//beego.DelStaticPath("/static")
|
||||||
beego.SetStaticPath("/static", "web/build/static")
|
beego.SetStaticPath("/static", "web/build/static")
|
||||||
@ -50,6 +51,7 @@ func main() {
|
|||||||
// https://studygolang.com/articles/2303
|
// https://studygolang.com/articles/2303
|
||||||
beego.InsertFilter("*", beego.BeforeRouter, routers.StaticFilter)
|
beego.InsertFilter("*", beego.BeforeRouter, routers.StaticFilter)
|
||||||
beego.InsertFilter("*", beego.BeforeRouter, routers.AutoSigninFilter)
|
beego.InsertFilter("*", beego.BeforeRouter, routers.AutoSigninFilter)
|
||||||
|
beego.InsertFilter("*", beego.BeforeRouter, routers.CorsFilter)
|
||||||
beego.InsertFilter("*", beego.BeforeRouter, routers.AuthzFilter)
|
beego.InsertFilter("*", beego.BeforeRouter, routers.AuthzFilter)
|
||||||
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
|
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
|
||||||
|
|
||||||
|
@ -138,6 +138,11 @@ func (a *Adapter) createTable() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = a.Engine.Sync2(new(Model))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
err = a.Engine.Sync2(new(Provider))
|
err = a.Engine.Sync2(new(Provider))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -16,12 +16,21 @@ package object
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type SignupItem struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Visible bool `json:"visible"`
|
||||||
|
Required bool `json:"required"`
|
||||||
|
Prompted bool `json:"prompted"`
|
||||||
|
Rule string `json:"rule"`
|
||||||
|
}
|
||||||
|
|
||||||
type Application struct {
|
type Application struct {
|
||||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||||
@ -37,6 +46,7 @@ type Application struct {
|
|||||||
EnableSignUp bool `json:"enableSignUp"`
|
EnableSignUp bool `json:"enableSignUp"`
|
||||||
EnableSigninSession bool `json:"enableSigninSession"`
|
EnableSigninSession bool `json:"enableSigninSession"`
|
||||||
EnableCodeSignin bool `json:"enableCodeSignin"`
|
EnableCodeSignin bool `json:"enableCodeSignin"`
|
||||||
|
EnableSamlCompress bool `json:"enableSamlCompress"`
|
||||||
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
|
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
|
||||||
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
|
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
|
||||||
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
|
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
|
||||||
@ -257,7 +267,11 @@ func UpdateApplication(id string, application *Application) bool {
|
|||||||
providerItem.Provider = nil
|
providerItem.Provider = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(application)
|
session := adapter.Engine.ID(core.PK{owner, name}).AllCols()
|
||||||
|
if application.ClientSecret == "***" {
|
||||||
|
session.Omit("client_secret")
|
||||||
|
}
|
||||||
|
affected, err := session.Update(application)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -300,7 +314,6 @@ func (application *Application) GetId() string {
|
|||||||
func CheckRedirectUriValid(application *Application, redirectUri string) bool {
|
func CheckRedirectUriValid(application *Application, redirectUri string) bool {
|
||||||
var validUri = false
|
var validUri = false
|
||||||
for _, tmpUri := range application.RedirectUris {
|
for _, tmpUri := range application.RedirectUris {
|
||||||
fmt.Println(tmpUri, redirectUri)
|
|
||||||
if strings.Contains(redirectUri, tmpUri) {
|
if strings.Contains(redirectUri, tmpUri) {
|
||||||
validUri = true
|
validUri = true
|
||||||
break
|
break
|
||||||
@ -308,3 +321,39 @@ func CheckRedirectUriValid(application *Application, redirectUri string) bool {
|
|||||||
}
|
}
|
||||||
return validUri
|
return validUri
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsAllowOrigin(origin string) bool {
|
||||||
|
allowOrigin := false
|
||||||
|
originUrl, err := url.Parse(origin)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := adapter.Engine.Cols("redirect_uris").Rows(&Application{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
application := Application{}
|
||||||
|
for rows.Next() {
|
||||||
|
err := rows.Scan(&application)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
for _, tmpRedirectUri := range application.RedirectUris {
|
||||||
|
u1, err := url.Parse(tmpRedirectUri)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if u1.Scheme == originUrl.Scheme && u1.Host == originUrl.Host {
|
||||||
|
allowOrigin = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if allowOrigin {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allowOrigin
|
||||||
|
}
|
||||||
|
@ -17,6 +17,7 @@ package object
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/cred"
|
"github.com/casdoor/casdoor/cred"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
@ -195,3 +196,37 @@ func CheckUserPassword(organization string, username string, password string) (*
|
|||||||
func filterField(field string) bool {
|
func filterField(field string) bool {
|
||||||
return reFieldWhiteList.MatchString(field)
|
return reFieldWhiteList.MatchString(field)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CheckUserPermission(requestUserId, userId string, strict bool) (bool, error) {
|
||||||
|
if requestUserId == "" {
|
||||||
|
return false, fmt.Errorf("please login first")
|
||||||
|
}
|
||||||
|
|
||||||
|
targetUser := GetUser(userId)
|
||||||
|
if targetUser == nil {
|
||||||
|
return false, fmt.Errorf("the user: %s doesn't exist", userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
hasPermission := false
|
||||||
|
if strings.HasPrefix(requestUserId, "app/") {
|
||||||
|
hasPermission = true
|
||||||
|
} else {
|
||||||
|
requestUser := GetUser(requestUserId)
|
||||||
|
if requestUser == nil {
|
||||||
|
return false, fmt.Errorf("session outdated, please login again")
|
||||||
|
}
|
||||||
|
if requestUser.IsGlobalAdmin {
|
||||||
|
hasPermission = true
|
||||||
|
} else if requestUserId == userId {
|
||||||
|
hasPermission = true
|
||||||
|
} else if targetUser.Owner == requestUser.Owner {
|
||||||
|
if strict {
|
||||||
|
hasPermission = requestUser.IsAdmin
|
||||||
|
} else {
|
||||||
|
hasPermission = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasPermission, fmt.Errorf("you don't have the permission to do this")
|
||||||
|
}
|
@ -29,3 +29,16 @@ func SendEmail(provider *Provider, title string, content string, dest string, se
|
|||||||
|
|
||||||
return dialer.DialAndSend(message)
|
return dialer.DialAndSend(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DailSmtpServer Dail Smtp server
|
||||||
|
func DailSmtpServer(provider *Provider) error {
|
||||||
|
dialer := gomail.NewDialer(provider.Host, provider.Port, provider.ClientId, provider.ClientSecret)
|
||||||
|
|
||||||
|
sender, err := dialer.Dial()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer sender.Close()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
func InitDb() {
|
func InitDb() {
|
||||||
existed := initBuiltInOrganization()
|
existed := initBuiltInOrganization()
|
||||||
if !existed {
|
if !existed {
|
||||||
|
initBuiltInProvider()
|
||||||
initBuiltInUser()
|
initBuiltInUser()
|
||||||
initBuiltInApplication()
|
initBuiltInApplication()
|
||||||
initBuiltInCert()
|
initBuiltInCert()
|
||||||
@ -47,6 +48,31 @@ func initBuiltInOrganization() bool {
|
|||||||
PhonePrefix: "86",
|
PhonePrefix: "86",
|
||||||
DefaultAvatar: "https://casbin.org/img/casbin.svg",
|
DefaultAvatar: "https://casbin.org/img/casbin.svg",
|
||||||
Tags: []string{},
|
Tags: []string{},
|
||||||
|
AccountItems: []*AccountItem{
|
||||||
|
{Name: "Organization", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||||
|
{Name: "ID", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
|
||||||
|
{Name: "Name", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||||
|
{Name: "Display name", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||||
|
{Name: "Avatar", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||||
|
{Name: "User type", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||||
|
{Name: "Password", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||||
|
{Name: "Email", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||||
|
{Name: "Phone", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||||
|
{Name: "Country/Region", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||||
|
{Name: "Location", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||||
|
{Name: "Affiliation", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||||
|
{Name: "Title", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||||
|
{Name: "Homepage", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||||
|
{Name: "Bio", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||||
|
{Name: "Tag", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||||
|
{Name: "Signup application", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||||
|
{Name: "3rd-party logins", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||||
|
{Name: "Properties", Visible: false, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||||
|
{Name: "Is admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||||
|
{Name: "Is global admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||||
|
{Name: "Is forbidden", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||||
|
{Name: "Is deleted", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
AddOrganization(organization)
|
AddOrganization(organization)
|
||||||
return false
|
return false
|
||||||
@ -78,7 +104,7 @@ func initBuiltInUser() {
|
|||||||
IsGlobalAdmin: true,
|
IsGlobalAdmin: true,
|
||||||
IsForbidden: false,
|
IsForbidden: false,
|
||||||
IsDeleted: false,
|
IsDeleted: false,
|
||||||
SignupApplication: "built-in-app",
|
SignupApplication: "app-built-in",
|
||||||
CreatedIp: "127.0.0.1",
|
CreatedIp: "127.0.0.1",
|
||||||
Properties: make(map[string]string),
|
Properties: make(map[string]string),
|
||||||
}
|
}
|
||||||
@ -102,14 +128,16 @@ func initBuiltInApplication() {
|
|||||||
Cert: "cert-built-in",
|
Cert: "cert-built-in",
|
||||||
EnablePassword: true,
|
EnablePassword: true,
|
||||||
EnableSignUp: true,
|
EnableSignUp: true,
|
||||||
Providers: []*ProviderItem{},
|
Providers: []*ProviderItem{
|
||||||
|
{Name: "provider_captcha_default", CanSignUp: false, CanSignIn: false, CanUnlink: false, Prompted: false, AlertType: "None", Provider: nil},
|
||||||
|
},
|
||||||
SignupItems: []*SignupItem{
|
SignupItems: []*SignupItem{
|
||||||
{Name: "ID", Visible: false, Required: true, Prompted: false, Rule: "Random"},
|
{Name: "ID", Visible: false, Required: true, Prompted: false, Rule: "Random"},
|
||||||
{Name: "Username", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
{Name: "Username", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
||||||
{Name: "Display name", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
{Name: "Display name", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
||||||
{Name: "Password", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
{Name: "Password", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
||||||
{Name: "Confirm password", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
{Name: "Confirm password", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
||||||
{Name: "Email", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
{Name: "Email", Visible: true, Required: true, Prompted: false, Rule: "Normal"},
|
||||||
{Name: "Phone", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
{Name: "Phone", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
||||||
{Name: "Agreement", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
{Name: "Agreement", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
||||||
},
|
},
|
||||||
@ -147,7 +175,7 @@ func initBuiltInCert() {
|
|||||||
DisplayName: "Built-in Cert",
|
DisplayName: "Built-in Cert",
|
||||||
Scope: "JWT",
|
Scope: "JWT",
|
||||||
Type: "x509",
|
Type: "x509",
|
||||||
CryptoAlgorithm: "RSA",
|
CryptoAlgorithm: "RS256",
|
||||||
BitSize: 4096,
|
BitSize: 4096,
|
||||||
ExpireInYears: 20,
|
ExpireInYears: 20,
|
||||||
PublicKey: tokenJwtPublicKey,
|
PublicKey: tokenJwtPublicKey,
|
||||||
@ -176,3 +204,20 @@ func initBuiltInLdap() {
|
|||||||
}
|
}
|
||||||
AddLdap(ldap)
|
AddLdap(ldap)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initBuiltInProvider() {
|
||||||
|
provider := GetProvider("admin/provider_captcha_default")
|
||||||
|
if provider != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
provider = &Provider{
|
||||||
|
Owner: "admin",
|
||||||
|
Name: "provider_captcha_default",
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
DisplayName: "Captcha Default",
|
||||||
|
Category: "Captcha",
|
||||||
|
Type: "Default",
|
||||||
|
}
|
||||||
|
AddProvider(provider)
|
||||||
|
}
|
||||||
|
101
object/ldap.go
101
object/ldap.go
@ -19,6 +19,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
goldap "github.com/go-ldap/ldap/v3"
|
goldap "github.com/go-ldap/ldap/v3"
|
||||||
"github.com/thanhpk/randstr"
|
"github.com/thanhpk/randstr"
|
||||||
@ -42,6 +43,7 @@ type Ldap struct {
|
|||||||
|
|
||||||
type ldapConn struct {
|
type ldapConn struct {
|
||||||
Conn *goldap.Conn
|
Conn *goldap.Conn
|
||||||
|
IsAD bool
|
||||||
}
|
}
|
||||||
|
|
||||||
//type ldapGroup struct {
|
//type ldapGroup struct {
|
||||||
@ -78,6 +80,13 @@ type LdapRespUser struct {
|
|||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ldapServerType struct {
|
||||||
|
Vendorname string
|
||||||
|
Vendorversion string
|
||||||
|
IsGlobalCatalogReady string
|
||||||
|
ForestFunctionality string
|
||||||
|
}
|
||||||
|
|
||||||
func LdapUsersToLdapRespUsers(users []ldapUser) []LdapRespUser {
|
func LdapUsersToLdapRespUsers(users []ldapUser) []LdapRespUser {
|
||||||
returnAnyNotEmpty := func(strs ...string) string {
|
returnAnyNotEmpty := func(strs ...string) string {
|
||||||
for _, str := range strs {
|
for _, str := range strs {
|
||||||
@ -104,6 +113,45 @@ func LdapUsersToLdapRespUsers(users []ldapUser) []LdapRespUser {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isMicrosoftAD(Conn *goldap.Conn) (bool, error) {
|
||||||
|
SearchFilter := "(objectclass=*)"
|
||||||
|
SearchAttributes := []string{"vendorname", "vendorversion", "isGlobalCatalogReady", "forestFunctionality"}
|
||||||
|
|
||||||
|
searchReq := goldap.NewSearchRequest("",
|
||||||
|
goldap.ScopeBaseObject, goldap.NeverDerefAliases, 0, 0, false,
|
||||||
|
SearchFilter, SearchAttributes, nil)
|
||||||
|
searchResult, err := Conn.Search(searchReq)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if len(searchResult.Entries) == 0 {
|
||||||
|
return false, errors.New("no result")
|
||||||
|
}
|
||||||
|
isMicrosoft := false
|
||||||
|
var ldapServerType ldapServerType
|
||||||
|
for _, entry := range searchResult.Entries {
|
||||||
|
for _, attribute := range entry.Attributes {
|
||||||
|
switch attribute.Name {
|
||||||
|
case "vendorname":
|
||||||
|
ldapServerType.Vendorname = attribute.Values[0]
|
||||||
|
case "vendorversion":
|
||||||
|
ldapServerType.Vendorversion = attribute.Values[0]
|
||||||
|
case "isGlobalCatalogReady":
|
||||||
|
ldapServerType.IsGlobalCatalogReady = attribute.Values[0]
|
||||||
|
case "forestFunctionality":
|
||||||
|
ldapServerType.ForestFunctionality = attribute.Values[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ldapServerType.Vendorname == "" &&
|
||||||
|
ldapServerType.Vendorversion == "" &&
|
||||||
|
ldapServerType.IsGlobalCatalogReady == "TRUE" &&
|
||||||
|
ldapServerType.ForestFunctionality != "" {
|
||||||
|
isMicrosoft = true
|
||||||
|
}
|
||||||
|
return isMicrosoft, err
|
||||||
|
}
|
||||||
|
|
||||||
func GetLdapConn(host string, port int, adminUser string, adminPasswd string) (*ldapConn, error) {
|
func GetLdapConn(host string, port int, adminUser string, adminPasswd string) (*ldapConn, error) {
|
||||||
conn, err := goldap.Dial("tcp", fmt.Sprintf("%s:%d", host, port))
|
conn, err := goldap.Dial("tcp", fmt.Sprintf("%s:%d", host, port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -115,7 +163,11 @@ func GetLdapConn(host string, port int, adminUser string, adminPasswd string) (*
|
|||||||
return nil, fmt.Errorf("fail to login Ldap server with [%s]", adminUser)
|
return nil, fmt.Errorf("fail to login Ldap server with [%s]", adminUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ldapConn{Conn: conn}, nil
|
isAD, err := isMicrosoftAD(conn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("fail to get Ldap server type [%s]", adminUser)
|
||||||
|
}
|
||||||
|
return &ldapConn{Conn: conn, IsAD: isAD}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//FIXME: The Base DN does not necessarily contain the Group
|
//FIXME: The Base DN does not necessarily contain the Group
|
||||||
@ -158,10 +210,19 @@ func (l *ldapConn) GetLdapUsers(baseDn string) ([]ldapUser, error) {
|
|||||||
SearchFilter := "(objectClass=posixAccount)"
|
SearchFilter := "(objectClass=posixAccount)"
|
||||||
SearchAttributes := []string{"uidNumber", "uid", "cn", "gidNumber", "entryUUID", "mail", "email",
|
SearchAttributes := []string{"uidNumber", "uid", "cn", "gidNumber", "entryUUID", "mail", "email",
|
||||||
"emailAddress", "telephoneNumber", "mobile", "mobileTelephoneNumber", "registeredAddress", "postalAddress"}
|
"emailAddress", "telephoneNumber", "mobile", "mobileTelephoneNumber", "registeredAddress", "postalAddress"}
|
||||||
|
SearchFilterMsAD := "(objectClass=user)"
|
||||||
searchReq := goldap.NewSearchRequest(baseDn,
|
SearchAttributesMsAD := []string{"uidNumber", "sAMAccountName", "cn", "gidNumber", "entryUUID", "mail", "email",
|
||||||
|
"emailAddress", "telephoneNumber", "mobile", "mobileTelephoneNumber", "registeredAddress", "postalAddress"}
|
||||||
|
var searchReq *goldap.SearchRequest
|
||||||
|
if l.IsAD {
|
||||||
|
searchReq = goldap.NewSearchRequest(baseDn,
|
||||||
|
goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
|
||||||
|
SearchFilterMsAD, SearchAttributesMsAD, nil)
|
||||||
|
} else {
|
||||||
|
searchReq = goldap.NewSearchRequest(baseDn,
|
||||||
goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
|
goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
|
||||||
SearchFilter, SearchAttributes, nil)
|
SearchFilter, SearchAttributes, nil)
|
||||||
|
}
|
||||||
searchResult, err := l.Conn.SearchWithPaging(searchReq, 100)
|
searchResult, err := l.Conn.SearchWithPaging(searchReq, 100)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -181,12 +242,16 @@ func (l *ldapConn) GetLdapUsers(baseDn string) ([]ldapUser, error) {
|
|||||||
ldapUserItem.UidNumber = attribute.Values[0]
|
ldapUserItem.UidNumber = attribute.Values[0]
|
||||||
case "uid":
|
case "uid":
|
||||||
ldapUserItem.Uid = attribute.Values[0]
|
ldapUserItem.Uid = attribute.Values[0]
|
||||||
|
case "sAMAccountName":
|
||||||
|
ldapUserItem.Uid = attribute.Values[0]
|
||||||
case "cn":
|
case "cn":
|
||||||
ldapUserItem.Cn = attribute.Values[0]
|
ldapUserItem.Cn = attribute.Values[0]
|
||||||
case "gidNumber":
|
case "gidNumber":
|
||||||
ldapUserItem.GidNumber = attribute.Values[0]
|
ldapUserItem.GidNumber = attribute.Values[0]
|
||||||
case "entryUUID":
|
case "entryUUID":
|
||||||
ldapUserItem.Uuid = attribute.Values[0]
|
ldapUserItem.Uuid = attribute.Values[0]
|
||||||
|
case "objectGUID":
|
||||||
|
ldapUserItem.Uuid = attribute.Values[0]
|
||||||
case "mail":
|
case "mail":
|
||||||
ldapUserItem.Mail = attribute.Values[0]
|
ldapUserItem.Mail = attribute.Values[0]
|
||||||
case "email":
|
case "email":
|
||||||
@ -300,7 +365,7 @@ func DeleteLdap(ldap *Ldap) bool {
|
|||||||
return affected != 0
|
return affected != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func SyncLdapUsers(owner string, users []LdapRespUser) (*[]LdapRespUser, *[]LdapRespUser) {
|
func SyncLdapUsers(owner string, users []LdapRespUser, ldapId string) (*[]LdapRespUser, *[]LdapRespUser) {
|
||||||
var existUsers []LdapRespUser
|
var existUsers []LdapRespUser
|
||||||
var failedUsers []LdapRespUser
|
var failedUsers []LdapRespUser
|
||||||
var uuids []string
|
var uuids []string
|
||||||
@ -311,6 +376,25 @@ func SyncLdapUsers(owner string, users []LdapRespUser) (*[]LdapRespUser, *[]Ldap
|
|||||||
|
|
||||||
existUuids := CheckLdapUuidExist(owner, uuids)
|
existUuids := CheckLdapUuidExist(owner, uuids)
|
||||||
|
|
||||||
|
organization := getOrganization("admin", owner)
|
||||||
|
ldap := GetLdap(ldapId)
|
||||||
|
|
||||||
|
var dc []string
|
||||||
|
for _, basedn := range strings.Split(ldap.BaseDn, ",") {
|
||||||
|
if strings.Contains(basedn, "dc=") {
|
||||||
|
dc = append(dc, basedn[3:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
affiliation := strings.Join(dc, ".")
|
||||||
|
|
||||||
|
var ou []string
|
||||||
|
for _, admin := range strings.Split(ldap.Admin, ",") {
|
||||||
|
if strings.Contains(admin, "ou=") {
|
||||||
|
ou = append(ou, admin[3:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tag := strings.Join(ou, ".")
|
||||||
|
|
||||||
for _, user := range users {
|
for _, user := range users {
|
||||||
found := false
|
found := false
|
||||||
if len(existUuids) > 0 {
|
if len(existUuids) > 0 {
|
||||||
@ -325,15 +409,14 @@ func SyncLdapUsers(owner string, users []LdapRespUser) (*[]LdapRespUser, *[]Ldap
|
|||||||
Owner: owner,
|
Owner: owner,
|
||||||
Name: buildLdapUserName(user.Uid, user.UidNumber),
|
Name: buildLdapUserName(user.Uid, user.UidNumber),
|
||||||
CreatedTime: util.GetCurrentTime(),
|
CreatedTime: util.GetCurrentTime(),
|
||||||
Password: "123",
|
|
||||||
DisplayName: user.Cn,
|
DisplayName: user.Cn,
|
||||||
Avatar: "https://casbin.org/img/casbin.svg",
|
Avatar: organization.DefaultAvatar,
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
Phone: user.Phone,
|
Phone: user.Phone,
|
||||||
Address: []string{user.Address},
|
Address: []string{user.Address},
|
||||||
Affiliation: "Example Inc.",
|
Affiliation: affiliation,
|
||||||
Tag: "staff",
|
Tag: tag,
|
||||||
Score: 2000,
|
Score: beego.AppConfig.DefaultInt("initScore", 2000),
|
||||||
Ldap: user.Uuid,
|
Ldap: user.Uuid,
|
||||||
}) {
|
}) {
|
||||||
failedUsers = append(failedUsers, user)
|
failedUsers = append(failedUsers, user)
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/astaxie/beego/logs"
|
"github.com/astaxie/beego/logs"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LdapAutoSynchronizer struct {
|
type LdapAutoSynchronizer struct {
|
||||||
@ -47,7 +48,7 @@ func (l *LdapAutoSynchronizer) StartAutoSync(ldapId string) error {
|
|||||||
stopChan := make(chan struct{})
|
stopChan := make(chan struct{})
|
||||||
l.ldapIdToStopChan[ldapId] = stopChan
|
l.ldapIdToStopChan[ldapId] = stopChan
|
||||||
logs.Info(fmt.Sprintf("autoSync started for %s", ldap.Id))
|
logs.Info(fmt.Sprintf("autoSync started for %s", ldap.Id))
|
||||||
go l.syncRoutine(ldap, stopChan)
|
util.SafeGoroutine(func() {l.syncRoutine(ldap, stopChan)})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,7 +86,7 @@ func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) {
|
|||||||
logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
|
logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
existed, failed := SyncLdapUsers(ldap.Owner, LdapUsersToLdapRespUsers(users))
|
existed, failed := SyncLdapUsers(ldap.Owner, LdapUsersToLdapRespUsers(users), ldap.Id)
|
||||||
if len(*failed) != 0 {
|
if len(*failed) != 0 {
|
||||||
logs.Warning(fmt.Sprintf("ldap autosync,%d new users,but %d user failed during :", len(users)-len(*existed)-len(*failed), len(*failed)), *failed)
|
logs.Warning(fmt.Sprintf("ldap autosync,%d new users,but %d user failed during :", len(users)-len(*existed)-len(*failed), len(*failed)), *failed)
|
||||||
} else {
|
} else {
|
||||||
|
122
object/model.go
Normal file
122
object/model.go
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
// 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 object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
"xorm.io/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Model struct {
|
||||||
|
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||||
|
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||||
|
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||||
|
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||||
|
|
||||||
|
ModelText string `xorm:"mediumtext" json:"modelText"`
|
||||||
|
IsEnabled bool `json:"isEnabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetModelCount(owner, field, value string) int {
|
||||||
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
|
count, err := session.Count(&Model{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetModels(owner string) []*Model {
|
||||||
|
models := []*Model{}
|
||||||
|
err := adapter.Engine.Desc("created_time").Find(&models, &Model{Owner: owner})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return models
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPaginationModels(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Model {
|
||||||
|
models := []*Model{}
|
||||||
|
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||||
|
err := session.Find(&models)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return models
|
||||||
|
}
|
||||||
|
|
||||||
|
func getModel(owner string, name string) *Model {
|
||||||
|
if owner == "" || name == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
model := Model{Owner: owner, Name: name}
|
||||||
|
existed, err := adapter.Engine.Get(&model)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &model
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetModel(id string) *Model {
|
||||||
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
|
return getModel(owner, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateModel(id string, model *Model) bool {
|
||||||
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
|
if getModel(owner, name) == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(model)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddModel(model *Model) bool {
|
||||||
|
affected, err := adapter.Engine.Insert(model)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteModel(model *Model) bool {
|
||||||
|
affected, err := adapter.Engine.ID(core.PK{model.Owner, model.Name}).Delete(&Model{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (model *Model) GetId() string {
|
||||||
|
return fmt.Sprintf("%s/%s", model.Owner, model.Name)
|
||||||
|
}
|
@ -76,7 +76,7 @@ func GetOidcDiscovery(host string) OidcDiscovery {
|
|||||||
UserinfoEndpoint: fmt.Sprintf("%s/api/userinfo", originBackend),
|
UserinfoEndpoint: fmt.Sprintf("%s/api/userinfo", originBackend),
|
||||||
JwksUri: fmt.Sprintf("%s/.well-known/jwks", originBackend),
|
JwksUri: fmt.Sprintf("%s/.well-known/jwks", originBackend),
|
||||||
IntrospectionEndpoint: fmt.Sprintf("%s/api/login/oauth/introspect", originBackend),
|
IntrospectionEndpoint: fmt.Sprintf("%s/api/login/oauth/introspect", originBackend),
|
||||||
ResponseTypesSupported: []string{"id_token"},
|
ResponseTypesSupported: []string{"code", "token", "id_token", "code token", "code id_token", "token id_token", "code token id_token", "none"},
|
||||||
ResponseModesSupported: []string{"login", "code", "link"},
|
ResponseModesSupported: []string{"login", "code", "link"},
|
||||||
GrantTypesSupported: []string{"password", "authorization_code"},
|
GrantTypesSupported: []string{"password", "authorization_code"},
|
||||||
SubjectTypesSupported: []string{"public"},
|
SubjectTypesSupported: []string{"public"},
|
||||||
@ -105,6 +105,8 @@ func GetJsonWebKeySet() (jose.JSONWebKeySet, error) {
|
|||||||
jwk.Key = x509Cert.PublicKey
|
jwk.Key = x509Cert.PublicKey
|
||||||
jwk.Certificates = []*x509.Certificate{x509Cert}
|
jwk.Certificates = []*x509.Certificate{x509Cert}
|
||||||
jwk.KeyID = cert.Name
|
jwk.KeyID = cert.Name
|
||||||
|
jwk.Algorithm = cert.CryptoAlgorithm
|
||||||
|
jwk.Use = "sig"
|
||||||
jwks.Keys = append(jwks.Keys, jwk)
|
jwks.Keys = append(jwks.Keys, jwk)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,13 @@ import (
|
|||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type AccountItem struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Visible bool `json:"visible"`
|
||||||
|
ViewRule string `json:"viewRule"`
|
||||||
|
ModifyRule string `json:"modifyRule"`
|
||||||
|
}
|
||||||
|
|
||||||
type Organization struct {
|
type Organization struct {
|
||||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||||
@ -35,6 +42,9 @@ type Organization struct {
|
|||||||
Tags []string `xorm:"mediumtext" json:"tags"`
|
Tags []string `xorm:"mediumtext" json:"tags"`
|
||||||
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
||||||
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
||||||
|
IsProfilePublic bool `json:"isProfilePublic"`
|
||||||
|
|
||||||
|
AccountItems []*AccountItem `xorm:"varchar(2000)" json:"accountItems"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOrganizationCount(owner, field, value string) int {
|
func GetOrganizationCount(owner, field, value string) int {
|
||||||
@ -120,14 +130,18 @@ func UpdateOrganization(id string, organization *Organization) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if name != organization.Name {
|
if name != organization.Name {
|
||||||
applications := GetApplicationsByOrganizationName("admin", name)
|
go func() {
|
||||||
for _, application := range applications {
|
application := new(Application)
|
||||||
application.Organization = organization.Name
|
application.Organization = organization.Name
|
||||||
UpdateApplication(application.GetId(), application)
|
_, _ = adapter.Engine.Where("organization=?", name).Update(application)
|
||||||
}
|
|
||||||
|
user := new(User)
|
||||||
|
user.Owner = organization.Name
|
||||||
|
_, _ = adapter.Engine.Where("owner=?", name).Update(user)
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
if organization.MasterPassword != "" {
|
if organization.MasterPassword != "" && organization.MasterPassword != "***" {
|
||||||
credManager := cred.GetCredManager(organization.PasswordType)
|
credManager := cred.GetCredManager(organization.PasswordType)
|
||||||
if credManager != nil {
|
if credManager != nil {
|
||||||
hashedPassword := credManager.GetHashedPassword(organization.MasterPassword, "", organization.PasswordSalt)
|
hashedPassword := credManager.GetHashedPassword(organization.MasterPassword, "", organization.PasswordSalt)
|
||||||
@ -135,7 +149,11 @@ func UpdateOrganization(id string, organization *Organization) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(organization)
|
session := adapter.Engine.ID(core.PK{owner, name}).AllCols()
|
||||||
|
if organization.MasterPassword == "***" {
|
||||||
|
session.Omit("master_password")
|
||||||
|
}
|
||||||
|
affected, err := session.Update(organization)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,16 @@ type Payment struct {
|
|||||||
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
|
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
|
||||||
State string `xorm:"varchar(100)" json:"state"`
|
State string `xorm:"varchar(100)" json:"state"`
|
||||||
Message string `xorm:"varchar(1000)" json:"message"`
|
Message string `xorm:"varchar(1000)" json:"message"`
|
||||||
|
|
||||||
|
PersonName string `xorm:"varchar(100)" json:"personName"`
|
||||||
|
PersonIdCard string `xorm:"varchar(100)" json:"personIdCard"`
|
||||||
|
PersonEmail string `xorm:"varchar(100)" json:"personEmail"`
|
||||||
|
PersonPhone string `xorm:"varchar(100)" json:"personPhone"`
|
||||||
|
InvoiceType string `xorm:"varchar(100)" json:"invoiceType"`
|
||||||
|
InvoiceTitle string `xorm:"varchar(100)" json:"invoiceTitle"`
|
||||||
|
InvoiceTaxId string `xorm:"varchar(100)" json:"invoiceTaxId"`
|
||||||
|
InvoiceRemark string `xorm:"varchar(100)" json:"invoiceRemark"`
|
||||||
|
InvoiceUrl string `xorm:"varchar(255)" json:"invoiceUrl"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPaymentCount(owner, field, value string) int {
|
func GetPaymentCount(owner, field, value string) int {
|
||||||
@ -197,6 +207,44 @@ func NotifyPayment(request *http.Request, body []byte, owner string, providerNam
|
|||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func invoicePayment(payment *Payment) (string, error) {
|
||||||
|
provider := getProvider(payment.Owner, payment.Provider)
|
||||||
|
if provider == nil {
|
||||||
|
return "", fmt.Errorf("the payment provider: %s does not exist", payment.Provider)
|
||||||
|
}
|
||||||
|
|
||||||
|
pProvider, _, err := provider.getPaymentProvider()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
invoiceUrl, err := pProvider.GetInvoice(payment.Name, payment.PersonName, payment.PersonIdCard, payment.PersonEmail, payment.PersonPhone, payment.InvoiceType, payment.InvoiceTitle, payment.InvoiceTaxId)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return invoiceUrl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func InvoicePayment(payment *Payment) (string, error) {
|
||||||
|
if payment.State != "Paid" {
|
||||||
|
return "", fmt.Errorf("the payment state is supposed to be: \"%s\", got: \"%s\"", "Paid", payment.State)
|
||||||
|
}
|
||||||
|
|
||||||
|
invoiceUrl, err := invoicePayment(payment)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
payment.InvoiceUrl = invoiceUrl
|
||||||
|
affected := UpdatePayment(payment.GetId(), payment)
|
||||||
|
if !affected {
|
||||||
|
return "", fmt.Errorf("failed to update the payment: %s", payment.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return invoiceUrl, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (payment *Payment) GetId() string {
|
func (payment *Payment) GetId() string {
|
||||||
return fmt.Sprintf("%s/%s", payment.Owner, payment.Name)
|
return fmt.Sprintf("%s/%s", payment.Owner, payment.Name)
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ type Permission struct {
|
|||||||
Users []string `xorm:"mediumtext" json:"users"`
|
Users []string `xorm:"mediumtext" json:"users"`
|
||||||
Roles []string `xorm:"mediumtext" json:"roles"`
|
Roles []string `xorm:"mediumtext" json:"roles"`
|
||||||
|
|
||||||
|
Model string `xorm:"varchar(100)" json:"model"`
|
||||||
ResourceType string `xorm:"varchar(100)" json:"resourceType"`
|
ResourceType string `xorm:"varchar(100)" json:"resourceType"`
|
||||||
Resources []string `xorm:"mediumtext" json:"resources"`
|
Resources []string `xorm:"mediumtext" json:"resources"`
|
||||||
Actions []string `xorm:"mediumtext" json:"actions"`
|
Actions []string `xorm:"mediumtext" json:"actions"`
|
||||||
|
@ -170,6 +170,7 @@ func BuyProduct(id string, providerName string, user *User, host string) (string
|
|||||||
|
|
||||||
owner := product.Owner
|
owner := product.Owner
|
||||||
productName := product.Name
|
productName := product.Name
|
||||||
|
payerName := fmt.Sprintf("%s | %s", user.Name, user.DisplayName)
|
||||||
paymentName := util.GenerateTimeId()
|
paymentName := util.GenerateTimeId()
|
||||||
productDisplayName := product.DisplayName
|
productDisplayName := product.DisplayName
|
||||||
|
|
||||||
@ -177,7 +178,7 @@ func BuyProduct(id string, providerName string, user *User, host string) (string
|
|||||||
returnUrl := fmt.Sprintf("%s/payments/%s/result", originFrontend, paymentName)
|
returnUrl := fmt.Sprintf("%s/payments/%s/result", originFrontend, paymentName)
|
||||||
notifyUrl := fmt.Sprintf("%s/api/notify-payment/%s/%s/%s/%s", originBackend, owner, providerName, productName, paymentName)
|
notifyUrl := fmt.Sprintf("%s/api/notify-payment/%s/%s/%s/%s", originBackend, owner, providerName, productName, paymentName)
|
||||||
|
|
||||||
payUrl, err := pProvider.Pay(providerName, productName, paymentName, productDisplayName, product.Price, returnUrl, notifyUrl)
|
payUrl, err := pProvider.Pay(providerName, productName, payerName, paymentName, productDisplayName, product.Price, returnUrl, notifyUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -32,10 +32,10 @@ func TestProduct(t *testing.T) {
|
|||||||
cert := getCert(product.Owner, "cert-pay-alipay")
|
cert := getCert(product.Owner, "cert-pay-alipay")
|
||||||
pProvider := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.PublicKey, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey)
|
pProvider := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.PublicKey, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey)
|
||||||
|
|
||||||
paymentId := util.GenerateTimeId()
|
paymentName := util.GenerateTimeId()
|
||||||
returnUrl := ""
|
returnUrl := ""
|
||||||
notifyUrl := ""
|
notifyUrl := ""
|
||||||
payUrl, err := pProvider.Pay(product.DisplayName, product.Name, provider.Name, paymentId, product.Price, returnUrl, notifyUrl)
|
payUrl, err := pProvider.Pay(provider.Name, product.Name, "alice", paymentName, product.DisplayName, product.Price, returnUrl, notifyUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,11 @@ type Provider struct {
|
|||||||
ClientId2 string `xorm:"varchar(100)" json:"clientId2"`
|
ClientId2 string `xorm:"varchar(100)" json:"clientId2"`
|
||||||
ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"`
|
ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"`
|
||||||
Cert string `xorm:"varchar(100)" json:"cert"`
|
Cert string `xorm:"varchar(100)" json:"cert"`
|
||||||
|
CustomAuthUrl string `xorm:"varchar(200)" json:"customAuthUrl"`
|
||||||
|
CustomScope string `xorm:"varchar(200)" json:"customScope"`
|
||||||
|
CustomTokenUrl string `xorm:"varchar(200)" json:"customTokenUrl"`
|
||||||
|
CustomUserInfoUrl string `xorm:"varchar(200)" json:"customUserInfoUrl"`
|
||||||
|
CustomLogo string `xorm:"varchar(200)" json:"customLogo"`
|
||||||
|
|
||||||
Host string `xorm:"varchar(100)" json:"host"`
|
Host string `xorm:"varchar(100)" json:"host"`
|
||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
@ -137,8 +142,8 @@ func GetProvider(id string) *Provider {
|
|||||||
return getProvider(owner, name)
|
return getProvider(owner, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDefaultHumanCheckProvider() *Provider {
|
func GetDefaultCaptchaProvider() *Provider {
|
||||||
provider := Provider{Owner: "admin", Category: "HumanCheck"}
|
provider := Provider{Owner: "admin", Category: "Captcha"}
|
||||||
existed, err := adapter.Engine.Get(&provider)
|
existed, err := adapter.Engine.Get(&provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -151,13 +156,30 @@ func GetDefaultHumanCheckProvider() *Provider {
|
|||||||
return &provider
|
return &provider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetWechatMiniProgramProvider(application *Application) *Provider {
|
||||||
|
providers := application.Providers
|
||||||
|
for _, provider := range providers {
|
||||||
|
if provider.Provider.Type == "WeChatMiniProgram" {
|
||||||
|
return provider.Provider
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func UpdateProvider(id string, provider *Provider) bool {
|
func UpdateProvider(id string, provider *Provider) bool {
|
||||||
owner, name := util.GetOwnerAndNameFromId(id)
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
if getProvider(owner, name) == nil {
|
if getProvider(owner, name) == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(provider)
|
session := adapter.Engine.ID(core.PK{owner, name}).AllCols()
|
||||||
|
if provider.ClientSecret == "***" {
|
||||||
|
session = session.Omit("client_secret")
|
||||||
|
}
|
||||||
|
if provider.ClientSecret2 == "***" {
|
||||||
|
session = session.Omit("client_secret2")
|
||||||
|
}
|
||||||
|
affected, err := session.Update(provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -203,3 +225,37 @@ func (p *Provider) getPaymentProvider() (pp.PaymentProvider, *Cert, error) {
|
|||||||
func (p *Provider) GetId() string {
|
func (p *Provider) GetId() string {
|
||||||
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
|
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetCaptchaProviderByOwnerName(applicationId string) (*Provider, error) {
|
||||||
|
owner, name := util.GetOwnerAndNameFromId(applicationId)
|
||||||
|
provider := Provider{Owner: owner, Name: name, Category: "Captcha"}
|
||||||
|
existed, err := adapter.Engine.Get(&provider)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !existed {
|
||||||
|
return nil, fmt.Errorf("the provider: %s does not exist", applicationId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &provider, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCaptchaProviderByApplication(applicationId, isCurrentProvider string) (*Provider, error) {
|
||||||
|
if isCurrentProvider == "true" {
|
||||||
|
return GetCaptchaProviderByOwnerName(applicationId)
|
||||||
|
}
|
||||||
|
application := GetApplication(applicationId)
|
||||||
|
if application == nil || len(application.Providers) == 0 {
|
||||||
|
return nil, fmt.Errorf("invalid application id")
|
||||||
|
}
|
||||||
|
for _, provider := range application.Providers {
|
||||||
|
if provider.Provider == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if provider.Provider.Category == "Captcha" {
|
||||||
|
return GetCaptchaProviderByOwnerName(fmt.Sprintf("%s/%s", provider.Provider.Owner, provider.Provider.Name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
@ -23,7 +23,7 @@ import (
|
|||||||
|
|
||||||
type Resource struct {
|
type Resource struct {
|
||||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||||
Name string `xorm:"varchar(200) notnull pk" json:"name"`
|
Name string `xorm:"varchar(250) notnull pk" json:"name"`
|
||||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||||
|
|
||||||
User string `xorm:"varchar(100)" json:"user"`
|
User string `xorm:"varchar(100)" json:"user"`
|
||||||
@ -31,7 +31,7 @@ type Resource struct {
|
|||||||
Application string `xorm:"varchar(100)" json:"application"`
|
Application string `xorm:"varchar(100)" json:"application"`
|
||||||
Tag string `xorm:"varchar(100)" json:"tag"`
|
Tag string `xorm:"varchar(100)" json:"tag"`
|
||||||
Parent string `xorm:"varchar(100)" json:"parent"`
|
Parent string `xorm:"varchar(100)" json:"parent"`
|
||||||
FileName string `xorm:"varchar(100)" json:"fileName"`
|
FileName string `xorm:"varchar(1000)" json:"fileName"`
|
||||||
FileType string `xorm:"varchar(100)" json:"fileType"`
|
FileType string `xorm:"varchar(100)" json:"fileType"`
|
||||||
FileFormat string `xorm:"varchar(100)" json:"fileFormat"`
|
FileFormat string `xorm:"varchar(100)" json:"fileFormat"`
|
||||||
FileSize int `json:"fileSize"`
|
FileSize int `json:"fileSize"`
|
||||||
|
369
object/saml_idp.go
Normal file
369
object/saml_idp.go
Normal file
@ -0,0 +1,369 @@
|
|||||||
|
// 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 object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/flate"
|
||||||
|
"crypto"
|
||||||
|
"crypto/rsa"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/pem"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/RobotsAndPencils/go-saml"
|
||||||
|
"github.com/astaxie/beego"
|
||||||
|
"github.com/beevik/etree"
|
||||||
|
"github.com/golang-jwt/jwt/v4"
|
||||||
|
dsig "github.com/russellhaering/goxmldsig"
|
||||||
|
uuid "github.com/satori/go.uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
//returns a saml2 response
|
||||||
|
func NewSamlResponse(user *User, host string, publicKey string, destination string, iss string, requestId string, redirectUri []string) (*etree.Element, error) {
|
||||||
|
samlResponse := &etree.Element{
|
||||||
|
Space: "samlp",
|
||||||
|
Tag: "Response",
|
||||||
|
}
|
||||||
|
now := time.Now().UTC().Format(time.RFC3339)
|
||||||
|
expireTime := time.Now().UTC().Add(time.Hour * 24).Format(time.RFC3339)
|
||||||
|
samlResponse.CreateAttr("xmlns:samlp", "urn:oasis:names:tc:SAML:2.0:protocol")
|
||||||
|
samlResponse.CreateAttr("xmlns:saml", "urn:oasis:names:tc:SAML:2.0:assertion")
|
||||||
|
arId := uuid.NewV4()
|
||||||
|
|
||||||
|
samlResponse.CreateAttr("ID", fmt.Sprintf("_%s", arId))
|
||||||
|
samlResponse.CreateAttr("Version", "2.0")
|
||||||
|
samlResponse.CreateAttr("IssueInstant", now)
|
||||||
|
samlResponse.CreateAttr("Destination", destination)
|
||||||
|
samlResponse.CreateAttr("InResponseTo", requestId)
|
||||||
|
samlResponse.CreateElement("saml:Issuer").SetText(host)
|
||||||
|
|
||||||
|
samlResponse.CreateElement("samlp:Status").CreateElement("samlp:StatusCode").CreateAttr("Value", "urn:oasis:names:tc:SAML:2.0:status:Success")
|
||||||
|
|
||||||
|
assertion := samlResponse.CreateElement("saml:Assertion")
|
||||||
|
assertion.CreateAttr("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
|
||||||
|
assertion.CreateAttr("xmlns:xs", "http://www.w3.org/2001/XMLSchema")
|
||||||
|
assertion.CreateAttr("ID", fmt.Sprintf("_%s", uuid.NewV4()))
|
||||||
|
assertion.CreateAttr("Version", "2.0")
|
||||||
|
assertion.CreateAttr("IssueInstant", now)
|
||||||
|
assertion.CreateElement("saml:Issuer").SetText(host)
|
||||||
|
subject := assertion.CreateElement("saml:Subject")
|
||||||
|
subject.CreateElement("saml:NameID").SetText(user.Email)
|
||||||
|
subjectConfirmation := subject.CreateElement("saml:SubjectConfirmation")
|
||||||
|
subjectConfirmation.CreateAttr("Method", "urn:oasis:names:tc:SAML:2.0:cm:bearer")
|
||||||
|
subjectConfirmationData := subjectConfirmation.CreateElement("saml:SubjectConfirmationData")
|
||||||
|
subjectConfirmationData.CreateAttr("InResponseTo", requestId)
|
||||||
|
subjectConfirmationData.CreateAttr("Recipient", destination)
|
||||||
|
subjectConfirmationData.CreateAttr("NotOnOrAfter", expireTime)
|
||||||
|
condition := assertion.CreateElement("saml:Conditions")
|
||||||
|
condition.CreateAttr("NotBefore", now)
|
||||||
|
condition.CreateAttr("NotOnOrAfter", expireTime)
|
||||||
|
audience := condition.CreateElement("saml:AudienceRestriction")
|
||||||
|
audience.CreateElement("saml:Audience").SetText(iss)
|
||||||
|
for _, value := range redirectUri {
|
||||||
|
audience.CreateElement("saml:Audience").SetText(value)
|
||||||
|
}
|
||||||
|
authnStatement := assertion.CreateElement("saml:AuthnStatement")
|
||||||
|
authnStatement.CreateAttr("AuthnInstant", now)
|
||||||
|
authnStatement.CreateAttr("SessionIndex", fmt.Sprintf("_%s", uuid.NewV4()))
|
||||||
|
authnStatement.CreateAttr("SessionNotOnOrAfter", expireTime)
|
||||||
|
authnStatement.CreateElement("saml:AuthnContext").CreateElement("saml:AuthnContextClassRef").SetText("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport")
|
||||||
|
|
||||||
|
attributes := assertion.CreateElement("saml:AttributeStatement")
|
||||||
|
email := attributes.CreateElement("saml:Attribute")
|
||||||
|
email.CreateAttr("Name", "Email")
|
||||||
|
email.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
|
||||||
|
email.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(user.Email)
|
||||||
|
name := attributes.CreateElement("saml:Attribute")
|
||||||
|
name.CreateAttr("Name", "Name")
|
||||||
|
name.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
|
||||||
|
name.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(user.Name)
|
||||||
|
displayName := attributes.CreateElement("saml:Attribute")
|
||||||
|
displayName.CreateAttr("Name", "DisplayName")
|
||||||
|
displayName.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
|
||||||
|
displayName.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(user.DisplayName)
|
||||||
|
|
||||||
|
return samlResponse, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type X509Key struct {
|
||||||
|
X509Certificate string
|
||||||
|
PrivateKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x X509Key) GetKeyPair() (privateKey *rsa.PrivateKey, cert []byte, err error) {
|
||||||
|
cert, _ = base64.StdEncoding.DecodeString(x.X509Certificate)
|
||||||
|
privateKey, err = jwt.ParseRSAPrivateKeyFromPEM([]byte(x.PrivateKey))
|
||||||
|
return privateKey, cert, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//SAML METADATA
|
||||||
|
type IdpEntityDescriptor struct {
|
||||||
|
XMLName xml.Name `xml:"EntityDescriptor"`
|
||||||
|
DS string `xml:"xmlns:ds,attr"`
|
||||||
|
XMLNS string `xml:"xmlns,attr"`
|
||||||
|
MD string `xml:"xmlns:md,attr"`
|
||||||
|
EntityId string `xml:"entityID,attr"`
|
||||||
|
|
||||||
|
IdpSSODescriptor IdpSSODescriptor `xml:"IDPSSODescriptor"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeyInfo struct {
|
||||||
|
XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# KeyInfo"`
|
||||||
|
X509Data X509Data `xml:",innerxml"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type X509Data struct {
|
||||||
|
XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# X509Data"`
|
||||||
|
X509Certificate X509Certificate `xml:",innerxml"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type X509Certificate struct {
|
||||||
|
XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# X509Certificate"`
|
||||||
|
Cert string `xml:",innerxml"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeyDescriptor struct {
|
||||||
|
XMLName xml.Name `xml:"KeyDescriptor"`
|
||||||
|
Use string `xml:"use,attr"`
|
||||||
|
KeyInfo KeyInfo `xml:"KeyInfo"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IdpSSODescriptor struct {
|
||||||
|
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:metadata IDPSSODescriptor"`
|
||||||
|
ProtocolSupportEnumeration string `xml:"protocolSupportEnumeration,attr"`
|
||||||
|
SigningKeyDescriptor KeyDescriptor
|
||||||
|
NameIDFormats []NameIDFormat `xml:"NameIDFormat"`
|
||||||
|
SingleSignOnService SingleSignOnService `xml:"SingleSignOnService"`
|
||||||
|
Attribute []Attribute `xml:"Attribute"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NameIDFormat struct {
|
||||||
|
XMLName xml.Name
|
||||||
|
Value string `xml:",innerxml"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SingleSignOnService struct {
|
||||||
|
XMLName xml.Name
|
||||||
|
Binding string `xml:"Binding,attr"`
|
||||||
|
Location string `xml:"Location,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Attribute struct {
|
||||||
|
XMLName xml.Name
|
||||||
|
Name string `xml:"Name,attr"`
|
||||||
|
NameFormat string `xml:"NameFormat,attr"`
|
||||||
|
FriendlyName string `xml:"FriendlyName,attr"`
|
||||||
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, error) {
|
||||||
|
//_, originBackend := getOriginFromHost(host)
|
||||||
|
cert := getCertByApplication(application)
|
||||||
|
block, _ := pem.Decode([]byte(cert.PublicKey))
|
||||||
|
publicKey := base64.StdEncoding.EncodeToString(block.Bytes)
|
||||||
|
|
||||||
|
origin := beego.AppConfig.String("origin")
|
||||||
|
originFrontend, originBackend := getOriginFromHost(host)
|
||||||
|
if origin != "" {
|
||||||
|
originBackend = origin
|
||||||
|
}
|
||||||
|
d := IdpEntityDescriptor{
|
||||||
|
XMLName: xml.Name{
|
||||||
|
Local: "md:EntityDescriptor",
|
||||||
|
},
|
||||||
|
DS: "http://www.w3.org/2000/09/xmldsig#",
|
||||||
|
XMLNS: "urn:oasis:names:tc:SAML:2.0:metadata",
|
||||||
|
MD: "urn:oasis:names:tc:SAML:2.0:metadata",
|
||||||
|
EntityId: originBackend,
|
||||||
|
IdpSSODescriptor: IdpSSODescriptor{
|
||||||
|
SigningKeyDescriptor: KeyDescriptor{
|
||||||
|
Use: "signing",
|
||||||
|
KeyInfo: KeyInfo{
|
||||||
|
X509Data: X509Data{
|
||||||
|
X509Certificate: X509Certificate{
|
||||||
|
Cert: publicKey,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NameIDFormats: []NameIDFormat{
|
||||||
|
{Value: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"},
|
||||||
|
{Value: "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"},
|
||||||
|
{Value: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"},
|
||||||
|
},
|
||||||
|
Attribute: []Attribute{
|
||||||
|
{Xmlns: "urn:oasis:names:tc:SAML:2.0:assertion", Name: "Email", NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", FriendlyName: "E-Mail"},
|
||||||
|
{Xmlns: "urn:oasis:names:tc:SAML:2.0:assertion", Name: "DisplayName", NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", FriendlyName: "displayName"},
|
||||||
|
{Xmlns: "urn:oasis:names:tc:SAML:2.0:assertion", Name: "Name", NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", FriendlyName: "Name"},
|
||||||
|
},
|
||||||
|
SingleSignOnService: SingleSignOnService{
|
||||||
|
Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
|
||||||
|
Location: fmt.Sprintf("%s/login/saml/authorize/%s/%s", originFrontend, application.Owner, application.Name),
|
||||||
|
},
|
||||||
|
ProtocolSupportEnumeration: "urn:oasis:names:tc:SAML:2.0:protocol",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return &d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSamlResponse generates a SAML2.0 response
|
||||||
|
// parameter samlRequest is saml request in base64 format
|
||||||
|
func GetSamlResponse(application *Application, user *User, samlRequest string, host string) (string, string, error) {
|
||||||
|
// base64 decode
|
||||||
|
defated, err := base64.StdEncoding.DecodeString(samlRequest)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("err: %s", err.Error())
|
||||||
|
}
|
||||||
|
// decompress
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
rdr := flate.NewReader(bytes.NewReader(defated))
|
||||||
|
io.Copy(&buffer, rdr)
|
||||||
|
var authnRequest saml.AuthnRequest
|
||||||
|
err = xml.Unmarshal(buffer.Bytes(), &authnRequest)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("err: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify samlRequest
|
||||||
|
if valid := CheckRedirectUriValid(application, authnRequest.Issuer.Url); !valid {
|
||||||
|
return "", "", fmt.Errorf("err: invalid issuer url")
|
||||||
|
}
|
||||||
|
|
||||||
|
// get public key string
|
||||||
|
cert := getCertByApplication(application)
|
||||||
|
block, _ := pem.Decode([]byte(cert.PublicKey))
|
||||||
|
publicKey := base64.StdEncoding.EncodeToString(block.Bytes)
|
||||||
|
|
||||||
|
_, originBackend := getOriginFromHost(host)
|
||||||
|
|
||||||
|
// build signedResponse
|
||||||
|
samlResponse, _ := NewSamlResponse(user, originBackend, publicKey, authnRequest.AssertionConsumerServiceURL, authnRequest.Issuer.Url, authnRequest.ID, application.RedirectUris)
|
||||||
|
randomKeyStore := &X509Key{
|
||||||
|
PrivateKey: cert.PrivateKey,
|
||||||
|
X509Certificate: publicKey,
|
||||||
|
}
|
||||||
|
ctx := dsig.NewDefaultSigningContext(randomKeyStore)
|
||||||
|
ctx.Hash = crypto.SHA1
|
||||||
|
//signedXML, err := ctx.SignEnvelopedLimix(samlResponse)
|
||||||
|
//if err != nil {
|
||||||
|
// return "", "", fmt.Errorf("err: %s", err.Error())
|
||||||
|
//}
|
||||||
|
sig, err := ctx.ConstructSignature(samlResponse, true)
|
||||||
|
samlResponse.InsertChildAt(1, sig)
|
||||||
|
|
||||||
|
doc := etree.NewDocument()
|
||||||
|
doc.SetRoot(samlResponse)
|
||||||
|
xmlBytes, err := doc.WriteToBytes()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("err: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// compress
|
||||||
|
if application.EnableSamlCompress {
|
||||||
|
flated := bytes.NewBuffer(nil)
|
||||||
|
writer, err := flate.NewWriter(flated, flate.DefaultCompression)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("err: %s", err.Error())
|
||||||
|
}
|
||||||
|
writer.Write(xmlBytes)
|
||||||
|
writer.Close()
|
||||||
|
xmlBytes = flated.Bytes()
|
||||||
|
}
|
||||||
|
// base64 encode
|
||||||
|
res := base64.StdEncoding.EncodeToString(xmlBytes)
|
||||||
|
return res, authnRequest.AssertionConsumerServiceURL, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSamlResponse11 return a saml1.1 response(not 2.0)
|
||||||
|
func NewSamlResponse11(user *User, requestID string, host string) *etree.Element {
|
||||||
|
samlResponse := &etree.Element{
|
||||||
|
Space: "samlp",
|
||||||
|
Tag: "Response",
|
||||||
|
}
|
||||||
|
//create samlresponse
|
||||||
|
samlResponse.CreateAttr("xmlns:samlp", "urn:oasis:names:tc:SAML:1.0:protocol")
|
||||||
|
samlResponse.CreateAttr("MajorVersion", "1")
|
||||||
|
samlResponse.CreateAttr("MinorVersion", "1")
|
||||||
|
|
||||||
|
responseID := uuid.NewV4()
|
||||||
|
samlResponse.CreateAttr("ResponseID", fmt.Sprintf("_%s", responseID))
|
||||||
|
samlResponse.CreateAttr("InResponseTo", requestID)
|
||||||
|
|
||||||
|
now := time.Now().UTC().Format(time.RFC3339)
|
||||||
|
expireTime := time.Now().UTC().Add(time.Hour * 24).Format(time.RFC3339)
|
||||||
|
|
||||||
|
samlResponse.CreateAttr("IssueInstant", now)
|
||||||
|
|
||||||
|
samlResponse.CreateElement("samlp:Status").CreateElement("samlp:StatusCode").CreateAttr("Value", "samlp:Success")
|
||||||
|
|
||||||
|
//create assertion which is inside the response
|
||||||
|
assertion := samlResponse.CreateElement("saml:Assertion")
|
||||||
|
assertion.CreateAttr("xmlns:saml", "urn:oasis:names:tc:SAML:1.0:assertion")
|
||||||
|
assertion.CreateAttr("MajorVersion", "1")
|
||||||
|
assertion.CreateAttr("MinorVersion", "1")
|
||||||
|
assertion.CreateAttr("AssertionID", uuid.NewV4().String())
|
||||||
|
assertion.CreateAttr("Issuer", host)
|
||||||
|
assertion.CreateAttr("IssueInstant", now)
|
||||||
|
|
||||||
|
condition := assertion.CreateElement("saml:Conditions")
|
||||||
|
condition.CreateAttr("NotBefore", now)
|
||||||
|
condition.CreateAttr("NotOnOrAfter", expireTime)
|
||||||
|
|
||||||
|
//AuthenticationStatement inside assertion
|
||||||
|
authenticationStatement := assertion.CreateElement("saml:AuthenticationStatement")
|
||||||
|
authenticationStatement.CreateAttr("AuthenticationMethod", "urn:oasis:names:tc:SAML:1.0:am:password")
|
||||||
|
authenticationStatement.CreateAttr("AuthenticationInstant", now)
|
||||||
|
|
||||||
|
//subject inside AuthenticationStatement
|
||||||
|
subject := assertion.CreateElement("saml:Subject")
|
||||||
|
//nameIdentifier inside subject
|
||||||
|
nameIdentifier := subject.CreateElement("saml:NameIdentifier")
|
||||||
|
//nameIdentifier.CreateAttr("Format", "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress")
|
||||||
|
nameIdentifier.SetText(user.Name)
|
||||||
|
|
||||||
|
//subjectConfirmation inside subject
|
||||||
|
subjectConfirmation := subject.CreateElement("saml:SubjectConfirmation")
|
||||||
|
subjectConfirmation.CreateElement("saml:ConfirmationMethod").SetText("urn:oasis:names:tc:SAML:1.0:cm:artifact")
|
||||||
|
|
||||||
|
attributeStatement := assertion.CreateElement("saml:AttributeStatement")
|
||||||
|
subjectInAttribute := attributeStatement.CreateElement("saml:Subject")
|
||||||
|
nameIdentifierInAttribute := subjectInAttribute.CreateElement("saml:NameIdentifier")
|
||||||
|
nameIdentifierInAttribute.SetText(user.Name)
|
||||||
|
|
||||||
|
subjectConfirmationInAttribute := subjectInAttribute.CreateElement("saml:SubjectConfirmation")
|
||||||
|
subjectConfirmationInAttribute.CreateElement("saml:ConfirmationMethod").SetText("urn:oasis:names:tc:SAML:1.0:cm:artifact")
|
||||||
|
|
||||||
|
data, _ := json.Marshal(user)
|
||||||
|
tmp := map[string]string{}
|
||||||
|
json.Unmarshal(data, &tmp)
|
||||||
|
|
||||||
|
for k, v := range tmp {
|
||||||
|
if v != "" {
|
||||||
|
attr := attributeStatement.CreateElement("saml:Attribute")
|
||||||
|
attr.CreateAttr("saml:AttributeName", k)
|
||||||
|
attr.CreateAttr("saml:AttributeNamespace", "http://www.ja-sig.org/products/cas/")
|
||||||
|
attr.CreateElement("saml:AttributeValue").SetText(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return samlResponse
|
||||||
|
}
|
@ -56,6 +56,9 @@ func getUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool
|
|||||||
// provider.Domain = "http://localhost:8000" or "https://door.casdoor.com"
|
// provider.Domain = "http://localhost:8000" or "https://door.casdoor.com"
|
||||||
host = util.UrlJoin(provider.Domain, "/files")
|
host = util.UrlJoin(provider.Domain, "/files")
|
||||||
}
|
}
|
||||||
|
if provider.Type == "Azure Blob" {
|
||||||
|
host = fmt.Sprintf("%s/%s", host, provider.Bucket)
|
||||||
|
}
|
||||||
|
|
||||||
fileUrl := util.UrlJoin(host, objectKey)
|
fileUrl := util.UrlJoin(host, objectKey)
|
||||||
if hasTimestamp {
|
if hasTimestamp {
|
||||||
|
@ -133,7 +133,11 @@ func UpdateSyncer(id string, syncer *Syncer) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(syncer)
|
session := adapter.Engine.ID(core.PK{owner, name}).AllCols()
|
||||||
|
if syncer.Password == "***" {
|
||||||
|
session.Omit("password")
|
||||||
|
}
|
||||||
|
affected, err := session.Update(syncer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -206,3 +210,8 @@ func (syncer *Syncer) getTable() string {
|
|||||||
return syncer.Table
|
return syncer.Table
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RunSyncer(syncer *Syncer) {
|
||||||
|
syncer.initAdapter()
|
||||||
|
syncer.syncUsers()
|
||||||
|
}
|
||||||
|
@ -25,6 +25,11 @@ import (
|
|||||||
|
|
||||||
type OriginalUser = User
|
type OriginalUser = User
|
||||||
|
|
||||||
|
type Credential struct {
|
||||||
|
Value string `json:"value"`
|
||||||
|
Salt string `json:"salt"`
|
||||||
|
}
|
||||||
|
|
||||||
func (syncer *Syncer) getOriginalUsers() ([]*OriginalUser, error) {
|
func (syncer *Syncer) getOriginalUsers() ([]*OriginalUser, error) {
|
||||||
sql := fmt.Sprintf("select * from %s", syncer.getTable())
|
sql := fmt.Sprintf("select * from %s", syncer.getTable())
|
||||||
results, err := syncer.Adapter.Engine.QueryString(sql)
|
results, err := syncer.Adapter.Engine.QueryString(sql)
|
||||||
|
@ -15,9 +15,11 @@
|
|||||||
package object
|
package object
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
@ -99,12 +101,18 @@ func (syncer *Syncer) setUserByKeyValue(user *User, key string, value string) {
|
|||||||
user.PasswordSalt = value
|
user.PasswordSalt = value
|
||||||
case "DisplayName":
|
case "DisplayName":
|
||||||
user.DisplayName = value
|
user.DisplayName = value
|
||||||
|
case "FirstName":
|
||||||
|
user.FirstName = value
|
||||||
|
case "LastName":
|
||||||
|
user.LastName = value
|
||||||
case "Avatar":
|
case "Avatar":
|
||||||
user.Avatar = syncer.getPartialAvatarUrl(value)
|
user.Avatar = syncer.getPartialAvatarUrl(value)
|
||||||
case "PermanentAvatar":
|
case "PermanentAvatar":
|
||||||
user.PermanentAvatar = value
|
user.PermanentAvatar = value
|
||||||
case "Email":
|
case "Email":
|
||||||
user.Email = value
|
user.Email = value
|
||||||
|
case "EmailVerified":
|
||||||
|
user.EmailVerified = util.ParseBool(value)
|
||||||
case "Phone":
|
case "Phone":
|
||||||
user.Phone = value
|
user.Phone = value
|
||||||
case "Location":
|
case "Location":
|
||||||
@ -165,8 +173,45 @@ func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]string) []*Or
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tableColumn := range syncer.TableColumns {
|
for _, tableColumn := range syncer.TableColumns {
|
||||||
syncer.setUserByKeyValue(originalUser, tableColumn.CasdoorName, result[tableColumn.Name])
|
value := ""
|
||||||
|
if strings.Contains(tableColumn.Name, "+") {
|
||||||
|
names := strings.Split(tableColumn.Name, "+")
|
||||||
|
var values []string
|
||||||
|
for _, name := range names {
|
||||||
|
values = append(values, result[strings.Trim(name, " ")])
|
||||||
}
|
}
|
||||||
|
value = strings.Join(values, " ")
|
||||||
|
} else {
|
||||||
|
value = result[tableColumn.Name]
|
||||||
|
}
|
||||||
|
syncer.setUserByKeyValue(originalUser, tableColumn.CasdoorName, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if syncer.Type == "Keycloak" {
|
||||||
|
// query and set password and password salt from credential table
|
||||||
|
sql := fmt.Sprintf("select * from credential where type = 'password' and user_id = '%s'", originalUser.Id)
|
||||||
|
credentialResult, _ := syncer.Adapter.Engine.QueryString(sql)
|
||||||
|
if len(credentialResult) > 0 {
|
||||||
|
credential := Credential{}
|
||||||
|
_ = json.Unmarshal([]byte(credentialResult[0]["SECRET_DATA"]), &credential)
|
||||||
|
originalUser.Password = credential.Value
|
||||||
|
originalUser.PasswordSalt = credential.Salt
|
||||||
|
}
|
||||||
|
// query and set signup application from user group table
|
||||||
|
sql = fmt.Sprintf("select name from keycloak_group where id = " +
|
||||||
|
"(select group_id as gid from user_group_membership where user_id = '%s')", originalUser.Id)
|
||||||
|
groupResult, _ := syncer.Adapter.Engine.QueryString(sql)
|
||||||
|
if len(groupResult) > 0 {
|
||||||
|
originalUser.SignupApplication = groupResult[0]["name"]
|
||||||
|
}
|
||||||
|
// create time
|
||||||
|
i, _ := strconv.ParseInt(originalUser.CreatedTime, 10, 64)
|
||||||
|
tm := time.Unix(i/int64(1000), 0)
|
||||||
|
originalUser.CreatedTime = tm.Format("2006-01-02T15:04:05+08:00")
|
||||||
|
// enable
|
||||||
|
originalUser.IsForbidden = !(result["ENABLED"] == "\x01")
|
||||||
|
}
|
||||||
|
|
||||||
users = append(users, originalUser)
|
users = append(users, originalUser)
|
||||||
}
|
}
|
||||||
return users
|
return users
|
||||||
|
136
object/token.go
136
object/token.go
@ -22,10 +22,15 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/idp"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
hourSeconds = 3600
|
||||||
|
)
|
||||||
|
|
||||||
type Code struct {
|
type Code struct {
|
||||||
Message string `xorm:"varchar(100)" json:"message"`
|
Message string `xorm:"varchar(100)" json:"message"`
|
||||||
Code string `xorm:"varchar(100)" json:"code"`
|
Code string `xorm:"varchar(100)" json:"code"`
|
||||||
@ -58,6 +63,7 @@ type TokenWrapper struct {
|
|||||||
TokenType string `json:"token_type"`
|
TokenType string `json:"token_type"`
|
||||||
ExpiresIn int `json:"expires_in"`
|
ExpiresIn int `json:"expires_in"`
|
||||||
Scope string `json:"scope"`
|
Scope string `json:"scope"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type IntrospectionResponse struct {
|
type IntrospectionResponse struct {
|
||||||
@ -290,7 +296,7 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
|
|||||||
Code: util.GenerateClientId(),
|
Code: util.GenerateClientId(),
|
||||||
AccessToken: accessToken,
|
AccessToken: accessToken,
|
||||||
RefreshToken: refreshToken,
|
RefreshToken: refreshToken,
|
||||||
ExpiresIn: application.ExpireInHours * 60,
|
ExpiresIn: application.ExpireInHours * hourSeconds,
|
||||||
Scope: scope,
|
Scope: scope,
|
||||||
TokenType: "Bearer",
|
TokenType: "Bearer",
|
||||||
CodeChallenge: challenge,
|
CodeChallenge: challenge,
|
||||||
@ -305,24 +311,31 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string, scope string, username string, password string, host string) *TokenWrapper {
|
|
||||||
|
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string, scope string, username string, password string, host string, tag string, avatar string) *TokenWrapper {
|
||||||
|
var errString string
|
||||||
application := GetApplicationByClientId(clientId)
|
application := GetApplicationByClientId(clientId)
|
||||||
if application == nil {
|
if application == nil {
|
||||||
|
errString = "error: invalid client_id"
|
||||||
return &TokenWrapper{
|
return &TokenWrapper{
|
||||||
AccessToken: "error: invalid client_id",
|
AccessToken: errString,
|
||||||
TokenType: "",
|
TokenType: "",
|
||||||
ExpiresIn: 0,
|
ExpiresIn: 0,
|
||||||
Scope: "",
|
Scope: "",
|
||||||
|
Error: errString,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Check if grantType is allowed in the current application
|
//Check if grantType is allowed in the current application
|
||||||
if !IsGrantTypeValid(grantType, application.GrantTypes) {
|
|
||||||
|
if !IsGrantTypeValid(grantType, application.GrantTypes) && tag == "" {
|
||||||
|
errString = fmt.Sprintf("error: grant_type: %s is not supported in this application", grantType)
|
||||||
return &TokenWrapper{
|
return &TokenWrapper{
|
||||||
AccessToken: fmt.Sprintf("error: grant_type: %s is not supported in this application", grantType),
|
AccessToken: errString,
|
||||||
TokenType: "",
|
TokenType: "",
|
||||||
ExpiresIn: 0,
|
ExpiresIn: 0,
|
||||||
Scope: "",
|
Scope: "",
|
||||||
|
Error: errString,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -337,12 +350,19 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
|
|||||||
token, err = GetClientCredentialsToken(application, clientSecret, scope, host)
|
token, err = GetClientCredentialsToken(application, clientSecret, scope, host)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tag == "wechat_miniprogram" {
|
||||||
|
// Wechat Mini Program
|
||||||
|
token, err = GetWechatMiniProgramToken(application, code, host, username, avatar)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
errString = err.Error()
|
||||||
return &TokenWrapper{
|
return &TokenWrapper{
|
||||||
AccessToken: err.Error(),
|
AccessToken: errString,
|
||||||
TokenType: "",
|
TokenType: "",
|
||||||
ExpiresIn: 0,
|
ExpiresIn: 0,
|
||||||
Scope: "",
|
Scope: "",
|
||||||
|
Error: errString,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -361,62 +381,75 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
|
|||||||
}
|
}
|
||||||
|
|
||||||
func RefreshToken(grantType string, refreshToken string, scope string, clientId string, clientSecret string, host string) *TokenWrapper {
|
func RefreshToken(grantType string, refreshToken string, scope string, clientId string, clientSecret string, host string) *TokenWrapper {
|
||||||
|
var errString string
|
||||||
// check parameters
|
// check parameters
|
||||||
if grantType != "refresh_token" {
|
if grantType != "refresh_token" {
|
||||||
|
errString = "error: grant_type should be \"refresh_token\""
|
||||||
return &TokenWrapper{
|
return &TokenWrapper{
|
||||||
AccessToken: "error: grant_type should be \"refresh_token\"",
|
AccessToken: errString,
|
||||||
TokenType: "",
|
TokenType: "",
|
||||||
ExpiresIn: 0,
|
ExpiresIn: 0,
|
||||||
Scope: "",
|
Scope: "",
|
||||||
|
Error: errString,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
application := GetApplicationByClientId(clientId)
|
application := GetApplicationByClientId(clientId)
|
||||||
if application == nil {
|
if application == nil {
|
||||||
|
errString = "error: invalid client_id"
|
||||||
return &TokenWrapper{
|
return &TokenWrapper{
|
||||||
AccessToken: "error: invalid client_id",
|
AccessToken: errString,
|
||||||
TokenType: "",
|
TokenType: "",
|
||||||
ExpiresIn: 0,
|
ExpiresIn: 0,
|
||||||
Scope: "",
|
Scope: "",
|
||||||
|
Error: errString,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if clientSecret != "" && application.ClientSecret != clientSecret {
|
if clientSecret != "" && application.ClientSecret != clientSecret {
|
||||||
|
errString = "error: invalid client_secret"
|
||||||
return &TokenWrapper{
|
return &TokenWrapper{
|
||||||
AccessToken: "error: invalid client_secret",
|
AccessToken: errString,
|
||||||
TokenType: "",
|
TokenType: "",
|
||||||
ExpiresIn: 0,
|
ExpiresIn: 0,
|
||||||
Scope: "",
|
Scope: "",
|
||||||
|
Error: errString,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// check whether the refresh token is valid, and has not expired.
|
// check whether the refresh token is valid, and has not expired.
|
||||||
token := Token{RefreshToken: refreshToken}
|
token := Token{RefreshToken: refreshToken}
|
||||||
existed, err := adapter.Engine.Get(&token)
|
existed, err := adapter.Engine.Get(&token)
|
||||||
if err != nil || !existed {
|
if err != nil || !existed {
|
||||||
|
errString = "error: invalid refresh_token"
|
||||||
return &TokenWrapper{
|
return &TokenWrapper{
|
||||||
AccessToken: "error: invalid refresh_token",
|
AccessToken: errString,
|
||||||
TokenType: "",
|
TokenType: "",
|
||||||
ExpiresIn: 0,
|
ExpiresIn: 0,
|
||||||
Scope: "",
|
Scope: "",
|
||||||
|
Error: errString,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cert := getCertByApplication(application)
|
cert := getCertByApplication(application)
|
||||||
_, err = ParseJwtToken(refreshToken, cert)
|
_, err = ParseJwtToken(refreshToken, cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
errString := fmt.Sprintf("error: %s", err.Error())
|
||||||
return &TokenWrapper{
|
return &TokenWrapper{
|
||||||
AccessToken: fmt.Sprintf("error: %s", err.Error()),
|
AccessToken: errString,
|
||||||
TokenType: "",
|
TokenType: "",
|
||||||
ExpiresIn: 0,
|
ExpiresIn: 0,
|
||||||
Scope: "",
|
Scope: "",
|
||||||
|
Error: errString,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// generate a new token
|
// generate a new token
|
||||||
user := getUser(application.Organization, token.User)
|
user := getUser(application.Organization, token.User)
|
||||||
if user.IsForbidden {
|
if user.IsForbidden {
|
||||||
|
errString = "error: the user is forbidden to sign in, please contact the administrator"
|
||||||
return &TokenWrapper{
|
return &TokenWrapper{
|
||||||
AccessToken: "error: the user is forbidden to sign in, please contact the administrator",
|
AccessToken: errString,
|
||||||
TokenType: "",
|
TokenType: "",
|
||||||
ExpiresIn: 0,
|
ExpiresIn: 0,
|
||||||
Scope: "",
|
Scope: "",
|
||||||
|
Error: errString,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newAccessToken, newRefreshToken, err := generateJwtToken(application, user, "", scope, host)
|
newAccessToken, newRefreshToken, err := generateJwtToken(application, user, "", scope, host)
|
||||||
@ -434,7 +467,7 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
|||||||
Code: util.GenerateClientId(),
|
Code: util.GenerateClientId(),
|
||||||
AccessToken: newAccessToken,
|
AccessToken: newAccessToken,
|
||||||
RefreshToken: newRefreshToken,
|
RefreshToken: newRefreshToken,
|
||||||
ExpiresIn: application.ExpireInHours * 60,
|
ExpiresIn: application.ExpireInHours * hourSeconds,
|
||||||
Scope: scope,
|
Scope: scope,
|
||||||
TokenType: "Bearer",
|
TokenType: "Bearer",
|
||||||
}
|
}
|
||||||
@ -543,7 +576,7 @@ func GetPasswordToken(application *Application, username string, password string
|
|||||||
Code: util.GenerateClientId(),
|
Code: util.GenerateClientId(),
|
||||||
AccessToken: accessToken,
|
AccessToken: accessToken,
|
||||||
RefreshToken: refreshToken,
|
RefreshToken: refreshToken,
|
||||||
ExpiresIn: application.ExpireInHours * 60,
|
ExpiresIn: application.ExpireInHours * hourSeconds,
|
||||||
Scope: scope,
|
Scope: scope,
|
||||||
TokenType: "Bearer",
|
TokenType: "Bearer",
|
||||||
CodeIsUsed: true,
|
CodeIsUsed: true,
|
||||||
@ -575,7 +608,7 @@ func GetClientCredentialsToken(application *Application, clientSecret string, sc
|
|||||||
User: nullUser.Name,
|
User: nullUser.Name,
|
||||||
Code: util.GenerateClientId(),
|
Code: util.GenerateClientId(),
|
||||||
AccessToken: accessToken,
|
AccessToken: accessToken,
|
||||||
ExpiresIn: application.ExpireInHours * 60,
|
ExpiresIn: application.ExpireInHours * hourSeconds,
|
||||||
Scope: scope,
|
Scope: scope,
|
||||||
TokenType: "Bearer",
|
TokenType: "Bearer",
|
||||||
CodeIsUsed: true,
|
CodeIsUsed: true,
|
||||||
@ -600,7 +633,7 @@ func GetTokenByUser(application *Application, user *User, scope string, host str
|
|||||||
Code: util.GenerateClientId(),
|
Code: util.GenerateClientId(),
|
||||||
AccessToken: accessToken,
|
AccessToken: accessToken,
|
||||||
RefreshToken: refreshToken,
|
RefreshToken: refreshToken,
|
||||||
ExpiresIn: application.ExpireInHours * 60,
|
ExpiresIn: application.ExpireInHours * hourSeconds,
|
||||||
Scope: scope,
|
Scope: scope,
|
||||||
TokenType: "Bearer",
|
TokenType: "Bearer",
|
||||||
CodeIsUsed: true,
|
CodeIsUsed: true,
|
||||||
@ -608,3 +641,74 @@ func GetTokenByUser(application *Application, user *User, scope string, host str
|
|||||||
AddToken(token)
|
AddToken(token)
|
||||||
return token, nil
|
return token, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wechat Mini Program flow
|
||||||
|
func GetWechatMiniProgramToken(application *Application, code string, host string, username string, avatar string) (*Token, error) {
|
||||||
|
mpProvider := GetWechatMiniProgramProvider(application)
|
||||||
|
if mpProvider == nil {
|
||||||
|
return nil, errors.New("error: the application does not support wechat mini program")
|
||||||
|
}
|
||||||
|
provider := GetProvider(util.GetId(mpProvider.Name))
|
||||||
|
mpIdp := idp.NewWeChatMiniProgramIdProvider(provider.ClientId, provider.ClientSecret)
|
||||||
|
session, err := mpIdp.GetSessionByCode(code)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
openId, unionId := session.Openid, session.Unionid
|
||||||
|
if openId == "" && unionId == "" {
|
||||||
|
return nil, errors.New("err: WeChat's openid and unionid are empty")
|
||||||
|
}
|
||||||
|
user := getUserByWechatId(openId, unionId)
|
||||||
|
if user == nil {
|
||||||
|
if !application.EnableSignUp {
|
||||||
|
return nil, errors.New("err: the application does not allow to sign up new account")
|
||||||
|
}
|
||||||
|
//Add new user
|
||||||
|
var name string
|
||||||
|
if username != "" {
|
||||||
|
name = username
|
||||||
|
} else {
|
||||||
|
name = fmt.Sprintf("wechat-%s", openId)
|
||||||
|
}
|
||||||
|
|
||||||
|
user = &User{
|
||||||
|
Owner: application.Organization,
|
||||||
|
Id: util.GenerateId(),
|
||||||
|
Name: name,
|
||||||
|
Avatar: avatar,
|
||||||
|
SignupApplication: application.Name,
|
||||||
|
WeChat: openId,
|
||||||
|
WeChatUnionId: unionId,
|
||||||
|
Type: "normal-user",
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
IsAdmin: false,
|
||||||
|
IsGlobalAdmin: false,
|
||||||
|
IsForbidden: false,
|
||||||
|
IsDeleted: false,
|
||||||
|
}
|
||||||
|
AddUser(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
accessToken, refreshToken, err := generateJwtToken(application, user, "", "", host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
token := &Token{
|
||||||
|
Owner: application.Owner,
|
||||||
|
Name: util.GenerateId(),
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
Application: application.Name,
|
||||||
|
Organization: user.Owner,
|
||||||
|
User: user.Name,
|
||||||
|
Code: session.SessionKey, //a trick, because miniprogram does not use the code, so use the code field to save the session_key
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
ExpiresIn: application.ExpireInHours * 60,
|
||||||
|
Scope: "",
|
||||||
|
TokenType: "Bearer",
|
||||||
|
CodeIsUsed: true,
|
||||||
|
}
|
||||||
|
AddToken(token)
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
@ -15,14 +15,19 @@
|
|||||||
package object
|
package object
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"encoding/pem"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/beevik/etree"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
|
dsig "github.com/russellhaering/goxmldsig"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CasServiceResponse struct {
|
type CasServiceResponse struct {
|
||||||
@ -84,6 +89,7 @@ type CasAnyAttribute struct {
|
|||||||
type CasAuthenticationSuccessWrapper struct {
|
type CasAuthenticationSuccessWrapper struct {
|
||||||
AuthenticationSuccess *CasAuthenticationSuccess // the token we issued
|
AuthenticationSuccess *CasAuthenticationSuccess // the token we issued
|
||||||
Service string //to which service this token is issued
|
Service string //to which service this token is issued
|
||||||
|
UserId string
|
||||||
}
|
}
|
||||||
|
|
||||||
type CasProxySuccess struct {
|
type CasProxySuccess struct {
|
||||||
@ -96,17 +102,32 @@ type CasProxyFailure struct {
|
|||||||
Message string `xml:",innerxml"`
|
Message string `xml:",innerxml"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Saml11Request struct {
|
||||||
|
XMLName xml.Name `xml:"Request"`
|
||||||
|
SAMLP string `xml:"samlp,attr"`
|
||||||
|
MajorVersion string `xml:"MajorVersion,attr"`
|
||||||
|
MinorVersion string `xml:"MinorVersion,attr"`
|
||||||
|
RequestID string `xml:"RequestID,attr"`
|
||||||
|
IssueInstant string `xml:"IssueInstance,attr"`
|
||||||
|
AssertionArtifact Saml11AssertionArtifact
|
||||||
|
}
|
||||||
|
type Saml11AssertionArtifact struct {
|
||||||
|
XMLName xml.Name `xml:"AssertionArtifact"`
|
||||||
|
InnerXML string `xml:",innerxml"`
|
||||||
|
}
|
||||||
|
|
||||||
//st is short for service ticket
|
//st is short for service ticket
|
||||||
var stToServiceResponse sync.Map
|
var stToServiceResponse sync.Map
|
||||||
|
|
||||||
//pgt is short for proxy granting ticket
|
//pgt is short for proxy granting ticket
|
||||||
var pgtToServiceResponse sync.Map
|
var pgtToServiceResponse sync.Map
|
||||||
|
|
||||||
func StoreCasTokenForPgt(token *CasAuthenticationSuccess, service string) string {
|
func StoreCasTokenForPgt(token *CasAuthenticationSuccess, service, userId string) string {
|
||||||
pgt := fmt.Sprintf("PGT-%s", util.GenerateId())
|
pgt := fmt.Sprintf("PGT-%s", util.GenerateId())
|
||||||
pgtToServiceResponse.Store(pgt, &CasAuthenticationSuccessWrapper{
|
pgtToServiceResponse.Store(pgt, &CasAuthenticationSuccessWrapper{
|
||||||
AuthenticationSuccess: token,
|
AuthenticationSuccess: token,
|
||||||
Service: service,
|
Service: service,
|
||||||
|
UserId: userId,
|
||||||
})
|
})
|
||||||
return pgt
|
return pgt
|
||||||
}
|
}
|
||||||
@ -115,33 +136,45 @@ func GenerateId() {
|
|||||||
panic("unimplemented")
|
panic("unimplemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetCasTokenByPgt(pgt string) (bool, *CasAuthenticationSuccess, string) {
|
/**
|
||||||
|
@ret1: whether a token is found
|
||||||
|
@ret2: token, nil if not found
|
||||||
|
@ret3: the service URL who requested to issue this token
|
||||||
|
@ret4: userIf of user who requested to issue this token
|
||||||
|
*/
|
||||||
|
func GetCasTokenByPgt(pgt string) (bool, *CasAuthenticationSuccess, string, string) {
|
||||||
if responseWrapperType, ok := pgtToServiceResponse.LoadAndDelete(pgt); ok {
|
if responseWrapperType, ok := pgtToServiceResponse.LoadAndDelete(pgt); ok {
|
||||||
responseWrapperTypeCast := responseWrapperType.(*CasAuthenticationSuccessWrapper)
|
responseWrapperTypeCast := responseWrapperType.(*CasAuthenticationSuccessWrapper)
|
||||||
return true, responseWrapperTypeCast.AuthenticationSuccess, responseWrapperTypeCast.Service
|
return true, responseWrapperTypeCast.AuthenticationSuccess, responseWrapperTypeCast.Service, responseWrapperTypeCast.UserId
|
||||||
}
|
}
|
||||||
return false, nil, ""
|
return false, nil, "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetCasTokenByTicket(ticket string) (bool, *CasAuthenticationSuccess, string) {
|
/**
|
||||||
|
@ret1: whether a token is found
|
||||||
|
@ret2: token, nil if not found
|
||||||
|
@ret3: the service URL who requested to issue this token
|
||||||
|
@ret4: userIf of user who requested to issue this token
|
||||||
|
*/
|
||||||
|
func GetCasTokenByTicket(ticket string) (bool, *CasAuthenticationSuccess, string, string) {
|
||||||
if responseWrapperType, ok := stToServiceResponse.LoadAndDelete(ticket); ok {
|
if responseWrapperType, ok := stToServiceResponse.LoadAndDelete(ticket); ok {
|
||||||
responseWrapperTypeCast := responseWrapperType.(*CasAuthenticationSuccessWrapper)
|
responseWrapperTypeCast := responseWrapperType.(*CasAuthenticationSuccessWrapper)
|
||||||
return true, responseWrapperTypeCast.AuthenticationSuccess, responseWrapperTypeCast.Service
|
return true, responseWrapperTypeCast.AuthenticationSuccess, responseWrapperTypeCast.Service, responseWrapperTypeCast.UserId
|
||||||
}
|
}
|
||||||
return false, nil, ""
|
return false, nil, "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func StoreCasTokenForProxyTicket(token *CasAuthenticationSuccess, targetService string) string {
|
func StoreCasTokenForProxyTicket(token *CasAuthenticationSuccess, targetService, userId string) string {
|
||||||
proxyTicket := fmt.Sprintf("PT-%s", util.GenerateId())
|
proxyTicket := fmt.Sprintf("PT-%s", util.GenerateId())
|
||||||
stToServiceResponse.Store(proxyTicket, &CasAuthenticationSuccessWrapper{
|
stToServiceResponse.Store(proxyTicket, &CasAuthenticationSuccessWrapper{
|
||||||
AuthenticationSuccess: token,
|
AuthenticationSuccess: token,
|
||||||
Service: targetService,
|
Service: targetService,
|
||||||
|
UserId: userId,
|
||||||
})
|
})
|
||||||
return proxyTicket
|
return proxyTicket
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateCasToken(userId string, service string) (string, error) {
|
func GenerateCasToken(userId string, service string) (string, error) {
|
||||||
|
|
||||||
if user := GetUser(userId); user != nil {
|
if user := GetUser(userId); user != nil {
|
||||||
authenticationSuccess := CasAuthenticationSuccess{
|
authenticationSuccess := CasAuthenticationSuccess{
|
||||||
User: user.Name,
|
User: user.Name,
|
||||||
@ -166,11 +199,69 @@ func GenerateCasToken(userId string, service string) (string, error) {
|
|||||||
stToServiceResponse.Store(st, &CasAuthenticationSuccessWrapper{
|
stToServiceResponse.Store(st, &CasAuthenticationSuccessWrapper{
|
||||||
AuthenticationSuccess: &authenticationSuccess,
|
AuthenticationSuccess: &authenticationSuccess,
|
||||||
Service: service,
|
Service: service,
|
||||||
|
UserId: userId,
|
||||||
})
|
})
|
||||||
return st, nil
|
return st, nil
|
||||||
} else {
|
} else {
|
||||||
return "", fmt.Errorf("invalid user Id")
|
return "", fmt.Errorf("invalid user Id")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@ret1: saml response
|
||||||
|
@ret2: the service URL who requested to issue this token
|
||||||
|
@ret3: error
|
||||||
|
*/
|
||||||
|
func GetValidationBySaml(samlRequest string, host string) (string, string, error) {
|
||||||
|
var request Saml11Request
|
||||||
|
err := xml.Unmarshal([]byte(samlRequest), &request)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
ticket := request.AssertionArtifact.InnerXML
|
||||||
|
if ticket == "" {
|
||||||
|
return "", "", fmt.Errorf("samlp:AssertionArtifact field not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, _, service, userId := GetCasTokenByTicket(ticket)
|
||||||
|
if !ok {
|
||||||
|
return "", "", fmt.Errorf("ticket %s found", ticket)
|
||||||
|
}
|
||||||
|
|
||||||
|
user := GetUser(userId)
|
||||||
|
if user == nil {
|
||||||
|
return "", "", fmt.Errorf("user %s found", userId)
|
||||||
|
}
|
||||||
|
application := GetApplicationByUser(user)
|
||||||
|
if application == nil {
|
||||||
|
return "", "", fmt.Errorf("application for user %s found", userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
samlResponse := NewSamlResponse11(user, request.RequestID, host)
|
||||||
|
|
||||||
|
cert := getCertByApplication(application)
|
||||||
|
block, _ := pem.Decode([]byte(cert.PublicKey))
|
||||||
|
publicKey := base64.StdEncoding.EncodeToString(block.Bytes)
|
||||||
|
randomKeyStore := &X509Key{
|
||||||
|
PrivateKey: cert.PrivateKey,
|
||||||
|
X509Certificate: publicKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := dsig.NewDefaultSigningContext(randomKeyStore)
|
||||||
|
ctx.Hash = crypto.SHA1
|
||||||
|
signedXML, err := ctx.SignEnveloped(samlResponse)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("err: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
doc := etree.NewDocument()
|
||||||
|
doc.SetRoot(signedXML)
|
||||||
|
xmlStr, err := doc.WriteToString()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("err: %s", err.Error())
|
||||||
|
}
|
||||||
|
return xmlStr, service, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,6 +39,7 @@ type User struct {
|
|||||||
Avatar string `xorm:"varchar(500)" json:"avatar"`
|
Avatar string `xorm:"varchar(500)" json:"avatar"`
|
||||||
PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"`
|
PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"`
|
||||||
Email string `xorm:"varchar(100) index" json:"email"`
|
Email string `xorm:"varchar(100) index" json:"email"`
|
||||||
|
EmailVerified bool `json:"emailVerified"`
|
||||||
Phone string `xorm:"varchar(100) index" json:"phone"`
|
Phone string `xorm:"varchar(100) index" json:"phone"`
|
||||||
Location string `xorm:"varchar(100)" json:"location"`
|
Location string `xorm:"varchar(100)" json:"location"`
|
||||||
Address []string `json:"address"`
|
Address []string `json:"address"`
|
||||||
@ -75,6 +76,7 @@ type User struct {
|
|||||||
Google string `xorm:"varchar(100)" json:"google"`
|
Google string `xorm:"varchar(100)" json:"google"`
|
||||||
QQ string `xorm:"qq varchar(100)" json:"qq"`
|
QQ string `xorm:"qq varchar(100)" json:"qq"`
|
||||||
WeChat string `xorm:"wechat varchar(100)" json:"wechat"`
|
WeChat string `xorm:"wechat varchar(100)" json:"wechat"`
|
||||||
|
WeChatUnionId string `xorm:"varchar(100)" json:"unionId"`
|
||||||
Facebook string `xorm:"facebook varchar(100)" json:"facebook"`
|
Facebook string `xorm:"facebook varchar(100)" json:"facebook"`
|
||||||
DingTalk string `xorm:"dingtalk varchar(100)" json:"dingtalk"`
|
DingTalk string `xorm:"dingtalk varchar(100)" json:"dingtalk"`
|
||||||
Weibo string `xorm:"weibo varchar(100)" json:"weibo"`
|
Weibo string `xorm:"weibo varchar(100)" json:"weibo"`
|
||||||
@ -92,6 +94,10 @@ type User struct {
|
|||||||
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"`
|
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"`
|
||||||
Slack string `xorm:"slack varchar(100)" json:"slack"`
|
Slack string `xorm:"slack varchar(100)" json:"slack"`
|
||||||
Steam string `xorm:"steam varchar(100)" json:"steam"`
|
Steam string `xorm:"steam varchar(100)" json:"steam"`
|
||||||
|
Bilibili string `xorm:"bilibili varchar(100)" json:"bilibili"`
|
||||||
|
Okta string `xorm:"okta varchar(100)" json:"okta"`
|
||||||
|
Douyin string `xorm:"douyin vachar(100)" json:"douyin"`
|
||||||
|
Custom string `xorm:"custom varchar(100)" json:"custom"`
|
||||||
|
|
||||||
Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
|
Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
|
||||||
Properties map[string]string `json:"properties"`
|
Properties map[string]string `json:"properties"`
|
||||||
@ -226,6 +232,23 @@ func getUserById(owner string, id string) *User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getUserByWechatId(wechatOpenId string, wechatUnionId string) *User {
|
||||||
|
if wechatUnionId == "" {
|
||||||
|
wechatUnionId = wechatOpenId
|
||||||
|
}
|
||||||
|
user := &User{}
|
||||||
|
existed, err := adapter.Engine.Where("wechat = ? OR wechat = ? OR unionid = ?", wechatOpenId, wechatUnionId, wechatUnionId).Get(user)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return user
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func GetUserByEmail(owner string, email string) *User {
|
func GetUserByEmail(owner string, email string) *User {
|
||||||
if owner == "" || email == "" {
|
if owner == "" || email == "" {
|
||||||
return nil
|
return nil
|
||||||
@ -293,6 +316,9 @@ func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) boo
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if user.Password == "***" {
|
||||||
|
user.Password = oldUser.Password
|
||||||
|
}
|
||||||
user.UpdateUserHash()
|
user.UpdateUserHash()
|
||||||
|
|
||||||
if user.Avatar != oldUser.Avatar && user.Avatar != "" && user.PermanentAvatar != "*" {
|
if user.Avatar != oldUser.Avatar && user.Avatar != "" && user.PermanentAvatar != "*" {
|
||||||
|
@ -97,3 +97,14 @@ func TestGetMaskedUsers(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetUserByField(t *testing.T) {
|
||||||
|
InitConfig()
|
||||||
|
|
||||||
|
user := GetUserByField("built-in", "DingTalk", "test")
|
||||||
|
if user != nil {
|
||||||
|
t.Logf("%+v", user)
|
||||||
|
} else {
|
||||||
|
t.Log("no user found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -29,7 +29,7 @@ func GetUserByField(organizationName string, field string, value string) *User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
user := User{Owner: organizationName}
|
user := User{Owner: organizationName}
|
||||||
existed, err := adapter.Engine.Where(fmt.Sprintf("%s=?", field), value).Get(&user)
|
existed, err := adapter.Engine.Where(fmt.Sprintf("%s=?", strings.ToLower(field)), value).Get(&user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ func NewAlipayPaymentProvider(appId string, appPublicKey string, appPrivateKey s
|
|||||||
return pp
|
return pp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *AlipayPaymentProvider) Pay(providerName string, productName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) {
|
func (pp *AlipayPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) {
|
||||||
//pp.Client.DebugSwitch = gopay.DebugOn
|
//pp.Client.DebugSwitch = gopay.DebugOn
|
||||||
|
|
||||||
bm := gopay.BodyMap{}
|
bm := gopay.BodyMap{}
|
||||||
@ -90,3 +90,7 @@ func (pp *AlipayPaymentProvider) Notify(request *http.Request, body []byte, auth
|
|||||||
|
|
||||||
return productDisplayName, paymentName, price, productName, providerName, nil
|
return productDisplayName, paymentName, price, productName, providerName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pp *AlipayPaymentProvider) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
112
pp/gc.go
112
pp/gc.go
@ -38,11 +38,14 @@ type GcPayReqInfo struct {
|
|||||||
OrderDate string `json:"orderdate"`
|
OrderDate string `json:"orderdate"`
|
||||||
OrderNo string `json:"orderno"`
|
OrderNo string `json:"orderno"`
|
||||||
Amount string `json:"amount"`
|
Amount string `json:"amount"`
|
||||||
PayerId string `json:"payerid"`
|
|
||||||
PayerName string `json:"payername"`
|
|
||||||
Xmpch string `json:"xmpch"`
|
Xmpch string `json:"xmpch"`
|
||||||
|
Body string `json:"body"`
|
||||||
ReturnUrl string `json:"return_url"`
|
ReturnUrl string `json:"return_url"`
|
||||||
NotifyUrl string `json:"notify_url"`
|
NotifyUrl string `json:"notify_url"`
|
||||||
|
PayerId string `json:"payerid"`
|
||||||
|
PayerName string `json:"payername"`
|
||||||
|
Remark1 string `json:"remark1"`
|
||||||
|
Remark2 string `json:"remark2"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GcPayRespInfo struct {
|
type GcPayRespInfo struct {
|
||||||
@ -87,6 +90,27 @@ type GcResponseBody struct {
|
|||||||
Sign string `json:"sign"`
|
Sign string `json:"sign"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GcInvoiceReqInfo struct {
|
||||||
|
BusNo string `json:"busno"`
|
||||||
|
PayerName string `json:"payername"`
|
||||||
|
IdNum string `json:"idnum"`
|
||||||
|
PayerType string `json:"payertype"`
|
||||||
|
InvoiceTitle string `json:"invoicetitle"`
|
||||||
|
Tin string `json:"tin"`
|
||||||
|
Phone string `json:"phone"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GcInvoiceRespInfo struct {
|
||||||
|
BusNo string `json:"busno"`
|
||||||
|
State string `json:"state"`
|
||||||
|
EbillCode string `json:"ebillcode"`
|
||||||
|
EbillNo string `json:"ebillno"`
|
||||||
|
CheckCode string `json:"checkcode"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
func NewGcPaymentProvider(clientId string, clientSecret string, host string) *GcPaymentProvider {
|
func NewGcPaymentProvider(clientId string, clientSecret string, host string) *GcPaymentProvider {
|
||||||
pp := &GcPaymentProvider{}
|
pp := &GcPaymentProvider{}
|
||||||
|
|
||||||
@ -130,16 +154,17 @@ func (pp *GcPaymentProvider) doPost(postBytes []byte) ([]byte, error) {
|
|||||||
return respBytes, nil
|
return respBytes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *GcPaymentProvider) Pay(providerName string, productName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) {
|
func (pp *GcPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) {
|
||||||
payReqInfo := GcPayReqInfo{
|
payReqInfo := GcPayReqInfo{
|
||||||
OrderDate: util.GenerateSimpleTimeId(),
|
OrderDate: util.GenerateSimpleTimeId(),
|
||||||
OrderNo: util.GenerateTimeId(),
|
OrderNo: paymentName,
|
||||||
Amount: getPriceString(price),
|
Amount: getPriceString(price),
|
||||||
PayerId: "",
|
|
||||||
PayerName: "",
|
|
||||||
Xmpch: pp.Xmpch,
|
Xmpch: pp.Xmpch,
|
||||||
|
Body: productDisplayName,
|
||||||
ReturnUrl: returnUrl,
|
ReturnUrl: returnUrl,
|
||||||
NotifyUrl: notifyUrl,
|
NotifyUrl: notifyUrl,
|
||||||
|
Remark1: payerName,
|
||||||
|
Remark2: productName,
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := json.Marshal(payReqInfo)
|
b, err := json.Marshal(payReqInfo)
|
||||||
@ -230,3 +255,78 @@ func (pp *GcPaymentProvider) Notify(request *http.Request, body []byte, authorit
|
|||||||
|
|
||||||
return productDisplayName, paymentName, price, productName, providerName, nil
|
return productDisplayName, paymentName, price, productName, providerName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pp *GcPaymentProvider) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) {
|
||||||
|
payerType := "0"
|
||||||
|
if invoiceType == "Organization" {
|
||||||
|
payerType = "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
invoiceReqInfo := GcInvoiceReqInfo{
|
||||||
|
BusNo: paymentName,
|
||||||
|
PayerName: personName,
|
||||||
|
IdNum: personIdCard,
|
||||||
|
PayerType: payerType,
|
||||||
|
InvoiceTitle: invoiceTitle,
|
||||||
|
Tin: invoiceTaxId,
|
||||||
|
Phone: personPhone,
|
||||||
|
Email: personEmail,
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(invoiceReqInfo)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
body := GcRequestBody{
|
||||||
|
Op: "InvoiceEBillByOrder",
|
||||||
|
Xmpch: pp.Xmpch,
|
||||||
|
Version: "1.4",
|
||||||
|
Data: base64.StdEncoding.EncodeToString(b),
|
||||||
|
RequestTime: util.GenerateSimpleTimeId(),
|
||||||
|
}
|
||||||
|
|
||||||
|
params := fmt.Sprintf("data=%s&op=%s&requesttime=%s&version=%s&xmpch=%s%s", body.Data, body.Op, body.RequestTime, body.Version, body.Xmpch, pp.SecretKey)
|
||||||
|
body.Sign = strings.ToUpper(util.GetMd5Hash(params))
|
||||||
|
|
||||||
|
bodyBytes, err := json.Marshal(body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
respBytes, err := pp.doPost(bodyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var respBody GcResponseBody
|
||||||
|
err = json.Unmarshal(respBytes, &respBody)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if respBody.ReturnCode != "SUCCESS" {
|
||||||
|
return "", fmt.Errorf("%s: %s", respBody.ReturnCode, respBody.ReturnMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
invoiceRespInfoBytes, err := base64.StdEncoding.DecodeString(respBody.Data)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var invoiceRespInfo GcInvoiceRespInfo
|
||||||
|
err = json.Unmarshal(invoiceRespInfoBytes, &invoiceRespInfo)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if invoiceRespInfo.State == "0" {
|
||||||
|
return "", fmt.Errorf("申请成功,开票中")
|
||||||
|
}
|
||||||
|
|
||||||
|
if invoiceRespInfo.Url == "" {
|
||||||
|
return "", fmt.Errorf("invoice URL is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
return invoiceRespInfo.Url, nil
|
||||||
|
}
|
||||||
|
@ -17,8 +17,9 @@ package pp
|
|||||||
import "net/http"
|
import "net/http"
|
||||||
|
|
||||||
type PaymentProvider interface {
|
type PaymentProvider interface {
|
||||||
Pay(providerName string, productName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error)
|
Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error)
|
||||||
Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error)
|
Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error)
|
||||||
|
GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPaymentProvider(typ string, appId string, clientSecret string, host string, appPublicKey string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) PaymentProvider {
|
func GetPaymentProvider(typ string, appId string, clientSecret string, host string, appPublicKey string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) PaymentProvider {
|
||||||
|
@ -76,7 +76,7 @@ func getProxyHttpClient() *http.Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetHttpClient(url string) *http.Client {
|
func GetHttpClient(url string) *http.Client {
|
||||||
if strings.Contains(url, "githubusercontent.com") {
|
if strings.Contains(url, "githubusercontent.com") || strings.Contains(url, "googleusercontent.com") {
|
||||||
return ProxyHttpClient
|
return ProxyHttpClient
|
||||||
} else {
|
} else {
|
||||||
return DefaultHttpClient
|
return DefaultHttpClient
|
||||||
|
@ -101,9 +101,14 @@ func willLog(subOwner string, subName string, method string, urlPath string, obj
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getUrlPath(urlPath string) string {
|
func getUrlPath(urlPath string) string {
|
||||||
if strings.HasPrefix(urlPath, "/cas") && (strings.HasSuffix(urlPath, "/serviceValidate") || strings.HasSuffix(urlPath, "/proxy") || strings.HasSuffix(urlPath, "/proxyValidate") || strings.HasSuffix(urlPath, "/validate")) {
|
if strings.HasPrefix(urlPath, "/cas") && (strings.HasSuffix(urlPath, "/serviceValidate") || strings.HasSuffix(urlPath, "/proxy") || strings.HasSuffix(urlPath, "/proxyValidate") || strings.HasSuffix(urlPath, "/validate") || strings.HasSuffix(urlPath, "/p3/serviceValidate") || strings.HasSuffix(urlPath, "/p3/proxyValidate") || strings.HasSuffix(urlPath, "/samlValidate")) {
|
||||||
return "/cas"
|
return "/cas"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(urlPath, "/api/login/oauth") {
|
||||||
|
return "/api/login/oauth"
|
||||||
|
}
|
||||||
|
|
||||||
return urlPath
|
return urlPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
49
routers/cors_filter.go
Normal file
49
routers/cors_filter.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// 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 routers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/context"
|
||||||
|
"github.com/casdoor/casdoor/conf"
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
headerOrigin = "Origin"
|
||||||
|
headerAllowOrigin = "Access-Control-Allow-Origin"
|
||||||
|
headerAllowMethods = "Access-Control-Allow-Methods"
|
||||||
|
headerAllowHeaders = "Access-Control-Allow-Headers"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CorsFilter(ctx *context.Context) {
|
||||||
|
origin := ctx.Input.Header(headerOrigin)
|
||||||
|
if origin != "" && origin != conf.GetConfigString("origin") {
|
||||||
|
if object.IsAllowOrigin(origin) {
|
||||||
|
ctx.Output.Header(headerAllowOrigin, origin)
|
||||||
|
ctx.Output.Header(headerAllowMethods, "POST, GET, OPTIONS")
|
||||||
|
ctx.Output.Header(headerAllowHeaders, "Content-Type, Authorization")
|
||||||
|
} else {
|
||||||
|
ctx.ResponseWriter.WriteHeader(http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.Input.Method() == "OPTIONS" {
|
||||||
|
ctx.ResponseWriter.WriteHeader(http.StatusOK)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -65,5 +65,5 @@ func RecordMessage(ctx *context.Context) {
|
|||||||
record.Organization, record.User = util.GetOwnerAndNameFromId(userId)
|
record.Organization, record.User = util.GetOwnerAndNameFromId(userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
go object.AddRecord(record)
|
util.SafeGoroutine(func() { object.AddRecord(record) })
|
||||||
}
|
}
|
||||||
|
@ -54,6 +54,7 @@ func initAPI() {
|
|||||||
beego.Router("/api/unlink", &controllers.ApiController{}, "POST:Unlink")
|
beego.Router("/api/unlink", &controllers.ApiController{}, "POST:Unlink")
|
||||||
beego.Router("/api/get-saml-login", &controllers.ApiController{}, "GET:GetSamlLogin")
|
beego.Router("/api/get-saml-login", &controllers.ApiController{}, "GET:GetSamlLogin")
|
||||||
beego.Router("/api/acs", &controllers.ApiController{}, "POST:HandleSamlLogin")
|
beego.Router("/api/acs", &controllers.ApiController{}, "POST:HandleSamlLogin")
|
||||||
|
beego.Router("/api/saml/metadata", &controllers.ApiController{}, "GET:GetSamlMeta")
|
||||||
|
|
||||||
beego.Router("/api/get-organizations", &controllers.ApiController{}, "GET:GetOrganizations")
|
beego.Router("/api/get-organizations", &controllers.ApiController{}, "GET:GetOrganizations")
|
||||||
beego.Router("/api/get-organization", &controllers.ApiController{}, "GET:GetOrganization")
|
beego.Router("/api/get-organization", &controllers.ApiController{}, "GET:GetOrganization")
|
||||||
@ -83,12 +84,19 @@ func initAPI() {
|
|||||||
beego.Router("/api/add-permission", &controllers.ApiController{}, "POST:AddPermission")
|
beego.Router("/api/add-permission", &controllers.ApiController{}, "POST:AddPermission")
|
||||||
beego.Router("/api/delete-permission", &controllers.ApiController{}, "POST:DeletePermission")
|
beego.Router("/api/delete-permission", &controllers.ApiController{}, "POST:DeletePermission")
|
||||||
|
|
||||||
|
beego.Router("/api/get-models", &controllers.ApiController{}, "GET:GetModels")
|
||||||
|
beego.Router("/api/get-model", &controllers.ApiController{}, "GET:GetModel")
|
||||||
|
beego.Router("/api/update-model", &controllers.ApiController{}, "POST:UpdateModel")
|
||||||
|
beego.Router("/api/add-model", &controllers.ApiController{}, "POST:AddModel")
|
||||||
|
beego.Router("/api/delete-model", &controllers.ApiController{}, "POST:DeleteModel")
|
||||||
|
|
||||||
beego.Router("/api/set-password", &controllers.ApiController{}, "POST:SetPassword")
|
beego.Router("/api/set-password", &controllers.ApiController{}, "POST:SetPassword")
|
||||||
beego.Router("/api/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword")
|
beego.Router("/api/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword")
|
||||||
beego.Router("/api/get-email-and-phone", &controllers.ApiController{}, "POST:GetEmailAndPhone")
|
beego.Router("/api/get-email-and-phone", &controllers.ApiController{}, "POST:GetEmailAndPhone")
|
||||||
beego.Router("/api/send-verification-code", &controllers.ApiController{}, "POST:SendVerificationCode")
|
beego.Router("/api/send-verification-code", &controllers.ApiController{}, "POST:SendVerificationCode")
|
||||||
|
beego.Router("/api/verify-captcha", &controllers.ApiController{}, "POST:VerifyCaptcha")
|
||||||
beego.Router("/api/reset-email-or-phone", &controllers.ApiController{}, "POST:ResetEmailOrPhone")
|
beego.Router("/api/reset-email-or-phone", &controllers.ApiController{}, "POST:ResetEmailOrPhone")
|
||||||
beego.Router("/api/get-human-check", &controllers.ApiController{}, "GET:GetHumanCheck")
|
beego.Router("/api/get-captcha", &controllers.ApiController{}, "GET:GetCaptcha")
|
||||||
|
|
||||||
beego.Router("/api/get-ldap-user", &controllers.ApiController{}, "POST:GetLdapUser")
|
beego.Router("/api/get-ldap-user", &controllers.ApiController{}, "POST:GetLdapUser")
|
||||||
beego.Router("/api/get-ldaps", &controllers.ApiController{}, "POST:GetLdaps")
|
beego.Router("/api/get-ldaps", &controllers.ApiController{}, "POST:GetLdaps")
|
||||||
@ -144,6 +152,7 @@ func initAPI() {
|
|||||||
beego.Router("/api/update-syncer", &controllers.ApiController{}, "POST:UpdateSyncer")
|
beego.Router("/api/update-syncer", &controllers.ApiController{}, "POST:UpdateSyncer")
|
||||||
beego.Router("/api/add-syncer", &controllers.ApiController{}, "POST:AddSyncer")
|
beego.Router("/api/add-syncer", &controllers.ApiController{}, "POST:AddSyncer")
|
||||||
beego.Router("/api/delete-syncer", &controllers.ApiController{}, "POST:DeleteSyncer")
|
beego.Router("/api/delete-syncer", &controllers.ApiController{}, "POST:DeleteSyncer")
|
||||||
|
beego.Router("/api/run-syncer", &controllers.ApiController{}, "GET:RunSyncer")
|
||||||
|
|
||||||
beego.Router("/api/get-certs", &controllers.ApiController{}, "GET:GetCerts")
|
beego.Router("/api/get-certs", &controllers.ApiController{}, "GET:GetCerts")
|
||||||
beego.Router("/api/get-cert", &controllers.ApiController{}, "GET:GetCert")
|
beego.Router("/api/get-cert", &controllers.ApiController{}, "GET:GetCert")
|
||||||
@ -165,6 +174,7 @@ func initAPI() {
|
|||||||
beego.Router("/api/add-payment", &controllers.ApiController{}, "POST:AddPayment")
|
beego.Router("/api/add-payment", &controllers.ApiController{}, "POST:AddPayment")
|
||||||
beego.Router("/api/delete-payment", &controllers.ApiController{}, "POST:DeletePayment")
|
beego.Router("/api/delete-payment", &controllers.ApiController{}, "POST:DeletePayment")
|
||||||
beego.Router("/api/notify-payment/?:owner/?:provider/?:product/?:payment", &controllers.ApiController{}, "POST:NotifyPayment")
|
beego.Router("/api/notify-payment/?:owner/?:provider/?:product/?:payment", &controllers.ApiController{}, "POST:NotifyPayment")
|
||||||
|
beego.Router("/api/invoice-payment", &controllers.ApiController{}, "POST:InvoicePayment")
|
||||||
|
|
||||||
beego.Router("/api/send-email", &controllers.ApiController{}, "POST:SendEmail")
|
beego.Router("/api/send-email", &controllers.ApiController{}, "POST:SendEmail")
|
||||||
beego.Router("/api/send-sms", &controllers.ApiController{}, "POST:SendSms")
|
beego.Router("/api/send-sms", &controllers.ApiController{}, "POST:SendSms")
|
||||||
@ -172,9 +182,13 @@ func initAPI() {
|
|||||||
beego.Router("/.well-known/openid-configuration", &controllers.RootController{}, "GET:GetOidcDiscovery")
|
beego.Router("/.well-known/openid-configuration", &controllers.RootController{}, "GET:GetOidcDiscovery")
|
||||||
beego.Router("/.well-known/jwks", &controllers.RootController{}, "*:GetJwks")
|
beego.Router("/.well-known/jwks", &controllers.RootController{}, "*:GetJwks")
|
||||||
|
|
||||||
beego.Router("/cas/:organization/:application/serviceValidate", &controllers.RootController{}, "GET:CasServiceAndProxyValidate")
|
beego.Router("/cas/:organization/:application/serviceValidate", &controllers.RootController{}, "GET:CasServiceValidate")
|
||||||
beego.Router("/cas/:organization/:application/proxyValidate", &controllers.RootController{}, "GET:CasServiceAndProxyValidate")
|
beego.Router("/cas/:organization/:application/proxyValidate", &controllers.RootController{}, "GET:CasProxyValidate")
|
||||||
beego.Router("/cas/:organization/:application/proxy", &controllers.RootController{}, "GET:CasProxy")
|
beego.Router("/cas/:organization/:application/proxy", &controllers.RootController{}, "GET:CasProxy")
|
||||||
beego.Router("/cas/:organization/:application/validate", &controllers.RootController{}, "GET:CasValidate")
|
beego.Router("/cas/:organization/:application/validate", &controllers.RootController{}, "GET:CasValidate")
|
||||||
|
|
||||||
|
beego.Router("/cas/:organization/:application/p3/serviceValidate", &controllers.RootController{}, "GET:CasP3ServiceAndProxyValidate")
|
||||||
|
beego.Router("/cas/:organization/:application/p3/proxyValidate", &controllers.RootController{}, "GET:CasP3ServiceAndProxyValidate")
|
||||||
|
beego.Router("/cas/:organization/:application/samlValidate", &controllers.RootController{}, "POST:SamlValidate")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ func StaticFilter(ctx *context.Context) {
|
|||||||
if strings.HasPrefix(urlPath, "/api/") || strings.HasPrefix(urlPath, "/.well-known/") {
|
if strings.HasPrefix(urlPath, "/api/") || strings.HasPrefix(urlPath, "/.well-known/") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(urlPath, "/cas") && (strings.HasSuffix(urlPath, "/serviceValidate") || strings.HasSuffix(urlPath, "/proxy") || strings.HasSuffix(urlPath, "/proxyValidate") || strings.HasSuffix(urlPath, "/validate")) {
|
if strings.HasPrefix(urlPath, "/cas") && (strings.HasSuffix(urlPath, "/serviceValidate") || strings.HasSuffix(urlPath, "/proxy") || strings.HasSuffix(urlPath, "/proxyValidate") || strings.HasSuffix(urlPath, "/validate") || strings.HasSuffix(urlPath, "/p3/serviceValidate") || strings.HasSuffix(urlPath, "/p3/proxyValidate") || strings.HasSuffix(urlPath, "/samlValidate")) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,8 +15,8 @@
|
|||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/qor/oss"
|
"github.com/casdoor/oss"
|
||||||
"github.com/qor/oss/aliyun"
|
"github.com/casdoor/oss/aliyun"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewAliyunOssStorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
|
func NewAliyunOssStorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
|
||||||
|
@ -16,8 +16,8 @@ package storage
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
awss3 "github.com/aws/aws-sdk-go/service/s3"
|
awss3 "github.com/aws/aws-sdk-go/service/s3"
|
||||||
"github.com/qor/oss"
|
"github.com/casdoor/oss"
|
||||||
"github.com/qor/oss/s3"
|
"github.com/casdoor/oss/s3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewAwsS3StorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
|
func NewAwsS3StorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
|
||||||
|
31
storage/azure.go
Normal file
31
storage/azure.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// 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 storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/casdoor/oss"
|
||||||
|
"github.com/casdoor/oss/azureblob"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewAzureBlobStorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
|
||||||
|
sp := azureblob.New(&azureblob.Config{
|
||||||
|
AccessId: clientId,
|
||||||
|
AccessKey: clientSecret,
|
||||||
|
Region: region,
|
||||||
|
Bucket: bucket,
|
||||||
|
Endpoint: endpoint,
|
||||||
|
})
|
||||||
|
return sp
|
||||||
|
}
|
@ -20,7 +20,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qor/oss"
|
"github.com/casdoor/oss"
|
||||||
)
|
)
|
||||||
|
|
||||||
var baseFolder = "files"
|
var baseFolder = "files"
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
package storage
|
package storage
|
||||||
|
|
||||||
import "github.com/qor/oss"
|
import "github.com/casdoor/oss"
|
||||||
|
|
||||||
func GetStorageProvider(providerType string, clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
|
func GetStorageProvider(providerType string, clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
|
||||||
switch providerType {
|
switch providerType {
|
||||||
@ -26,6 +26,8 @@ func GetStorageProvider(providerType string, clientId string, clientSecret strin
|
|||||||
return NewAliyunOssStorageProvider(clientId, clientSecret, region, bucket, endpoint)
|
return NewAliyunOssStorageProvider(clientId, clientSecret, region, bucket, endpoint)
|
||||||
case "Tencent Cloud COS":
|
case "Tencent Cloud COS":
|
||||||
return NewTencentCloudCosStorageProvider(clientId, clientSecret, region, bucket, endpoint)
|
return NewTencentCloudCosStorageProvider(clientId, clientSecret, region, bucket, endpoint)
|
||||||
|
case "Azure Blob":
|
||||||
|
return NewAzureBlobStorageProvider(clientId, clientSecret, region, bucket, endpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -15,8 +15,8 @@
|
|||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/qor/oss"
|
"github.com/casdoor/oss"
|
||||||
"github.com/qor/oss/tencent"
|
"github.com/casdoor/oss/tencent"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewTencentCloudCosStorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
|
func NewTencentCloudCosStorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -12,11 +12,22 @@ paths:
|
|||||||
tags:
|
tags:
|
||||||
- OIDC API
|
- OIDC API
|
||||||
operationId: RootController.GetJwks
|
operationId: RootController.GetJwks
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: ""
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/jose.JSONWebKey'
|
||||||
/.well-known/openid-configuration:
|
/.well-known/openid-configuration:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- OIDC API
|
- OIDC API
|
||||||
|
description: Get Oidc Discovery
|
||||||
operationId: RootController.GetOidcDiscovery
|
operationId: RootController.GetOidcDiscovery
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: ""
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/object.OidcDiscovery'
|
||||||
/api/add-application:
|
/api/add-application:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -58,6 +69,24 @@ paths:
|
|||||||
tags:
|
tags:
|
||||||
- Account API
|
- Account API
|
||||||
operationId: ApiController.AddLdap
|
operationId: ApiController.AddLdap
|
||||||
|
/api/add-model:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Model API
|
||||||
|
description: add model
|
||||||
|
operationId: ApiController.AddModel
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
description: The details of the model
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/object.Model'
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controllers.Response'
|
||||||
/api/add-organization:
|
/api/add-organization:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -243,11 +272,11 @@ paths:
|
|||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/controllers.Response'
|
$ref: '#/definitions/controllers.Response'
|
||||||
/api/api/get-human-check:
|
/api/api/get-captcha:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Login API
|
- Login API
|
||||||
operationId: ApiController.GetHumancheck
|
operationId: ApiController.GetCaptcha
|
||||||
/api/api/reset-email-or-phone:
|
/api/api/reset-email-or-phone:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -271,11 +300,11 @@ paths:
|
|||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
- in: body
|
- in: body
|
||||||
name: body
|
name: from
|
||||||
description: Details of the email request
|
description: Details of the email request
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/emailForm'
|
$ref: '#/definitions/controllers.EmailForm'
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: object
|
description: object
|
||||||
@ -299,11 +328,11 @@ paths:
|
|||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
- in: body
|
- in: body
|
||||||
name: body
|
name: from
|
||||||
description: Details of the sms request
|
description: Details of the sms request
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/smsForm'
|
$ref: '#/definitions/controllers.SmsForm'
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: object
|
description: object
|
||||||
@ -382,6 +411,24 @@ paths:
|
|||||||
tags:
|
tags:
|
||||||
- Account API
|
- Account API
|
||||||
operationId: ApiController.DeleteLdap
|
operationId: ApiController.DeleteLdap
|
||||||
|
/api/delete-model:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Model API
|
||||||
|
description: delete model
|
||||||
|
operationId: ApiController.DeleteModel
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
description: The details of the model
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/object.Model'
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controllers.Response'
|
||||||
/api/delete-organization:
|
/api/delete-organization:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -578,6 +625,43 @@ paths:
|
|||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/controllers.Response'
|
$ref: '#/definitions/controllers.Response'
|
||||||
|
/api/get-app-login:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Login API
|
||||||
|
description: get application login
|
||||||
|
operationId: ApiController.GetApplicationLogin
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: clientId
|
||||||
|
description: client id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: responseType
|
||||||
|
description: response type
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: redirectUri
|
||||||
|
description: redirect uri
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: scope
|
||||||
|
description: scope
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: state
|
||||||
|
description: state
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Response'
|
||||||
/api/get-application:
|
/api/get-application:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@ -700,6 +784,42 @@ paths:
|
|||||||
tags:
|
tags:
|
||||||
- Account API
|
- Account API
|
||||||
operationId: ApiController.GetLdaps
|
operationId: ApiController.GetLdaps
|
||||||
|
/api/get-model:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Model API
|
||||||
|
description: get model
|
||||||
|
operationId: ApiController.GetModel
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: id
|
||||||
|
description: The id of the model
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/object.Model'
|
||||||
|
/api/get-models:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Model API
|
||||||
|
description: get models
|
||||||
|
operationId: ApiController.GetModels
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: owner
|
||||||
|
description: The owner of models
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/object.Model'
|
||||||
/api/get-organization:
|
/api/get-organization:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@ -901,9 +1021,7 @@ paths:
|
|||||||
"200":
|
"200":
|
||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
type: array
|
$ref: '#/definitions/object.Record'
|
||||||
items:
|
|
||||||
$ref: '#/definitions/object.Records'
|
|
||||||
/api/get-records-filter:
|
/api/get-records-filter:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -912,18 +1030,17 @@ paths:
|
|||||||
operationId: ApiController.GetRecordsByFilter
|
operationId: ApiController.GetRecordsByFilter
|
||||||
parameters:
|
parameters:
|
||||||
- in: body
|
- in: body
|
||||||
name: body
|
name: filter
|
||||||
description: filter Record message
|
description: filter Record message
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/object.Records'
|
type: string
|
||||||
|
type: string
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
type: array
|
$ref: '#/definitions/object.Record'
|
||||||
items:
|
|
||||||
$ref: '#/definitions/object.Records'
|
|
||||||
/api/get-resource:
|
/api/get-resource:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@ -1216,6 +1333,23 @@ paths:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/object.Webhook'
|
$ref: '#/definitions/object.Webhook'
|
||||||
|
/api/invoice-payment:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Payment API
|
||||||
|
description: invoice payment
|
||||||
|
operationId: ApiController.InvoicePayment
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: id
|
||||||
|
description: The id of the payment
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controllers.Response'
|
||||||
/api/login:
|
/api/login:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -1224,21 +1358,51 @@ paths:
|
|||||||
operationId: ApiController.Login
|
operationId: ApiController.Login
|
||||||
parameters:
|
parameters:
|
||||||
- in: query
|
- in: query
|
||||||
name: oAuthParams
|
name: clientId
|
||||||
description: oAuth parameters
|
description: clientId
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: responseType
|
||||||
|
description: responseType
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: redirectUri
|
||||||
|
description: redirectUri
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: scope
|
||||||
|
description: scope
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: state
|
||||||
|
description: state
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: nonce
|
||||||
|
description: nonce
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: code_challenge_method
|
||||||
|
description: code_challenge_method
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: code_challenge
|
||||||
|
description: code_challenge
|
||||||
|
type: string
|
||||||
- in: body
|
- in: body
|
||||||
name: body
|
name: form
|
||||||
description: Login information
|
description: Login information
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/RequestForm'
|
$ref: '#/definitions/controllers.RequestForm'
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/controllers.api_controller.Response'
|
$ref: '#/definitions/Response'
|
||||||
/api/login/oauth/access_token:
|
/api/login/oauth/access_token:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -1424,6 +1588,24 @@ paths:
|
|||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/controllers.Response'
|
$ref: '#/definitions/controllers.Response'
|
||||||
|
/api/run-syncer:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Syncer API
|
||||||
|
description: run syncer
|
||||||
|
operationId: ApiController.RunSyncer
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
description: The details of the syncer
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/object.Syncer'
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controllers.Response'
|
||||||
/api/send-verification-code:
|
/api/send-verification-code:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -1493,42 +1675,6 @@ paths:
|
|||||||
tags:
|
tags:
|
||||||
- Login API
|
- Login API
|
||||||
/api/update-application:
|
/api/update-application:
|
||||||
get:
|
|
||||||
tags:
|
|
||||||
- Login API
|
|
||||||
description: get application login
|
|
||||||
operationId: ApiController.GetApplicationLogin
|
|
||||||
parameters:
|
|
||||||
- in: query
|
|
||||||
name: clientId
|
|
||||||
description: client id
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
- in: query
|
|
||||||
name: responseType
|
|
||||||
description: response type
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
- in: query
|
|
||||||
name: redirectUri
|
|
||||||
description: redirect uri
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
- in: query
|
|
||||||
name: scope
|
|
||||||
description: scope
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
- in: query
|
|
||||||
name: state
|
|
||||||
description: state
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: The Response object
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/controllers.api_controller.Response'
|
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- Application API
|
- Application API
|
||||||
@ -1579,6 +1725,29 @@ paths:
|
|||||||
tags:
|
tags:
|
||||||
- Account API
|
- Account API
|
||||||
operationId: ApiController.UpdateLdap
|
operationId: ApiController.UpdateLdap
|
||||||
|
/api/update-model:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Model API
|
||||||
|
description: update model
|
||||||
|
operationId: ApiController.UpdateModel
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: id
|
||||||
|
description: The id of the model
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
description: The details of the model
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/object.Model'
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controllers.Response'
|
||||||
/api/update-organization:
|
/api/update-organization:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -1830,27 +1999,97 @@ paths:
|
|||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/object.Userinfo'
|
$ref: '#/definitions/object.Userinfo'
|
||||||
|
/api/verify-captcha:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Verification API
|
||||||
|
operationId: ApiController.VerifyCaptcha
|
||||||
definitions:
|
definitions:
|
||||||
2026.0xc000380de0.false:
|
2200.0xc0003c4b70.false:
|
||||||
title: "false"
|
title: "false"
|
||||||
type: object
|
type: object
|
||||||
2060.0xc000380e10.false:
|
2235.0xc0003c4ba0.false:
|
||||||
title: "false"
|
title: "false"
|
||||||
type: object
|
type: object
|
||||||
RequestForm:
|
|
||||||
title: RequestForm
|
|
||||||
type: object
|
|
||||||
Response:
|
Response:
|
||||||
title: Response
|
title: Response
|
||||||
type: object
|
type: object
|
||||||
|
controllers.EmailForm:
|
||||||
|
title: EmailForm
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
content:
|
||||||
|
type: string
|
||||||
|
receivers:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
sender:
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
controllers.RequestForm:
|
||||||
|
title: RequestForm
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
affiliation:
|
||||||
|
type: string
|
||||||
|
application:
|
||||||
|
type: string
|
||||||
|
autoSignin:
|
||||||
|
type: boolean
|
||||||
|
code:
|
||||||
|
type: string
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
emailCode:
|
||||||
|
type: string
|
||||||
|
firstName:
|
||||||
|
type: string
|
||||||
|
idCard:
|
||||||
|
type: string
|
||||||
|
lastName:
|
||||||
|
type: string
|
||||||
|
method:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
organization:
|
||||||
|
type: string
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
phone:
|
||||||
|
type: string
|
||||||
|
phoneCode:
|
||||||
|
type: string
|
||||||
|
phonePrefix:
|
||||||
|
type: string
|
||||||
|
provider:
|
||||||
|
type: string
|
||||||
|
redirectUri:
|
||||||
|
type: string
|
||||||
|
region:
|
||||||
|
type: string
|
||||||
|
relayState:
|
||||||
|
type: string
|
||||||
|
samlRequest:
|
||||||
|
type: string
|
||||||
|
samlResponse:
|
||||||
|
type: string
|
||||||
|
state:
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
controllers.Response:
|
controllers.Response:
|
||||||
title: Response
|
title: Response
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
data:
|
data:
|
||||||
$ref: '#/definitions/2026.0xc000380de0.false'
|
$ref: '#/definitions/2200.0xc0003c4b70.false'
|
||||||
data2:
|
data2:
|
||||||
$ref: '#/definitions/2060.0xc000380e10.false'
|
$ref: '#/definitions/2235.0xc0003c4ba0.false'
|
||||||
msg:
|
msg:
|
||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
@ -1859,25 +2098,33 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
sub:
|
sub:
|
||||||
type: string
|
type: string
|
||||||
controllers.api_controller.Response:
|
controllers.SmsForm:
|
||||||
title: Response
|
title: SmsForm
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
data:
|
content:
|
||||||
$ref: '#/definitions/2026.0xc000380de0.false'
|
type: string
|
||||||
data2:
|
organizationId:
|
||||||
$ref: '#/definitions/2060.0xc000380e10.false'
|
type: string
|
||||||
msg:
|
receivers:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
jose.JSONWebKey:
|
||||||
|
title: JSONWebKey
|
||||||
|
type: object
|
||||||
|
object.AccountItem:
|
||||||
|
title: AccountItem
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
modifyRule:
|
||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
status:
|
viewRule:
|
||||||
type: string
|
type: string
|
||||||
sub:
|
visible:
|
||||||
type: string
|
type: boolean
|
||||||
emailForm:
|
|
||||||
title: emailForm
|
|
||||||
type: object
|
|
||||||
object.Adapter:
|
object.Adapter:
|
||||||
title: Adapter
|
title: Adapter
|
||||||
type: object
|
type: object
|
||||||
@ -2037,10 +2284,80 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
username:
|
username:
|
||||||
type: string
|
type: string
|
||||||
|
object.Model:
|
||||||
|
title: Model
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
createdTime:
|
||||||
|
type: string
|
||||||
|
displayName:
|
||||||
|
type: string
|
||||||
|
isEnabled:
|
||||||
|
type: boolean
|
||||||
|
modelText:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
owner:
|
||||||
|
type: string
|
||||||
|
object.OidcDiscovery:
|
||||||
|
title: OidcDiscovery
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
authorization_endpoint:
|
||||||
|
type: string
|
||||||
|
claims_supported:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
grant_types_supported:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
id_token_signing_alg_values_supported:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
introspection_endpoint:
|
||||||
|
type: string
|
||||||
|
issuer:
|
||||||
|
type: string
|
||||||
|
jwks_uri:
|
||||||
|
type: string
|
||||||
|
request_object_signing_alg_values_supported:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
request_parameter_supported:
|
||||||
|
type: boolean
|
||||||
|
response_modes_supported:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
response_types_supported:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
scopes_supported:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
subject_types_supported:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
token_endpoint:
|
||||||
|
type: string
|
||||||
|
userinfo_endpoint:
|
||||||
|
type: string
|
||||||
object.Organization:
|
object.Organization:
|
||||||
title: Organization
|
title: Organization
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
accountItems:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/object.AccountItem'
|
||||||
createdTime:
|
createdTime:
|
||||||
type: string
|
type: string
|
||||||
defaultAvatar:
|
defaultAvatar:
|
||||||
@ -2051,6 +2368,8 @@ definitions:
|
|||||||
type: boolean
|
type: boolean
|
||||||
favicon:
|
favicon:
|
||||||
type: string
|
type: string
|
||||||
|
isProfilePublic:
|
||||||
|
type: boolean
|
||||||
masterPassword:
|
masterPassword:
|
||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
@ -2081,6 +2400,16 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
displayName:
|
displayName:
|
||||||
type: string
|
type: string
|
||||||
|
invoiceRemark:
|
||||||
|
type: string
|
||||||
|
invoiceTaxId:
|
||||||
|
type: string
|
||||||
|
invoiceTitle:
|
||||||
|
type: string
|
||||||
|
invoiceType:
|
||||||
|
type: string
|
||||||
|
invoiceUrl:
|
||||||
|
type: string
|
||||||
message:
|
message:
|
||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
@ -2091,6 +2420,14 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
payUrl:
|
payUrl:
|
||||||
type: string
|
type: string
|
||||||
|
personEmail:
|
||||||
|
type: string
|
||||||
|
personIdCard:
|
||||||
|
type: string
|
||||||
|
personName:
|
||||||
|
type: string
|
||||||
|
personPhone:
|
||||||
|
type: string
|
||||||
price:
|
price:
|
||||||
type: number
|
type: number
|
||||||
format: double
|
format: double
|
||||||
@ -2126,6 +2463,8 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
isEnabled:
|
isEnabled:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
model:
|
||||||
|
type: string
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
owner:
|
owner:
|
||||||
@ -2205,6 +2544,16 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
createdTime:
|
createdTime:
|
||||||
type: string
|
type: string
|
||||||
|
customAuthUrl:
|
||||||
|
type: string
|
||||||
|
customLogo:
|
||||||
|
type: string
|
||||||
|
customScope:
|
||||||
|
type: string
|
||||||
|
customTokenUrl:
|
||||||
|
type: string
|
||||||
|
customUserInfoUrl:
|
||||||
|
type: string
|
||||||
displayName:
|
displayName:
|
||||||
type: string
|
type: string
|
||||||
domain:
|
domain:
|
||||||
@ -2264,9 +2613,35 @@ definitions:
|
|||||||
type: boolean
|
type: boolean
|
||||||
provider:
|
provider:
|
||||||
$ref: '#/definitions/object.Provider'
|
$ref: '#/definitions/object.Provider'
|
||||||
object.Records:
|
object.Record:
|
||||||
title: Records
|
title: Record
|
||||||
type: object
|
type: object
|
||||||
|
properties:
|
||||||
|
action:
|
||||||
|
type: string
|
||||||
|
clientIp:
|
||||||
|
type: string
|
||||||
|
createdTime:
|
||||||
|
type: string
|
||||||
|
extendedUser:
|
||||||
|
$ref: '#/definitions/object.User'
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
isTriggered:
|
||||||
|
type: boolean
|
||||||
|
method:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
organization:
|
||||||
|
type: string
|
||||||
|
owner:
|
||||||
|
type: string
|
||||||
|
requestUri:
|
||||||
|
type: string
|
||||||
|
user:
|
||||||
|
type: string
|
||||||
object.Role:
|
object.Role:
|
||||||
title: Role
|
title: Role
|
||||||
type: object
|
type: object
|
||||||
@ -2407,6 +2782,8 @@ definitions:
|
|||||||
properties:
|
properties:
|
||||||
access_token:
|
access_token:
|
||||||
type: string
|
type: string
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
expires_in:
|
expires_in:
|
||||||
type: integer
|
type: integer
|
||||||
format: int64
|
format: int64
|
||||||
@ -2430,6 +2807,8 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
affiliation:
|
affiliation:
|
||||||
type: string
|
type: string
|
||||||
|
alipay:
|
||||||
|
type: string
|
||||||
apple:
|
apple:
|
||||||
type: string
|
type: string
|
||||||
avatar:
|
avatar:
|
||||||
@ -2438,6 +2817,8 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
baidu:
|
baidu:
|
||||||
type: string
|
type: string
|
||||||
|
bilibili:
|
||||||
|
type: string
|
||||||
bio:
|
bio:
|
||||||
type: string
|
type: string
|
||||||
birthday:
|
birthday:
|
||||||
@ -2448,14 +2829,20 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
createdTime:
|
createdTime:
|
||||||
type: string
|
type: string
|
||||||
|
custom:
|
||||||
|
type: string
|
||||||
dingtalk:
|
dingtalk:
|
||||||
type: string
|
type: string
|
||||||
displayName:
|
displayName:
|
||||||
type: string
|
type: string
|
||||||
|
douyin:
|
||||||
|
type: string
|
||||||
education:
|
education:
|
||||||
type: string
|
type: string
|
||||||
email:
|
email:
|
||||||
type: string
|
type: string
|
||||||
|
emailVerified:
|
||||||
|
type: boolean
|
||||||
facebook:
|
facebook:
|
||||||
type: string
|
type: string
|
||||||
firstName:
|
firstName:
|
||||||
@ -2515,6 +2902,8 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
|
okta:
|
||||||
|
type: string
|
||||||
owner:
|
owner:
|
||||||
type: string
|
type: string
|
||||||
password:
|
password:
|
||||||
@ -2552,6 +2941,8 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
type:
|
type:
|
||||||
type: string
|
type: string
|
||||||
|
unionId:
|
||||||
|
type: string
|
||||||
updatedTime:
|
updatedTime:
|
||||||
type: string
|
type: string
|
||||||
wechat:
|
wechat:
|
||||||
@ -2612,9 +3003,6 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
url:
|
url:
|
||||||
type: string
|
type: string
|
||||||
smsForm:
|
|
||||||
title: smsForm
|
|
||||||
type: object
|
|
||||||
xorm.Engine:
|
xorm.Engine:
|
||||||
title: Engine
|
title: Engine
|
||||||
type: object
|
type: object
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@ -12,12 +12,19 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package object
|
package util
|
||||||
|
|
||||||
type SignupItem struct {
|
import (
|
||||||
Name string `json:"name"`
|
"crypto/hmac"
|
||||||
Visible bool `json:"visible"`
|
"crypto/sha1"
|
||||||
Required bool `json:"required"`
|
"encoding/base64"
|
||||||
Prompted bool `json:"prompted"`
|
)
|
||||||
Rule string `json:"rule"`
|
|
||||||
|
func GetHmacSha1(keyStr, value string) string {
|
||||||
|
key := []byte(keyStr)
|
||||||
|
mac := hmac.New(sha1.New, key)
|
||||||
|
mac.Write([]byte(value))
|
||||||
|
res := base64.StdEncoding.EncodeToString(mac.Sum(nil))
|
||||||
|
|
||||||
|
return res
|
||||||
}
|
}
|
@ -52,6 +52,10 @@ func ParseFloat(s string) float64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ParseBool(s string) bool {
|
func ParseBool(s string) bool {
|
||||||
|
if s == "\x01" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
i := ParseInt(s)
|
i := ParseInt(s)
|
||||||
return i != 0
|
return i != 0
|
||||||
}
|
}
|
||||||
|
38
util/util.go
Normal file
38
util/util.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// 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 util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/logs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SafeGoroutine(fn func()) {
|
||||||
|
var err error
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
var ok bool
|
||||||
|
err, ok = r.(error)
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("%v", r)
|
||||||
|
}
|
||||||
|
logs.Error("goroutine panic: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fn()
|
||||||
|
}()
|
||||||
|
}
|
@ -4,7 +4,7 @@ preserve_hierarchy: true
|
|||||||
files: [
|
files: [
|
||||||
# JSON translation files
|
# JSON translation files
|
||||||
{
|
{
|
||||||
source: '/web/src/locales/en/data.json',
|
source: '/src/locales/en/data.json',
|
||||||
translation: '/web/src/locales/%two_letters_code%/data.json',
|
translation: '/src/locales/%two_letters_code%/data.json',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@ -1,13 +1,23 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
|
<script>
|
||||||
|
var _hmt = _hmt || [];
|
||||||
|
(function() {
|
||||||
|
var hm = document.createElement("script");
|
||||||
|
hm.src = "https://hm.baidu.com/hm.js?5998fcd123c220efc0936edf4f250504";
|
||||||
|
var s = document.getElementsByTagName("script")[0];
|
||||||
|
s.parentNode.insertBefore(hm, s);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<!-- <link rel="icon" href="%PUBLIC_URL%/favicon.png" />-->
|
<!-- <link rel="icon" href="%PUBLIC_URL%/favicon.png" />-->
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
content="Web site created using create-react-app"
|
content="Casdoor - An Identity and Access Management (IAM) / Single-Sign-On (SSO) platform with web UI supporting OAuth 2.0, OIDC, SAML and CAS"
|
||||||
/>
|
/>
|
||||||
<link rel="apple-touch-icon" href="https://cdn.casdoor.com/static/favicon.png" />
|
<link rel="apple-touch-icon" href="https://cdn.casdoor.com/static/favicon.png" />
|
||||||
<!--
|
<!--
|
||||||
|
242
web/src/AccountTable.js
Normal file
242
web/src/AccountTable.js
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import {DownOutlined, DeleteOutlined, UpOutlined} from '@ant-design/icons';
|
||||||
|
import {Button, Col, Row, Select, Switch, Table, Tooltip} from 'antd';
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import i18next from "i18next";
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
|
class AccountTable extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
classes: props,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTable(table) {
|
||||||
|
this.props.onUpdateTable(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateField(table, index, key, value) {
|
||||||
|
table[index][key] = value;
|
||||||
|
this.updateTable(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
addRow(table) {
|
||||||
|
let row = {name: Setting.getNewRowNameForTable(table, "Please select an account item"), visible: true};
|
||||||
|
if (table === undefined) {
|
||||||
|
table = [];
|
||||||
|
}
|
||||||
|
table = Setting.addRow(table, row);
|
||||||
|
this.updateTable(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteRow(table, i) {
|
||||||
|
table = Setting.deleteRow(table, i);
|
||||||
|
this.updateTable(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
upRow(table, i) {
|
||||||
|
table = Setting.swapRow(table, i - 1, i);
|
||||||
|
this.updateTable(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
downRow(table, i) {
|
||||||
|
table = Setting.swapRow(table, i, i + 1);
|
||||||
|
this.updateTable(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTable(table) {
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: i18next.t("provider:Name"),
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
render: (text, record, index) => {
|
||||||
|
const items = [
|
||||||
|
{name: "Organization", displayName: i18next.t("general:Organization")},
|
||||||
|
{name: "ID", displayName: i18next.t("general:ID")},
|
||||||
|
{name: "Name", displayName: i18next.t("general:Name")},
|
||||||
|
{name: "Display name", displayName: i18next.t("general:Display name")},
|
||||||
|
{name: "Avatar", displayName: i18next.t("general:Avatar")},
|
||||||
|
{name: "User type", displayName: i18next.t("general:User type")},
|
||||||
|
{name: "Password", displayName: i18next.t("general:Password")},
|
||||||
|
{name: "Email", displayName: i18next.t("general:Email")},
|
||||||
|
{name: "Phone", displayName: i18next.t("general:Phone")},
|
||||||
|
{name: "Country/Region", displayName: i18next.t("user:Country/Region")},
|
||||||
|
{name: "Location", displayName: i18next.t("user:Location")},
|
||||||
|
{name: "Affiliation", displayName: i18next.t("user:Affiliation")},
|
||||||
|
{name: "Title", displayName: i18next.t("user:Title")},
|
||||||
|
{name: "Homepage", displayName: i18next.t("user:Homepage")},
|
||||||
|
{name: "Bio", displayName: i18next.t("user:Bio")},
|
||||||
|
{name: "Tag", displayName: i18next.t("user:Tag")},
|
||||||
|
{name: "Signup application", displayName: i18next.t("general:Signup application")},
|
||||||
|
{name: "3rd-party logins", displayName: i18next.t("user:3rd-party logins")},
|
||||||
|
{name: "Properties", displayName: i18next.t("user:Properties")},
|
||||||
|
{name: "Is admin", displayName: i18next.t("user:Is admin")},
|
||||||
|
{name: "Is global admin", displayName: i18next.t("user:Is global admin")},
|
||||||
|
{name: "Is forbidden", displayName: i18next.t("user:Is forbidden")},
|
||||||
|
{name: "Is deleted", displayName: i18next.t("user:Is deleted")},
|
||||||
|
];
|
||||||
|
|
||||||
|
const getItemDisplayName = (text) => {
|
||||||
|
const item = items.filter(item => item.name === text);
|
||||||
|
if (item.length === 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return item[0].displayName;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select virtual={false} style={{width: '100%'}}
|
||||||
|
value={getItemDisplayName(text)}
|
||||||
|
onChange={value => {
|
||||||
|
this.updateField(table, index, 'name', value);
|
||||||
|
}} >
|
||||||
|
{
|
||||||
|
Setting.getDeduplicatedArray(items, table, "name").map((item, index) => <Option key={index} value={item.name}>{item.displayName}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("provider:visible"),
|
||||||
|
dataIndex: 'visible',
|
||||||
|
key: 'visible',
|
||||||
|
width: '120px',
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Switch checked={text} onChange={checked => {
|
||||||
|
this.updateField(table, index, 'visible', checked);
|
||||||
|
}} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("organization:viewRule"),
|
||||||
|
dataIndex: 'viewRule',
|
||||||
|
key: 'viewRule',
|
||||||
|
width: '155px',
|
||||||
|
render: (text, record, index) => {
|
||||||
|
if (!record.visible) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let options = [
|
||||||
|
{id: 'Public', name: 'Public'},
|
||||||
|
{id: 'Self', name: 'Self'},
|
||||||
|
{id: 'Admin', name: 'Admin'},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select virtual={false} style={{width: '100%'}} value={text} onChange={(value => {
|
||||||
|
this.updateField(table, index, 'viewRule', value);
|
||||||
|
})}>
|
||||||
|
{
|
||||||
|
options.map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("organization:modifyRule"),
|
||||||
|
dataIndex: 'modifyRule',
|
||||||
|
key: 'modifyRule',
|
||||||
|
width: '155px',
|
||||||
|
render: (text, record, index) => {
|
||||||
|
if (!record.visible) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let options;
|
||||||
|
if (record.viewRule === "Admin") {
|
||||||
|
options = [
|
||||||
|
{id: 'Admin', name: 'Admin'},
|
||||||
|
{id: 'Immutable', name: 'Immutable'},
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
options = [
|
||||||
|
{id: 'Self', name: 'Self'},
|
||||||
|
{id: 'Admin', name: 'Admin'},
|
||||||
|
{id: 'Immutable', name: 'Immutable'},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select virtual={false} style={{width: '100%'}} value={text} onChange={(value => {
|
||||||
|
this.updateField(table, index, 'modifyRule', value);
|
||||||
|
})}>
|
||||||
|
{
|
||||||
|
options.map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Action"),
|
||||||
|
key: 'action',
|
||||||
|
width: '100px',
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Tooltip placement="bottomLeft" title={i18next.t("general:Up")}>
|
||||||
|
<Button style={{marginRight: "5px"}} disabled={index === 0} icon={<UpOutlined />} size="small" onClick={() => this.upRow(table, index)} />
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip placement="topLeft" title={i18next.t("general:Down")}>
|
||||||
|
<Button style={{marginRight: "5px"}} disabled={index === table.length - 1} icon={<DownOutlined />} size="small" onClick={() => this.downRow(table, index)} />
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip placement="topLeft" title={i18next.t("general:Delete")}>
|
||||||
|
<Button icon={<DeleteOutlined />} size="small" onClick={() => this.deleteRow(table, index)} />
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table scroll={{x: 'max-content'}} rowKey="name" columns={columns} dataSource={table} size="middle" bordered pagination={false}
|
||||||
|
title={() => (
|
||||||
|
<div>
|
||||||
|
{this.props.title}
|
||||||
|
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col span={24}>
|
||||||
|
{
|
||||||
|
this.renderTable(this.props.table)
|
||||||
|
}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AccountTable;
|
@ -69,6 +69,8 @@ import PromptPage from "./auth/PromptPage";
|
|||||||
import OdicDiscoveryPage from "./auth/OidcDiscoveryPage";
|
import OdicDiscoveryPage from "./auth/OidcDiscoveryPage";
|
||||||
import SamlCallback from './auth/SamlCallback';
|
import SamlCallback from './auth/SamlCallback';
|
||||||
import CasLogout from "./auth/CasLogout";
|
import CasLogout from "./auth/CasLogout";
|
||||||
|
import ModelListPage from "./ModelListPage";
|
||||||
|
import ModelEditPage from "./ModelEditPage";
|
||||||
|
|
||||||
const { Header, Footer } = Layout;
|
const { Header, Footer } = Layout;
|
||||||
|
|
||||||
@ -118,6 +120,8 @@ class App extends Component {
|
|||||||
this.setState({ selectedMenuKey: '/roles' });
|
this.setState({ selectedMenuKey: '/roles' });
|
||||||
} else if (uri.includes('/permissions')) {
|
} else if (uri.includes('/permissions')) {
|
||||||
this.setState({ selectedMenuKey: '/permissions' });
|
this.setState({ selectedMenuKey: '/permissions' });
|
||||||
|
} else if (uri.includes('/models')) {
|
||||||
|
this.setState({ selectedMenuKey: '/models' });
|
||||||
} else if (uri.includes('/providers')) {
|
} else if (uri.includes('/providers')) {
|
||||||
this.setState({ selectedMenuKey: '/providers' });
|
this.setState({ selectedMenuKey: '/providers' });
|
||||||
} else if (uri.includes('/applications')) {
|
} else if (uri.includes('/applications')) {
|
||||||
@ -382,6 +386,13 @@ class App extends Component {
|
|||||||
</Link>
|
</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
);
|
);
|
||||||
|
res.push(
|
||||||
|
<Menu.Item key="/models">
|
||||||
|
<Link to="/models">
|
||||||
|
{i18next.t("general:Models")}
|
||||||
|
</Link>
|
||||||
|
</Menu.Item>
|
||||||
|
);
|
||||||
res.push(
|
res.push(
|
||||||
<Menu.Item key="/providers">
|
<Menu.Item key="/providers">
|
||||||
<Link to="/providers">
|
<Link to="/providers">
|
||||||
@ -514,6 +525,8 @@ class App extends Component {
|
|||||||
<Route exact path="/roles/:organizationName/:roleName" render={(props) => this.renderLoginIfNotLoggedIn(<RoleEditPage account={this.state.account} {...props} />)}/>
|
<Route exact path="/roles/:organizationName/:roleName" render={(props) => this.renderLoginIfNotLoggedIn(<RoleEditPage account={this.state.account} {...props} />)}/>
|
||||||
<Route exact path="/permissions" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionListPage account={this.state.account} {...props} />)}/>
|
<Route exact path="/permissions" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionListPage account={this.state.account} {...props} />)}/>
|
||||||
<Route exact path="/permissions/:organizationName/:permissionName" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionEditPage account={this.state.account} {...props} />)}/>
|
<Route exact path="/permissions/:organizationName/:permissionName" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionEditPage account={this.state.account} {...props} />)}/>
|
||||||
|
<Route exact path="/models" render={(props) => this.renderLoginIfNotLoggedIn(<ModelListPage account={this.state.account} {...props} />)}/>
|
||||||
|
<Route exact path="/models/:organizationName/:modelName" render={(props) => this.renderLoginIfNotLoggedIn(<ModelEditPage account={this.state.account} {...props} />)}/>
|
||||||
<Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)}/>
|
<Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)}/>
|
||||||
<Route exact path="/providers/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)}/>
|
<Route exact path="/providers/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)}/>
|
||||||
<Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)}/>
|
<Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)}/>
|
||||||
@ -656,6 +669,7 @@ class App extends Component {
|
|||||||
<Route exact path="/login" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage account={this.state.account} {...props} />)}/>
|
<Route exact path="/login" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage account={this.state.account} {...props} />)}/>
|
||||||
<Route exact path="/signup/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signup"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account)}} />}/>
|
<Route exact path="/signup/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signup"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account)}} />}/>
|
||||||
<Route exact path="/login/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account)}} />}/>
|
<Route exact path="/login/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account)}} />}/>
|
||||||
|
<Route exact path="/login/saml/authorize/:owner/:applicationName" render={(props) => <LoginPage account={this.state.account} type={"saml"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account)}} />}/>
|
||||||
<Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout clearAccount={() => this.setState({account: null})} {...props} />)} />
|
<Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout clearAccount={() => this.setState({account: null})} {...props} />)} />
|
||||||
<Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage type={"cas"} mode={"signup"} account={this.state.account} {...props} />)}} />
|
<Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage type={"cas"} mode={"signup"} account={this.state.account} {...props} />)}} />
|
||||||
<Route exact path="/callback" component={AuthCallback}/>
|
<Route exact path="/callback" component={AuthCallback}/>
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Button, Card, Col, Input, Popover, Row, Select, Switch, Upload} from 'antd';
|
import {Button, Card, Col, Input, Popover, Row, Select, Switch, Upload} from 'antd';
|
||||||
import {LinkOutlined, UploadOutlined} from "@ant-design/icons";
|
import {CopyOutlined, LinkOutlined, UploadOutlined} from "@ant-design/icons";
|
||||||
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
||||||
import * as CertBackend from "./backend/CertBackend";
|
import * as CertBackend from "./backend/CertBackend";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
@ -28,11 +28,13 @@ import UrlTable from "./UrlTable";
|
|||||||
import ProviderTable from "./ProviderTable";
|
import ProviderTable from "./ProviderTable";
|
||||||
import SignupTable from "./SignupTable";
|
import SignupTable from "./SignupTable";
|
||||||
import PromptPage from "./auth/PromptPage";
|
import PromptPage from "./auth/PromptPage";
|
||||||
|
import copy from "copy-to-clipboard";
|
||||||
|
|
||||||
import {Controlled as CodeMirror} from 'react-codemirror2';
|
import {Controlled as CodeMirror} from 'react-codemirror2';
|
||||||
import "codemirror/lib/codemirror.css";
|
import "codemirror/lib/codemirror.css";
|
||||||
require('codemirror/theme/material-darker.css');
|
require('codemirror/theme/material-darker.css');
|
||||||
require("codemirror/mode/htmlmixed/htmlmixed");
|
require("codemirror/mode/htmlmixed/htmlmixed");
|
||||||
|
require("codemirror/mode/xml/xml");
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
@ -48,6 +50,7 @@ class ApplicationEditPage extends React.Component {
|
|||||||
providers: [],
|
providers: [],
|
||||||
uploading: false,
|
uploading: false,
|
||||||
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||||
|
samlMetadata: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,12 +59,13 @@ class ApplicationEditPage extends React.Component {
|
|||||||
this.getOrganizations();
|
this.getOrganizations();
|
||||||
this.getCerts();
|
this.getCerts();
|
||||||
this.getProviders();
|
this.getProviders();
|
||||||
|
this.getSamlMetadata();
|
||||||
}
|
}
|
||||||
|
|
||||||
getApplication() {
|
getApplication() {
|
||||||
ApplicationBackend.getApplication("admin", this.state.applicationName)
|
ApplicationBackend.getApplication("admin", this.state.applicationName)
|
||||||
.then((application) => {
|
.then((application) => {
|
||||||
if (application.grantTypes === null || application.grantTypes.length === 0) {
|
if (application.grantTypes === null || application.grantTypes === undefined || application.grantTypes.length === 0) {
|
||||||
application.grantTypes = ["authorization_code"];
|
application.grantTypes = ["authorization_code"];
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -97,6 +101,15 @@ class ApplicationEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSamlMetadata() {
|
||||||
|
ApplicationBackend.getSamlMetadata("admin", this.state.applicationName)
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
samlMetadata: res,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
parseApplicationField(key, value) {
|
parseApplicationField(key, value) {
|
||||||
if (["expireInHours", "refreshExpireInHours"].includes(key)) {
|
if (["expireInHours", "refreshExpireInHours"].includes(key)) {
|
||||||
value = Setting.myParseInt(value);
|
value = Setting.myParseInt(value);
|
||||||
@ -390,7 +403,7 @@ class ApplicationEditPage extends React.Component {
|
|||||||
}}/>
|
}}/>
|
||||||
<Upload maxCount={1} accept=".html" showUploadList={false}
|
<Upload maxCount={1} accept=".html" showUploadList={false}
|
||||||
beforeUpload={file => {return false}} onChange={info => {this.handleUpload(info)}}>
|
beforeUpload={file => {return false}} onChange={info => {this.handleUpload(info)}}>
|
||||||
<Button icon={<UploadOutlined />} loading={this.state.uploading}>Click to Upload</Button>
|
<Button icon={<UploadOutlined />} loading={this.state.uploading}>{i18next.t("general:Click to Upload")}</Button>
|
||||||
</Upload>
|
</Upload>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@ -455,11 +468,42 @@ class ApplicationEditPage extends React.Component {
|
|||||||
{id: "client_credentials", name: "Client Credentials"},
|
{id: "client_credentials", name: "Client Credentials"},
|
||||||
{id: "token", name: "Token"},
|
{id: "token", name: "Token"},
|
||||||
{id: "id_token", name: "ID Token"},
|
{id: "id_token", name: "ID Token"},
|
||||||
|
{id: "refresh_token", name: "Refresh Token"},
|
||||||
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||||
}
|
}
|
||||||
</Select>
|
</Select>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("application:Enable SAML compress"), i18next.t("application:Enable SAML compress - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={1} >
|
||||||
|
<Switch checked={this.state.application.enableSamlCompress} onChange={checked => {
|
||||||
|
this.updateApplicationField('enableSamlCompress', checked);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("application:SAML metadata"), i18next.t("application:SAML metadata - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22}>
|
||||||
|
<CodeMirror
|
||||||
|
value={this.state.samlMetadata}
|
||||||
|
options={{mode: 'xml', theme: 'default'}}
|
||||||
|
onBeforeChange={(editor, data, value) => {}}
|
||||||
|
/>
|
||||||
|
<br/>
|
||||||
|
<Button style={{marginBottom: "10px"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
|
||||||
|
copy(`${window.location.origin}/api/saml/metadata?application=admin/${encodeURIComponent(this.state.applicationName)}`);
|
||||||
|
Setting.showMessage("success", i18next.t("application:SAML metadata URL copied to clipboard successfully"));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18next.t("application:Copy SAML metadata URL")}
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:Providers"), i18next.t("general:Providers - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Providers"), i18next.t("general:Providers - Tooltip"))} :
|
||||||
@ -479,7 +523,7 @@ class ApplicationEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
{
|
{
|
||||||
this.renderPreview()
|
this.renderSignupSigninPreview()
|
||||||
}
|
}
|
||||||
</Row>
|
</Row>
|
||||||
{
|
{
|
||||||
@ -503,29 +547,33 @@ class ApplicationEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
{
|
{
|
||||||
this.renderPreview2()
|
this.renderPromptPreview()
|
||||||
}
|
}
|
||||||
</Row>
|
</Row>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPreview() {
|
renderSignupSigninPreview() {
|
||||||
let signUpUrl = `/signup/${this.state.application.name}`;
|
let signUpUrl = `/signup/${this.state.application.name}`;
|
||||||
let signInUrl = `/login/oauth/authorize?client_id=${this.state.application.clientId}&response_type=code&redirect_uri=${this.state.application.redirectUris[0]}&scope=read&state=casdoor`;
|
let signInUrl = `/login/oauth/authorize?client_id=${this.state.application.clientId}&response_type=code&redirect_uri=${this.state.application.redirectUris[0]}&scope=read&state=casdoor`;
|
||||||
|
let maskStyle = {position: 'absolute', top: '0px', left: '0px', zIndex: 10, height: '100%', width: '100%', background: 'rgba(0,0,0,0.4)'};
|
||||||
if (!this.state.application.enablePassword) {
|
if (!this.state.application.enablePassword) {
|
||||||
signUpUrl = signInUrl.replace("/login/oauth/authorize", "/signup/oauth/authorize");
|
signUpUrl = signInUrl.replace("/login/oauth/authorize", "/signup/oauth/authorize");
|
||||||
}
|
}
|
||||||
if (!Setting.isMobile()) {
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Col span={11} style={{display:"flex", flexDirection: "column"}}>
|
<Col span={11}>
|
||||||
<a style={{marginBottom: "10px", display: "flex"}} target="_blank" rel="noreferrer" href={signUpUrl}>
|
<Button style={{marginBottom: "10px"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
|
||||||
<Button type="primary">{i18next.t("application:Test signup page..")}</Button>
|
copy(`${window.location.origin}${signUpUrl}`);
|
||||||
</a>
|
Setting.showMessage("success", i18next.t("application:Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser"));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18next.t("application:Copy signup page URL")}
|
||||||
|
</Button>
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<div style={{position: "relative", width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems:"center", overflow:"auto", flexDirection:"column", flex: "auto"}}>
|
||||||
<div style={{width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems:"center", overflow:"auto", flexDirection:"column", flex: "auto"}}>
|
|
||||||
{
|
{
|
||||||
this.state.application.enablePassword ? (
|
this.state.application.enablePassword ? (
|
||||||
<SignupPage application={this.state.application} />
|
<SignupPage application={this.state.application} />
|
||||||
@ -533,64 +581,45 @@ class ApplicationEditPage extends React.Component {
|
|||||||
<LoginPage type={"login"} mode={"signup"} application={this.state.application} />
|
<LoginPage type={"login"} mode={"signup"} application={this.state.application} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
<div style={maskStyle}></div>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={11} style={{display:"flex", flexDirection: "column"}}>
|
<Col span={11}>
|
||||||
<a style={{marginBottom: "10px", display: "flex"}} target="_blank" rel="noreferrer" href={signInUrl}>
|
<Button style={{marginBottom: "10px"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
|
||||||
<Button type="primary">{i18next.t("application:Test signin page..")}</Button>
|
copy(`${window.location.origin}${signInUrl}`);
|
||||||
</a>
|
Setting.showMessage("success", i18next.t("application:Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser"));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18next.t("application:Copy signin page URL")}
|
||||||
|
</Button>
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<div style={{position: "relative", width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems:"center", overflow:"auto", flexDirection:"column", flex: "auto"}}>
|
||||||
<div style={{width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems:"center", overflow:"auto", flexDirection:"column", flex: "auto"}}>
|
|
||||||
<LoginPage type={"login"} mode={"signin"} application={this.state.application} />
|
<LoginPage type={"login"} mode={"signin"} application={this.state.application} />
|
||||||
|
<div style={maskStyle}></div>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
} else{
|
|
||||||
return(
|
|
||||||
<React.Fragment>
|
|
||||||
<Col span={24} style={{display:"flex", flexDirection: "column"}}>
|
|
||||||
<a style={{marginBottom: "10px", display: "flex"}} target="_blank" rel="noreferrer" href={signUpUrl}>
|
|
||||||
<Button type="primary">{i18next.t("application:Test signup page..")}</Button>
|
|
||||||
</a>
|
|
||||||
<div style={{marginBottom:"10px", width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems: "center", overflow: "auto", flexDirection: "column", flex: "auto"}}>
|
|
||||||
{
|
|
||||||
this.state.application.enablePassword ? (
|
|
||||||
<SignupPage application={this.state.application} />
|
|
||||||
) : (
|
|
||||||
<LoginPage type={"login"} mode={"signup"} application={this.state.application} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<a style={{marginBottom: "10px", display: "flex"}} target="_blank" rel="noreferrer" href={signInUrl}>
|
|
||||||
<Button type="primary">{i18next.t("application:Test signin page..")}</Button>
|
|
||||||
</a>
|
|
||||||
<div style={{width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems: "center", overflow: "auto", flexDirection: "column", flex: "auto"}}>
|
|
||||||
<LoginPage type={"login"} mode={"signin"} application={this.state.application} />
|
|
||||||
</div>
|
|
||||||
</Col>
|
|
||||||
</React.Fragment>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPreview2() {
|
renderPromptPreview() {
|
||||||
let promptUrl = `/prompt/${this.state.application.name}`;
|
let promptUrl = `/prompt/${this.state.application.name}`;
|
||||||
|
let maskStyle = {position: 'absolute', top: '0px', left: '0px', zIndex: 10, height: '100%', width: '100%', background: 'rgba(0,0,0,0.4)'};
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<Col span={11}>
|
||||||
<Col span={(Setting.isMobile()) ? 24 : 11} style={{display:"flex", flexDirection: "column", flex: "auto"}} >
|
<Button style={{marginBottom: "10px"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
|
||||||
<a style={{marginBottom: "10px"}} target="_blank" rel="noreferrer" href={promptUrl}>
|
copy(`${window.location.origin}${promptUrl}`);
|
||||||
<Button type="primary">{i18next.t("application:Test prompt page..")}</Button>
|
Setting.showMessage("success", i18next.t("application:Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser"));
|
||||||
</a>
|
}}
|
||||||
<br style={(Setting.isMobile()) ? {display: "none"} : {}} />
|
>
|
||||||
<br style={(Setting.isMobile()) ? {display: "none"} : {}} />
|
{i18next.t("application:Copy prompt page URL")}
|
||||||
<div style={{width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", flexDirection: "column", flex: "auto"}}>
|
</Button>
|
||||||
|
<br/>
|
||||||
|
<div style={{position: "relative", width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", flexDirection: "column", flex: "auto"}}>
|
||||||
<PromptPage application={this.state.application} account={this.props.account} />
|
<PromptPage application={this.state.application} account={this.props.account} />
|
||||||
|
<div style={maskStyle}></div>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
</React.Fragment>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user