mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-21 06:03:50 +08:00
Compare commits
106 Commits
Author | SHA1 | Date | |
---|---|---|---|
208dc11d25 | |||
503d244166 | |||
475b6da35a | |||
b9404f14dc | |||
0baae87390 | |||
06759041a8 | |||
cf4e76f9dc | |||
81f2d01dc1 | |||
61773d3173 | |||
ec29621547 | |||
b8e324cadf | |||
f37fd6ba87 | |||
b4bf734fe8 | |||
f0431701c9 | |||
aa5078de15 | |||
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 |
1
.github/workflows/build.yml
vendored
1
.github/workflows/build.yml
vendored
@ -114,6 +114,7 @@ jobs:
|
||||
uses: docker/build-push-action@v2
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||
with:
|
||||
target: STANDARD
|
||||
push: true
|
||||
tags: casbin/casdoor:${{steps.get-current-tag.outputs.tag }},casbin/casdoor:latest
|
||||
|
||||
|
60
Dockerfile
60
Dockerfile
@ -1,8 +1,3 @@
|
||||
FROM golang:1.17.5 AS BACK
|
||||
WORKDIR /go/src/casdoor
|
||||
COPY . .
|
||||
RUN ./build.sh && apt update && apt install wait-for-it && chmod +x /usr/bin/wait-for-it
|
||||
|
||||
FROM node:16.13.0 AS FRONT
|
||||
WORKDIR /web
|
||||
COPY ./web .
|
||||
@ -10,28 +5,47 @@ RUN yarn config set registry https://registry.npmmirror.com
|
||||
RUN yarn install && yarn run build
|
||||
|
||||
|
||||
FROM debian:latest AS ALLINONE
|
||||
RUN apt update
|
||||
RUN apt install -y ca-certificates && update-ca-certificates
|
||||
RUN apt install -y mariadb-server mariadb-client && mkdir -p web/build && chmod 777 /tmp
|
||||
FROM golang:1.17.5 AS BACK
|
||||
WORKDIR /go/src/casdoor
|
||||
COPY . .
|
||||
RUN ./build.sh
|
||||
|
||||
|
||||
FROM alpine:latest AS STANDARD
|
||||
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
|
||||
RUN sed -i 's/https/http/' /etc/apk/repositories
|
||||
RUN apk add curl
|
||||
RUN apk add ca-certificates && update-ca-certificates
|
||||
|
||||
WORKDIR /
|
||||
COPY --from=BACK /go/src/casdoor/server ./server
|
||||
COPY --from=BACK /go/src/casdoor/swagger ./swagger
|
||||
COPY --from=BACK /go/src/casdoor/conf/app.conf ./conf/app.conf
|
||||
COPY --from=FRONT /web/build ./web/build
|
||||
ENTRYPOINT ["/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/"
|
||||
|
||||
COPY --from=BACK /go/src/casdoor/ ./
|
||||
COPY --from=BACK /usr/bin/wait-for-it ./
|
||||
RUN mkdir -p web/build && apk add --no-cache bash coreutils
|
||||
COPY --from=FRONT /web/build /web/build
|
||||
CMD ./server
|
||||
RUN apt update
|
||||
RUN apt install -y ca-certificates && update-ca-certificates
|
||||
|
||||
WORKDIR /
|
||||
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"]
|
||||
|
154
README.md
154
README.md
@ -42,166 +42,66 @@
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
## 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
|
||||
go get github.com/casdoor/casdoor
|
||||
```
|
||||
- International: https://casdoor.org
|
||||
- Asian mirror: https://docs.casdoor.cn
|
||||
|
||||
or `git`:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/casdoor/casdoor
|
||||
```
|
||||
|
||||
Finally, change directory:
|
||||
## Install
|
||||
|
||||
```bash
|
||||
cd casdoor/
|
||||
```
|
||||
- By source code: https://casdoor.org/docs/basic/server-installation
|
||||
- By Docker: https://casdoor.org/docs/basic/try-with-docker
|
||||
|
||||
We provide two start up methods for all kinds of users.
|
||||
|
||||
### Manual
|
||||
|
||||
#### Simple configuration
|
||||
Casdoor requires a running Relational database to be operational.Thus you need to modify configuration to point out the location of database.
|
||||
## How to connect to Casdoor?
|
||||
|
||||
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
|
||||
createDatabase := flag.Bool("createDatabase", false, "true if you need casdoor to create database")
|
||||
```
|
||||
- Docs: https://casdoor.org/docs/basic/public-api
|
||||
- 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
|
||||
go run main.go
|
||||
```
|
||||
- Gitter: https://gitter.im/casbin/casdoor
|
||||
- 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
|
||||
|
||||
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
|
||||
|
||||
[Apache-2.0](https://github.com/casdoor/casdoor/blob/master/LICENSE)
|
||||
|
||||
[Apache-2.0](https://github.com/casdoor/casdoor/blob/master/LICENSE)
|
||||
|
@ -80,21 +80,23 @@ p, *, *, GET, /api/get-app-login, *, *
|
||||
p, *, *, POST, /api/logout, *, *
|
||||
p, *, *, GET, /api/get-account, *, *
|
||||
p, *, *, GET, /api/userinfo, *, *
|
||||
p, *, *, POST, /api/login/oauth/access_token, *, *
|
||||
p, *, *, POST, /api/login/oauth/refresh_token, *, *
|
||||
p, *, *, GET, /api/login/oauth/logout, *, *
|
||||
p, *, *, *, /api/login/oauth, *, *
|
||||
p, *, *, GET, /api/get-application, *, *
|
||||
p, *, *, GET, /api/get-applications, *, *
|
||||
p, *, *, GET, /api/get-user, *, *
|
||||
p, *, *, GET, /api/get-user-application, *, *
|
||||
p, *, *, GET, /api/get-resources, *, *
|
||||
p, *, *, GET, /api/get-product, *, *
|
||||
p, *, *, POST, /api/buy-product, *, *
|
||||
p, *, *, GET, /api/get-payment, *, *
|
||||
p, *, *, POST, /api/update-payment, *, *
|
||||
p, *, *, POST, /api/invoice-payment, *, *
|
||||
p, *, *, GET, /api/get-providers, *, *
|
||||
p, *, *, POST, /api/unlink, *, *
|
||||
p, *, *, POST, /api/set-password, *, *
|
||||
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/upload-resource, *, *
|
||||
p, *, *, GET, /.well-known/openid-configuration, *, *
|
||||
|
8
build.sh
8
build.sh
@ -1,11 +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
|
||||
curl www.google.com -o /dev/null --connect-timeout 5 2 > /dev/null
|
||||
if [ $? == 0 ]
|
||||
then
|
||||
echo "connect to google.com successed"
|
||||
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 "connect to google.com failed"
|
||||
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
|
||||
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
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
appname = casdoor
|
||||
httpport = 8000
|
||||
runmode = dev
|
||||
SessionOn = true
|
||||
copyrequestbody = true
|
||||
driverName = mysql
|
||||
dataSourceName = root:123456@tcp(localhost:3306)/
|
||||
@ -12,7 +11,7 @@ redisEndpoint =
|
||||
defaultStorageProvider =
|
||||
isCloudIntranet = false
|
||||
authState = "casdoor"
|
||||
sock5Proxy = "127.0.0.1:10808"
|
||||
socks5Proxy = "127.0.0.1:10808"
|
||||
verificationCodeTimeout = 10
|
||||
initScore = 2000
|
||||
logPostOnly = true
|
||||
|
@ -17,6 +17,7 @@ package conf
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@ -61,7 +62,12 @@ func GetBeegoConfDataSourceName() string {
|
||||
|
||||
runningInDocker := os.Getenv("RUNNING_IN_DOCKER")
|
||||
if runningInDocker == "true" {
|
||||
dataSourceName = strings.ReplaceAll(dataSourceName, "localhost", "host.docker.internal")
|
||||
// https://stackoverflow.com/questions/48546124/what-is-linux-equivalent-of-host-docker-internal
|
||||
if runtime.GOOS == "linux" {
|
||||
dataSourceName = strings.ReplaceAll(dataSourceName, "localhost", "172.17.0.1")
|
||||
} else {
|
||||
dataSourceName = strings.ReplaceAll(dataSourceName, "localhost", "host.docker.internal")
|
||||
}
|
||||
}
|
||||
|
||||
return dataSourceName
|
||||
|
@ -79,13 +79,9 @@ func TestGetConfBool(t *testing.T) {
|
||||
input string
|
||||
expected interface{}
|
||||
}{
|
||||
{"Should be return false", "SessionOn", false},
|
||||
{"Should be return false", "copyrequestbody", true},
|
||||
}
|
||||
|
||||
//do some set up job
|
||||
os.Setenv("SessionOn", "false")
|
||||
|
||||
err := beego.LoadAppConfig("ini", "app.conf")
|
||||
assert.Nil(t, err)
|
||||
for _, scenery := range scenarios {
|
||||
|
@ -75,12 +75,17 @@ type Response struct {
|
||||
Data2 interface{} `json:"data2"`
|
||||
}
|
||||
|
||||
type HumanCheck struct {
|
||||
Type string `json:"type"`
|
||||
AppKey string `json:"appKey"`
|
||||
Scene string `json:"scene"`
|
||||
CaptchaId string `json:"captchaId"`
|
||||
CaptchaImage interface{} `json:"captchaImage"`
|
||||
type Captcha struct {
|
||||
Type string `json:"type"`
|
||||
AppKey string `json:"appKey"`
|
||||
Scene string `json:"scene"`
|
||||
CaptchaId string `json:"captchaId"`
|
||||
CaptchaImage []byte `json:"captchaImage"`
|
||||
ClientId string `json:"clientId"`
|
||||
ClientSecret string `json:"clientSecret"`
|
||||
ClientId2 string `json:"clientId2"`
|
||||
ClientSecret2 string `json:"clientSecret2"`
|
||||
SubType string `json:"subType"`
|
||||
}
|
||||
|
||||
// Signup
|
||||
@ -116,7 +121,7 @@ func (c *ApiController) Signup() {
|
||||
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)
|
||||
if len(checkResult) != 0 {
|
||||
c.ResponseError(fmt.Sprintf("Email: %s", checkResult))
|
||||
@ -210,7 +215,7 @@ func (c *ApiController) Signup() {
|
||||
record := object.NewRecord(c.Ctx)
|
||||
record.Organization = application.Organization
|
||||
record.User = user.Name
|
||||
go object.AddRecord(record)
|
||||
util.SafeGoroutine(func() { object.AddRecord(record) })
|
||||
|
||||
userId := fmt.Sprintf("%s/%s", user.Owner, user.Name)
|
||||
util.LogInfo(c.Ctx, "API: [%s] is signed up as new user", userId)
|
||||
@ -285,25 +290,43 @@ func (c *ApiController) GetUserinfo() {
|
||||
resp, err := object.GetUserInfo(userId, scope, aud, host)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetHumanCheck ...
|
||||
// GetCaptcha ...
|
||||
// @Tag Login API
|
||||
// @Title GetHumancheck
|
||||
// @router /api/get-human-check [get]
|
||||
func (c *ApiController) GetHumanCheck() {
|
||||
c.Data["json"] = HumanCheck{Type: "none"}
|
||||
// @Title GetCaptcha
|
||||
// @router /api/get-captcha [get]
|
||||
func (c *ApiController) GetCaptcha() {
|
||||
applicationId := c.Input().Get("applicationId")
|
||||
isCurrentProvider := c.Input().Get("isCurrentProvider")
|
||||
|
||||
provider := object.GetDefaultHumanCheckProvider()
|
||||
if provider == nil {
|
||||
id, img := object.GetCaptcha()
|
||||
c.Data["json"] = HumanCheck{Type: "captcha", CaptchaId: id, CaptchaImage: img}
|
||||
c.ServeJSON()
|
||||
captchaProvider, err := object.GetCaptchaProviderByApplication(applicationId, isCurrentProvider)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
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/proxy"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func codeToResponse(code *object.Code) *Response {
|
||||
@ -132,8 +133,8 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
||||
// @Param redirectUri query string true "redirect uri"
|
||||
// @Param scope query string true "scope"
|
||||
// @Param state query string true "state"
|
||||
// @Success 200 {object} controllers.api_controller.Response The Response object
|
||||
// @router /update-application [get]
|
||||
// @Success 200 {object} Response The Response object
|
||||
// @router /get-app-login [get]
|
||||
func (c *ApiController) GetApplicationLogin() {
|
||||
clientId := c.Input().Get("clientId")
|
||||
responseType := c.Input().Get("responseType")
|
||||
@ -162,9 +163,16 @@ func setHttpClient(idProvider idp.IdProvider, providerType string) {
|
||||
// @Title Login
|
||||
// @Tag Login API
|
||||
// @Description login
|
||||
// @Param oAuthParams query string true "oAuth parameters"
|
||||
// @Param body body RequestForm true "Login information"
|
||||
// @Success 200 {object} controllers.api_controller.Response The Response object
|
||||
// @Param clientId query string true clientId
|
||||
// @Param responseType query string true responseType
|
||||
// @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]
|
||||
func (c *ApiController) Login() {
|
||||
resp := &Response{}
|
||||
@ -222,7 +230,11 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
|
||||
// disable the verification code
|
||||
object.DisableVerificationCode(form.Username)
|
||||
if strings.Contains(form.Username, "@") {
|
||||
object.DisableVerificationCode(form.Username)
|
||||
} else {
|
||||
object.DisableVerificationCode(fmt.Sprintf("+%s%s", form.PhonePrefix, form.Username))
|
||||
}
|
||||
|
||||
user = object.GetUserByFields(form.Organization, form.Username)
|
||||
if user == nil {
|
||||
@ -248,7 +260,7 @@ func (c *ApiController) Login() {
|
||||
record := object.NewRecord(c.Ctx)
|
||||
record.Organization = application.Organization
|
||||
record.User = user.Name
|
||||
go object.AddRecord(record)
|
||||
util.SafeGoroutine(func() { object.AddRecord(record) })
|
||||
}
|
||||
} else if form.Provider != "" {
|
||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||
@ -321,12 +333,6 @@ func (c *ApiController) Login() {
|
||||
user = object.GetUser(fmt.Sprintf("%s/%s", application.Organization, userInfo.Id))
|
||||
} else if provider.Category == "OAuth" {
|
||||
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 {
|
||||
@ -341,7 +347,7 @@ func (c *ApiController) Login() {
|
||||
record := object.NewRecord(c.Ctx)
|
||||
record.Organization = application.Organization
|
||||
record.User = user.Name
|
||||
go object.AddRecord(record)
|
||||
util.SafeGoroutine(func() { object.AddRecord(record) })
|
||||
} else if provider.Category == "OAuth" {
|
||||
// Sign up via OAuth
|
||||
if !application.EnableSignUp {
|
||||
@ -354,6 +360,19 @@ func (c *ApiController) Login() {
|
||||
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["no"] = strconv.Itoa(len(object.GetUsers(application.Organization)) + 2)
|
||||
user = &object.User{
|
||||
@ -390,7 +409,13 @@ func (c *ApiController) Login() {
|
||||
record := object.NewRecord(c.Ctx)
|
||||
record.Organization = application.Organization
|
||||
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" {
|
||||
resp = &Response{Status: "error", Msg: "The account does not exist"}
|
||||
}
|
||||
@ -403,9 +428,6 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
|
||||
oldUser := object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
|
||||
if oldUser == nil {
|
||||
oldUser = object.GetUserByField(application.Organization, provider.Type, userInfo.Username)
|
||||
}
|
||||
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))
|
||||
return
|
||||
@ -434,6 +456,11 @@ func (c *ApiController) Login() {
|
||||
|
||||
user := c.getCurrentUser()
|
||||
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 {
|
||||
c.ResponseError(fmt.Sprintf("unknown authentication type (not password or provider), form = %s", util.StructToJson(form)))
|
||||
return
|
||||
|
@ -215,7 +215,7 @@ func (c *ApiController) SyncLdapUsers() {
|
||||
|
||||
object.UpdateLdapSyncTime(ldapId)
|
||||
|
||||
exist, failed := object.SyncLdapUsers(owner, users)
|
||||
exist, failed := object.SyncLdapUsers(owner, users, ldapId)
|
||||
c.Data["json"] = &Response{Status: "ok", Data: &LdapSyncResp{
|
||||
Exist: *exist,
|
||||
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
|
||||
// @Tag OIDC API
|
||||
// @Description Get Oidc Discovery
|
||||
// @Success 200 {object} object.OidcDiscovery
|
||||
// @router /.well-known/openid-configuration [get]
|
||||
func (c *RootController) GetOidcDiscovery() {
|
||||
host := c.Ctx.Request.Host
|
||||
@ -27,6 +29,7 @@ func (c *RootController) GetOidcDiscovery() {
|
||||
|
||||
// @Title GetJwks
|
||||
// @Tag OIDC API
|
||||
// @Success 200 {object} jose.JSONWebKey
|
||||
// @router /.well-known/jwks [get]
|
||||
func (c *RootController) GetJwks() {
|
||||
jwks, err := object.GetJsonWebKeySet()
|
||||
|
@ -158,3 +158,20 @@ func (c *ApiController) NotifyPayment() {
|
||||
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
|
||||
// @Param pageSize query string true "The size of each 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]
|
||||
func (c *ApiController) GetRecords() {
|
||||
limit := c.Input().Get("pageSize")
|
||||
@ -50,8 +50,8 @@ func (c *ApiController) GetRecords() {
|
||||
// @Tag Record API
|
||||
// @Title GetRecordsByFilter
|
||||
// @Description get records by filter
|
||||
// @Param body body object.Records true "filter Record message"
|
||||
// @Success 200 {array} object.Records The Response object
|
||||
// @Param filter body string true "filter Record message"
|
||||
// @Success 200 {object} object.Record The Response object
|
||||
// @router /get-records-filter [post]
|
||||
func (c *ApiController) GetRecordsByFilter() {
|
||||
body := string(c.Ctx.Input.RequestBody)
|
||||
|
@ -26,6 +26,7 @@ func (c *ApiController) GetSamlMeta() {
|
||||
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
|
||||
|
@ -25,33 +25,61 @@ import (
|
||||
"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
|
||||
// @Title SendEmail
|
||||
// @Tag Service API
|
||||
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
|
||||
// @Param clientId query string true "The clientId of the application"
|
||||
// @Param clientSecret query string true "The clientSecret of the application"
|
||||
// @Param 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
|
||||
// @router /api/send-email [post]
|
||||
func (c *ApiController) SendEmail() {
|
||||
provider, _, ok := c.GetProviderFromContext("Email")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var emailForm EmailForm
|
||||
|
||||
var emailForm struct {
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
Sender string `json:"sender"`
|
||||
Receivers []string `json:"receivers"`
|
||||
}
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &emailForm)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var provider *object.Provider
|
||||
if emailForm.Provider != "" {
|
||||
// called by frontend's TestEmailWidget, provider name is set by frontend
|
||||
provider = 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) {
|
||||
c.ResponseError(fmt.Sprintf("Empty parameters for emailForm: %v", emailForm))
|
||||
return
|
||||
@ -86,7 +114,7 @@ func (c *ApiController) SendEmail() {
|
||||
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
|
||||
// @Param clientId query string true "The clientId of the application"
|
||||
// @Param clientSecret query string true "The clientSecret of the application"
|
||||
// @Param 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
|
||||
// @router /api/send-sms [post]
|
||||
func (c *ApiController) SendSms() {
|
||||
@ -95,11 +123,7 @@ func (c *ApiController) SendSms() {
|
||||
return
|
||||
}
|
||||
|
||||
var smsForm struct {
|
||||
Content string `json:"content"`
|
||||
Receivers []string `json:"receivers"`
|
||||
OrgId string `json:"organizationId"` // e.g. "admin/built-in"
|
||||
}
|
||||
var smsForm SmsForm
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &smsForm)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
|
@ -165,6 +165,8 @@ func (c *ApiController) GetOAuthCode() {
|
||||
// @Param client_secret query string true "OAuth client secret"
|
||||
// @Param code query string true "OAuth code"
|
||||
// @Success 200 {object} object.TokenWrapper The Response object
|
||||
// @Success 400 {object} object.TokenError The Response object
|
||||
// @Success 401 {object} object.TokenError The Response object
|
||||
// @router /login/oauth/access_token [post]
|
||||
func (c *ApiController) GetOAuthToken() {
|
||||
grantType := c.Input().Get("grant_type")
|
||||
@ -200,6 +202,7 @@ func (c *ApiController) GetOAuthToken() {
|
||||
host := c.Ctx.Request.Host
|
||||
|
||||
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host, tag, avatar)
|
||||
c.SetTokenErrorHttpStatus()
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
@ -213,6 +216,8 @@ func (c *ApiController) GetOAuthToken() {
|
||||
// @Param client_id query string true "OAuth client id"
|
||||
// @Param client_secret query string false "OAuth client secret"
|
||||
// @Success 200 {object} object.TokenWrapper The Response object
|
||||
// @Success 400 {object} object.TokenError The Response object
|
||||
// @Success 401 {object} object.TokenError The Response object
|
||||
// @router /login/oauth/refresh_token [post]
|
||||
func (c *ApiController) RefreshToken() {
|
||||
grantType := c.Input().Get("grant_type")
|
||||
@ -230,11 +235,12 @@ func (c *ApiController) RefreshToken() {
|
||||
clientSecret = tokenRequest.ClientSecret
|
||||
grantType = tokenRequest.GrantType
|
||||
scope = tokenRequest.Scope
|
||||
|
||||
refreshToken = tokenRequest.RefreshToken
|
||||
}
|
||||
}
|
||||
|
||||
c.Data["json"] = object.RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host)
|
||||
c.SetTokenErrorHttpStatus()
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
@ -270,26 +276,36 @@ func (c *ApiController) TokenLogout() {
|
||||
// @Param token formData string true "access_token's value or refresh_token's value"
|
||||
// @Param token_type_hint formData string true "the token type access_token or refresh_token"
|
||||
// @Success 200 {object} object.IntrospectionResponse The Response object
|
||||
// @Success 400 {object} object.TokenError The Response object
|
||||
// @Success 401 {object} object.TokenError The Response object
|
||||
// @router /login/oauth/introspect [post]
|
||||
func (c *ApiController) IntrospectToken() {
|
||||
tokenValue := c.Input().Get("token")
|
||||
clientId, clientSecret, ok := c.Ctx.Request.BasicAuth()
|
||||
if !ok {
|
||||
util.LogWarning(c.Ctx, "Basic Authorization parses failed")
|
||||
c.Data["json"] = Response{Status: "error", Msg: "Unauthorized operation"}
|
||||
c.ServeJSON()
|
||||
return
|
||||
clientId = c.Input().Get("client_id")
|
||||
clientSecret = c.Input().Get("client_secret")
|
||||
if clientId == "" || clientSecret == "" {
|
||||
c.ResponseError("empty clientId or clientSecret")
|
||||
c.Data["json"] = &object.TokenError{
|
||||
Error: object.INVALID_REQUEST,
|
||||
}
|
||||
c.SetTokenErrorHttpStatus()
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
}
|
||||
application := object.GetApplicationByClientId(clientId)
|
||||
if application == nil || application.ClientSecret != clientSecret {
|
||||
util.LogWarning(c.Ctx, "Basic Authorization failed")
|
||||
c.Data["json"] = Response{Status: "error", Msg: "Unauthorized operation"}
|
||||
c.ServeJSON()
|
||||
c.ResponseError("invalid application or wrong clientSecret")
|
||||
c.Data["json"] = &object.TokenError{
|
||||
Error: object.INVALID_CLIENT,
|
||||
}
|
||||
c.SetTokenErrorHttpStatus()
|
||||
return
|
||||
}
|
||||
token := object.GetTokenByTokenAndApplication(tokenValue, application.Name)
|
||||
if token == nil {
|
||||
util.LogWarning(c.Ctx, "application: %s can not find token", application.Name)
|
||||
c.Data["json"] = &object.IntrospectionResponse{Active: false}
|
||||
c.ServeJSON()
|
||||
return
|
||||
@ -299,7 +315,6 @@ func (c *ApiController) IntrospectToken() {
|
||||
// and token revoked case. but we not implement
|
||||
// TODO: 2022-03-03 add token revoked check, when we implemented the Token Revocation(rfc7009) Specs.
|
||||
// refs: https://tools.ietf.org/html/rfc7009
|
||||
util.LogWarning(c.Ctx, "token invalid")
|
||||
c.Data["json"] = &object.IntrospectionResponse{Active: false}
|
||||
c.ServeJSON()
|
||||
return
|
||||
|
@ -25,4 +25,5 @@ type TokenRequest struct {
|
||||
Password string `json:"password"`
|
||||
Tag string `json:"tag"`
|
||||
Avatar string `json:"avatar"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
@ -51,6 +51,23 @@ func (c *ApiController) ResponseError(error string, data ...interface{}) {
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// SetTokenErrorHttpStatus ...
|
||||
func (c *ApiController) SetTokenErrorHttpStatus() {
|
||||
_, ok := c.Data["json"].(*object.TokenError)
|
||||
if ok {
|
||||
if c.Data["json"].(*object.TokenError).Error == object.INVALID_CLIENT {
|
||||
c.Ctx.Output.SetStatus(401)
|
||||
c.Ctx.Output.Header("WWW-Authenticate", "Basic realm=\"OAuth2\"")
|
||||
} else {
|
||||
c.Ctx.Output.SetStatus(400)
|
||||
}
|
||||
}
|
||||
_, ok = c.Data["json"].(*object.TokenWrapper)
|
||||
if ok {
|
||||
c.Ctx.Output.SetStatus(200)
|
||||
}
|
||||
}
|
||||
|
||||
// RequireSignedIn ...
|
||||
func (c *ApiController) RequireSignedIn() (string, bool) {
|
||||
userId := c.GetSessionUsername()
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/captcha"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
@ -41,32 +42,40 @@ func (c *ApiController) getCurrentUser() *object.User {
|
||||
func (c *ApiController) SendVerificationCode() {
|
||||
destType := c.Ctx.Request.Form.Get("type")
|
||||
dest := c.Ctx.Request.Form.Get("dest")
|
||||
orgId := c.Ctx.Request.Form.Get("organizationId")
|
||||
checkType := c.Ctx.Request.Form.Get("checkType")
|
||||
checkId := c.Ctx.Request.Form.Get("checkId")
|
||||
checkKey := c.Ctx.Request.Form.Get("checkKey")
|
||||
checkUser := c.Ctx.Request.Form.Get("checkUser")
|
||||
applicationId := c.Ctx.Request.Form.Get("applicationId")
|
||||
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 destType == "" || dest == "" || applicationId == "" || !strings.Contains(applicationId, "/") || checkType == "" {
|
||||
c.ResponseError("Missing parameter.")
|
||||
return
|
||||
}
|
||||
|
||||
isHuman := false
|
||||
captchaProvider := object.GetDefaultHumanCheckProvider()
|
||||
if captchaProvider == nil {
|
||||
isHuman = object.VerifyCaptcha(checkId, checkKey)
|
||||
}
|
||||
captchaProvider := captcha.GetCaptchaProvider(checkType)
|
||||
|
||||
if !isHuman {
|
||||
c.ResponseError("Turing test failed.")
|
||||
return
|
||||
if captchaProvider != nil {
|
||||
if checkKey == "" {
|
||||
c.ResponseError("Missing parameter: checkKey.")
|
||||
return
|
||||
}
|
||||
isHuman, err := captchaProvider.VerifyCaptcha(checkKey, checkId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if !isHuman {
|
||||
c.ResponseError("Turing test failed.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
user := c.getCurrentUser()
|
||||
organization := object.GetOrganization(orgId)
|
||||
application := object.GetApplicationByOrganizationName(organization.Name)
|
||||
application := object.GetApplication(applicationId)
|
||||
organization := object.GetOrganization(fmt.Sprintf("%s/%s", application.Owner, application.Organization))
|
||||
|
||||
if checkUser == "true" && user == nil && object.GetUserByFields(organization.Name, dest) == nil {
|
||||
c.ResponseError("Please login first")
|
||||
@ -76,7 +85,7 @@ func (c *ApiController) SendVerificationCode() {
|
||||
sendResp := errors.New("Invalid dest type")
|
||||
|
||||
if user == nil && checkUser != "" && checkUser != "true" {
|
||||
_, name := util.GetOwnerAndNameFromId(orgId)
|
||||
name := application.Organization
|
||||
user = object.GetUser(fmt.Sprintf("%s/%s", name, checkUser))
|
||||
}
|
||||
switch destType {
|
||||
@ -99,13 +108,12 @@ func (c *ApiController) SendVerificationCode() {
|
||||
c.ResponseError("Invalid phone number")
|
||||
return
|
||||
}
|
||||
org := object.GetOrganization(orgId)
|
||||
if org == nil {
|
||||
c.ResponseError("Missing parameter.")
|
||||
if organization == nil {
|
||||
c.ResponseError("The organization doesn't exist.")
|
||||
return
|
||||
}
|
||||
|
||||
dest = fmt.Sprintf("+%s%s", org.PhonePrefix, dest)
|
||||
dest = fmt.Sprintf("+%s%s", organization.PhonePrefix, dest)
|
||||
provider := application.GetSmsProvider()
|
||||
sendResp = object.SendVerificationCodeToPhone(organization, user, provider, remoteAddr, dest)
|
||||
}
|
||||
@ -173,3 +181,36 @@ func (c *ApiController) ResetEmailOrPhone() {
|
||||
c.Data["json"] = Response{Status: "ok"}
|
||||
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
|
||||
}
|
@ -30,6 +30,8 @@ func GetCredManager(passwordType string) CredManager {
|
||||
return NewBcryptCredManager()
|
||||
} else if passwordType == "pbkdf2-salt" {
|
||||
return NewPbkdf2SaltCredManager()
|
||||
} else if passwordType == "argon2id" {
|
||||
return NewArgon2idCredManager()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -38,8 +38,10 @@ func NewMd5UserSaltCredManager() *Md5UserSaltCredManager {
|
||||
}
|
||||
|
||||
func (cm *Md5UserSaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
||||
hash := getMd5HexDigest(password)
|
||||
res := getMd5HexDigest(hash + userSalt)
|
||||
res := getMd5HexDigest(password)
|
||||
if userSalt != "" {
|
||||
res = getMd5HexDigest(res + userSalt)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
|
@ -38,8 +38,10 @@ func NewSha256SaltCredManager() *Sha256SaltCredManager {
|
||||
}
|
||||
|
||||
func (cm *Sha256SaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
||||
hash := getSha256HexDigest(password)
|
||||
res := getSha256HexDigest(hash + organizationSalt)
|
||||
res := getSha256HexDigest(password)
|
||||
if organizationSalt != "" {
|
||||
res = getSha256HexDigest(res + organizationSalt)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
|
@ -25,3 +25,10 @@ func TestGetSaltedPassword(t *testing.T) {
|
||||
cm := NewSha256SaltCredManager()
|
||||
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:
|
||||
context: ./
|
||||
dockerfile: Dockerfile
|
||||
target: STANDARD
|
||||
entrypoint: /bin/sh -c './server --createDatabase=true'
|
||||
ports:
|
||||
- "8000:8000"
|
||||
depends_on:
|
||||
- db
|
||||
environment:
|
||||
RUNNING_IN_DOCKER: "true"
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
volumes:
|
||||
- ./conf:/conf/
|
||||
db:
|
||||
|
8
docker-entrypoint.sh
Normal file
8
docker-entrypoint.sh
Normal file
@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
if [ "${MYSQL_ROOT_PASSWORD}" = "" ] ;then MYSQL_ROOT_PASSWORD=123456 ;fi
|
||||
|
||||
service mariadb start
|
||||
|
||||
mysqladmin -u root password ${MYSQL_ROOT_PASSWORD}
|
||||
|
||||
exec /server --createDatabase=true
|
15
go.mod
15
go.mod
@ -4,15 +4,15 @@ go 1.16
|
||||
|
||||
require (
|
||||
github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible // indirect
|
||||
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387
|
||||
github.com/astaxie/beego v1.12.3
|
||||
github.com/aws/aws-sdk-go v1.37.30
|
||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
|
||||
github.com/aws/aws-sdk-go v1.44.4
|
||||
github.com/beevik/etree v1.1.0
|
||||
github.com/casbin/casbin/v2 v2.30.1
|
||||
github.com/casbin/xorm-adapter/v2 v2.5.1
|
||||
github.com/casdoor/go-sms-sender v0.2.0
|
||||
github.com/casdoor/goth v1.69.0-FIX1
|
||||
github.com/casdoor/goth v1.69.0-FIX2
|
||||
github.com/casdoor/oss v1.2.0
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
|
||||
github.com/go-ldap/ldap/v3 v3.3.0
|
||||
@ -20,12 +20,10 @@ require (
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/golang-jwt/jwt/v4 v4.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/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
||||
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/russellhaering/gosaml2 v0.6.0
|
||||
github.com/russellhaering/goxmldsig v1.1.1
|
||||
@ -35,14 +33,13 @@ require (
|
||||
github.com/tealeg/xlsx v1.0.5
|
||||
github.com/thanhpk/randstr v1.0.4
|
||||
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/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
|
||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0
|
||||
gopkg.in/yaml.v2 v2.3.0 // indirect
|
||||
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=
|
||||
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=
|
||||
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/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
@ -50,18 +65,20 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
|
||||
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-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/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/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.1.6+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.2+incompatible h1:9gWa46nstkJ9miBReJcN8Gq34cBFbzSpQZVVT9N09TM=
|
||||
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/astaxie/beego v1.12.3 h1:SAQkdD2ePye+v8Gn1r4X6IKZM1wd28EyUOVQ3PDSOOQ=
|
||||
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/aws/aws-sdk-go v1.37.30 h1:fZeVg3QuTkWE/dEvPQbK6AL32+3G9ofJfGFSPS1XLH0=
|
||||
github.com/aws/aws-sdk-go v1.37.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/aws/aws-sdk-go v1.44.4 h1:ePN0CVJMdiz2vYUcJH96eyxRrtKGSDMgyhP6rah2OgE=
|
||||
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/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
|
||||
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
|
||||
@ -83,8 +100,10 @@ github.com/casbin/xorm-adapter/v2 v2.5.1 h1:BkpIxRHKa0s3bSMx173PpuU7oTs+Zw7XmD0B
|
||||
github.com/casbin/xorm-adapter/v2 v2.5.1/go.mod h1:AeH4dBKHC9/zYxzdPVHhPDzF8LYLqjDdb767CWJoV54=
|
||||
github.com/casdoor/go-sms-sender v0.2.0 h1:52bin4EBOPzOee64s9UK7jxd22FODvT9/+Y/Z+PSHpg=
|
||||
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/go.mod h1:Om55nRo8CkeDkPSNBbzXW4G5uI28ZUkSk5S69dPek3s=
|
||||
github.com/casdoor/goth v1.69.0-FIX2 h1:RgfIMkL9kekylgxHHK2ZY8ASAwOGns2HVlaBwLu7Bcs=
|
||||
github.com/casdoor/goth v1.69.0-FIX2/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/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
@ -113,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.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
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/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=
|
||||
@ -130,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-pay/gopay v1.5.72 h1:3zm64xMBhJBa8rXbm//q5UiGgOa4WO5XYEnU394N2Zw=
|
||||
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-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
@ -252,18 +279,19 @@ 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/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
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/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.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
|
||||
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/ma314smith/signedxml v0.0.0-20210628192057-abc5b481ae1c h1:UPJygtyk491bJJ/DnRJFuzcq9Dl9NSeFrJ7VdiRzMxc=
|
||||
github.com/ma314smith/signedxml v0.0.0-20210628192057-abc5b481ae1c/go.mod h1:KEgVcb43+f5KFUH/x6Vd3NROG0AIL2CuKMrIqYsmx6E=
|
||||
github.com/markbates/going v1.0.0 h1:DQw0ZP7NbNlFGcKbcE/IVSOAFzScxRtLpd0rLMzLhq0=
|
||||
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/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.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
||||
@ -317,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/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/qor/oss v0.0.0-20191031055114-aef9ba66bf76 h1:J2Xj92efYLxPl3BiibgEDEUiMsCBzwTurE/8JjD8CG4=
|
||||
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76/go.mod h1:JhtPzUhP5KGtCB2yksmxuYAD4hEWw4qGQJpucjsm3U0=
|
||||
github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk=
|
||||
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/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
@ -387,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-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-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/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@ -435,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-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-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-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=
|
||||
@ -451,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-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-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
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-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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@ -470,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-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-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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -484,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-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-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-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -503,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-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-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-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-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-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.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.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.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
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-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-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w=
|
||||
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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
@ -696,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/core v0.7.2 h1:mEO22A2Z7a3fPaZMk6gKL/jMD80iiyNwRrX5HOv3XLw=
|
||||
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.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 {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
|
@ -82,6 +82,7 @@ func (idp *CustomIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
|
@ -143,6 +143,7 @@ func (idp *DingTalkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
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
|
||||
}
|
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
|
||||
}
|
@ -84,8 +84,14 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
|
||||
}
|
||||
} else if typ == "Casdoor" {
|
||||
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) {
|
||||
return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl)
|
||||
} else if typ == "Bilibili" {
|
||||
return NewBilibiliIdProvider(clientId, clientSecret, redirectUrl)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
160
init_data.json.template
Normal file
160
init_data.json.template
Normal file
@ -0,0 +1,160 @@
|
||||
{
|
||||
"organizations": [
|
||||
{
|
||||
"owner": "",
|
||||
"name": "",
|
||||
"displayName": "",
|
||||
"websiteUrl": "",
|
||||
"favicon": "",
|
||||
"passwordType": "",
|
||||
"phonePrefix": "",
|
||||
"defaultAvatar": "",
|
||||
"tags": [""]
|
||||
}
|
||||
],
|
||||
"applications": [
|
||||
{
|
||||
"owner": "",
|
||||
"name": "",
|
||||
"displayName": "",
|
||||
"logo": "",
|
||||
"homepageUrl": "",
|
||||
"organization": "",
|
||||
"cert": "",
|
||||
"enablePassword": true,
|
||||
"enableSignUp": true,
|
||||
"clientId": "",
|
||||
"clientSecret": "",
|
||||
"providers": [
|
||||
{
|
||||
"name": "",
|
||||
"canSignUp": true,
|
||||
"canSignIn": true,
|
||||
"canUnlink": false,
|
||||
"prompted": false,
|
||||
"alertType": "None"
|
||||
}
|
||||
],
|
||||
"signupItems": [
|
||||
{
|
||||
"name": "ID",
|
||||
"visible": false,
|
||||
"required": true,
|
||||
"prompted": false,
|
||||
"rule": "Random"
|
||||
},
|
||||
{
|
||||
"name": "Username",
|
||||
"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": "Confirm password",
|
||||
"visible": true,
|
||||
"required": true,
|
||||
"prompted": false,
|
||||
"rule": "None"
|
||||
},
|
||||
{
|
||||
"name": "Email",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"redirectUris": [""],
|
||||
"expireInHours": 168
|
||||
}
|
||||
],
|
||||
"users": [
|
||||
{
|
||||
"owner": "",
|
||||
"name": "",
|
||||
"type": "normal-user",
|
||||
"password": "",
|
||||
"displayName": "",
|
||||
"avatar": "",
|
||||
"email": "",
|
||||
"phone": "",
|
||||
"address": [],
|
||||
"affiliation": "",
|
||||
"tag": "",
|
||||
"score": 2000,
|
||||
"ranking": 1,
|
||||
"isAdmin": true,
|
||||
"isGlobalAdmin": true,
|
||||
"isForbidden": false,
|
||||
"isDeleted": false,
|
||||
"signupApplication": "",
|
||||
"createdIp": ""
|
||||
}
|
||||
],
|
||||
"providers": [
|
||||
{
|
||||
"owner": "",
|
||||
"name": "",
|
||||
"displayName": "",
|
||||
"category": "",
|
||||
"type": ""
|
||||
}
|
||||
],
|
||||
"certs": [
|
||||
{
|
||||
"owner": "",
|
||||
"name": "",
|
||||
"displayName": "",
|
||||
"scope": "JWT",
|
||||
"type": "x509",
|
||||
"cryptoAlgorithm": "RS256",
|
||||
"bitSize": 4096,
|
||||
"expireInYears": 20,
|
||||
"publicKey": "",
|
||||
"privateKey": ""
|
||||
}
|
||||
],
|
||||
"ldaps": [
|
||||
{
|
||||
"id": "",
|
||||
"owner": "",
|
||||
"serverName": "",
|
||||
"host": "",
|
||||
"port": 389,
|
||||
"admin": "",
|
||||
"passwd": "",
|
||||
"baseDn": "",
|
||||
"autoSync": 0,
|
||||
"lastSync": ""
|
||||
}
|
||||
]
|
||||
}
|
8
main.go
8
main.go
@ -27,20 +27,22 @@ import (
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
"github.com/casdoor/casdoor/routers"
|
||||
_ "github.com/casdoor/casdoor/routers"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
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()
|
||||
|
||||
object.InitAdapter(*createDatabase)
|
||||
object.InitDb()
|
||||
object.InitFromFile()
|
||||
object.InitDefaultStorageProvider()
|
||||
object.InitLdapAutoSynchronizer()
|
||||
proxy.InitHttpClient()
|
||||
authz.InitAuthz()
|
||||
|
||||
go object.RunSyncUsersJob()
|
||||
util.SafeGoroutine(func() {object.RunSyncUsersJob()})
|
||||
|
||||
//beego.DelStaticPath("/static")
|
||||
beego.SetStaticPath("/static", "web/build/static")
|
||||
@ -50,9 +52,11 @@ func main() {
|
||||
// https://studygolang.com/articles/2303
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.StaticFilter)
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.AutoSigninFilter)
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.CorsFilter)
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.AuthzFilter)
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
|
||||
|
||||
beego.BConfig.WebConfig.Session.SessionOn = true
|
||||
beego.BConfig.WebConfig.Session.SessionName = "casdoor_session_id"
|
||||
if conf.GetConfigString("redisEndpoint") == "" {
|
||||
beego.BConfig.WebConfig.Session.SessionProvider = "file"
|
||||
|
@ -16,7 +16,7 @@ data:
|
||||
defaultStorageProvider =
|
||||
isCloudIntranet = false
|
||||
authState = "casdoor"
|
||||
sock5Proxy = "127.0.0.1:10808"
|
||||
socks5Proxy = "127.0.0.1:10808"
|
||||
verificationCodeTimeout = 10
|
||||
initScore = 2000
|
||||
logPostOnly = true
|
||||
|
@ -22,7 +22,7 @@ import (
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
//_ "github.com/denisenkom/go-mssqldb" // db = mssql
|
||||
_ "github.com/go-sql-driver/mysql" // db = mysql
|
||||
_ "github.com/go-sql-driver/mysql" // db = mysql
|
||||
//_ "github.com/lib/pq" // db = postgres
|
||||
"xorm.io/core"
|
||||
"xorm.io/xorm"
|
||||
@ -36,11 +36,12 @@ func InitConfig() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
beego.BConfig.WebConfig.Session.SessionOn = true
|
||||
|
||||
InitAdapter(true)
|
||||
}
|
||||
|
||||
func InitAdapter(createDatabase bool) {
|
||||
|
||||
adapter = NewAdapter(conf.GetConfigString("driverName"), conf.GetBeegoConfDataSourceName(), conf.GetConfigString("dbName"))
|
||||
if createDatabase {
|
||||
adapter.CreateDatabase()
|
||||
@ -138,6 +139,11 @@ func (a *Adapter) createTable() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Model))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Provider))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -16,12 +16,21 @@ package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"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 {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
@ -37,6 +46,7 @@ type Application struct {
|
||||
EnableSignUp bool `json:"enableSignUp"`
|
||||
EnableSigninSession bool `json:"enableSigninSession"`
|
||||
EnableCodeSignin bool `json:"enableCodeSignin"`
|
||||
EnableSamlCompress bool `json:"enableSamlCompress"`
|
||||
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
|
||||
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
|
||||
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
|
||||
@ -257,7 +267,11 @@ func UpdateApplication(id string, application *Application) bool {
|
||||
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 {
|
||||
panic(err)
|
||||
}
|
||||
@ -266,8 +280,12 @@ func UpdateApplication(id string, application *Application) bool {
|
||||
}
|
||||
|
||||
func AddApplication(application *Application) bool {
|
||||
application.ClientId = util.GenerateClientId()
|
||||
application.ClientSecret = util.GenerateClientSecret()
|
||||
if application.ClientId == "" {
|
||||
application.ClientId = util.GenerateClientId()
|
||||
}
|
||||
if application.ClientSecret == "" {
|
||||
application.ClientSecret = util.GenerateClientSecret()
|
||||
}
|
||||
for _, providerItem := range application.Providers {
|
||||
providerItem.Provider = nil
|
||||
}
|
||||
@ -307,3 +325,39 @@ func CheckRedirectUriValid(application *Application, redirectUri string) bool {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
@ -51,6 +51,10 @@ func downloadFile(url string) (*bytes.Buffer, error) {
|
||||
}
|
||||
|
||||
func getPermanentAvatarUrl(organization string, username string, url string) string {
|
||||
if url == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
if defaultStorageProvider == nil {
|
||||
return ""
|
||||
}
|
||||
|
@ -29,3 +29,16 @@ func SendEmail(provider *Provider, title string, content string, dest string, se
|
||||
|
||||
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() {
|
||||
existed := initBuiltInOrganization()
|
||||
if !existed {
|
||||
initBuiltInProvider()
|
||||
initBuiltInUser()
|
||||
initBuiltInApplication()
|
||||
initBuiltInCert()
|
||||
@ -47,6 +48,31 @@ func initBuiltInOrganization() bool {
|
||||
PhonePrefix: "86",
|
||||
DefaultAvatar: "https://casbin.org/img/casbin.svg",
|
||||
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)
|
||||
return false
|
||||
@ -78,7 +104,7 @@ func initBuiltInUser() {
|
||||
IsGlobalAdmin: true,
|
||||
IsForbidden: false,
|
||||
IsDeleted: false,
|
||||
SignupApplication: "built-in-app",
|
||||
SignupApplication: "app-built-in",
|
||||
CreatedIp: "127.0.0.1",
|
||||
Properties: make(map[string]string),
|
||||
}
|
||||
@ -102,14 +128,16 @@ func initBuiltInApplication() {
|
||||
Cert: "cert-built-in",
|
||||
EnablePassword: 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{
|
||||
{Name: "ID", Visible: false, Required: true, Prompted: false, Rule: "Random"},
|
||||
{Name: "Username", 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: "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: "Agreement", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
||||
},
|
||||
@ -147,7 +175,7 @@ func initBuiltInCert() {
|
||||
DisplayName: "Built-in Cert",
|
||||
Scope: "JWT",
|
||||
Type: "x509",
|
||||
CryptoAlgorithm: "RSA",
|
||||
CryptoAlgorithm: "RS256",
|
||||
BitSize: 4096,
|
||||
ExpireInYears: 20,
|
||||
PublicKey: tokenJwtPublicKey,
|
||||
@ -176,3 +204,20 @@ func initBuiltInLdap() {
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
146
object/init_data.go
Normal file
146
object/init_data.go
Normal file
@ -0,0 +1,146 @@
|
||||
// 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 "github.com/casdoor/casdoor/util"
|
||||
|
||||
type InitData struct {
|
||||
Organizations []*Organization `json:"organizations"`
|
||||
Applications []*Application `json:"applications"`
|
||||
Users []*User `json:"users"`
|
||||
Certs []*Cert `json:"certs"`
|
||||
Providers []*Provider `json:"providers"`
|
||||
Ldaps []*Ldap `json:"ldaps"`
|
||||
}
|
||||
|
||||
func InitFromFile() {
|
||||
initData := readInitDataFromFile("./init_data.json")
|
||||
if initData != nil {
|
||||
for _, organization := range initData.Organizations {
|
||||
initDefinedOrganization(organization)
|
||||
}
|
||||
for _, provider := range initData.Providers {
|
||||
initDefinedProvider(provider)
|
||||
}
|
||||
for _, user := range initData.Users {
|
||||
initDefinedUser(user)
|
||||
}
|
||||
for _, application := range initData.Applications {
|
||||
initDefinedApplication(application)
|
||||
}
|
||||
for _, cert := range initData.Certs {
|
||||
initDefinedCert(cert)
|
||||
}
|
||||
for _, ldap := range initData.Ldaps {
|
||||
initDefinedLdap(ldap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func readInitDataFromFile(filePath string) *InitData {
|
||||
if !util.FileExist(filePath) {
|
||||
return nil
|
||||
}
|
||||
|
||||
s := util.ReadStringFromPath(filePath)
|
||||
|
||||
data := &InitData{}
|
||||
err := util.JsonToStruct(s, data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func initDefinedOrganization(organization *Organization) {
|
||||
existed := getOrganization(organization.Owner, organization.Name)
|
||||
if existed != nil {
|
||||
return
|
||||
}
|
||||
organization.CreatedTime = util.GetCurrentTime()
|
||||
organization.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)
|
||||
}
|
||||
|
||||
func initDefinedApplication(application *Application) {
|
||||
existed := getApplication(application.Owner, application.Name)
|
||||
if existed != nil {
|
||||
return
|
||||
}
|
||||
application.CreatedTime = util.GetCurrentTime()
|
||||
AddApplication(application)
|
||||
}
|
||||
|
||||
func initDefinedUser(user *User) {
|
||||
existed := getUser(user.Owner, user.Name)
|
||||
if existed != nil {
|
||||
return
|
||||
}
|
||||
user.CreatedTime = util.GetCurrentTime()
|
||||
user.Id = util.GenerateId()
|
||||
user.Properties = make(map[string]string)
|
||||
AddUser(user)
|
||||
}
|
||||
|
||||
func initDefinedCert(cert *Cert) {
|
||||
existed := getCert(cert.Owner, cert.Name)
|
||||
if existed != nil {
|
||||
return
|
||||
}
|
||||
cert.CreatedTime = util.GetCurrentTime()
|
||||
AddCert(cert)
|
||||
}
|
||||
|
||||
func initDefinedLdap(ldap *Ldap) {
|
||||
existed := GetLdap(ldap.Id)
|
||||
if existed != nil {
|
||||
return
|
||||
}
|
||||
AddLdap(ldap)
|
||||
}
|
||||
|
||||
func initDefinedProvider(provider *Provider) {
|
||||
existed := GetProvider(provider.GetId())
|
||||
if existed != nil {
|
||||
return
|
||||
}
|
||||
AddProvider(provider)
|
||||
}
|
105
object/ldap.go
105
object/ldap.go
@ -19,6 +19,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
goldap "github.com/go-ldap/ldap/v3"
|
||||
"github.com/thanhpk/randstr"
|
||||
@ -42,6 +43,7 @@ type Ldap struct {
|
||||
|
||||
type ldapConn struct {
|
||||
Conn *goldap.Conn
|
||||
IsAD bool
|
||||
}
|
||||
|
||||
//type ldapGroup struct {
|
||||
@ -78,6 +80,13 @@ type LdapRespUser struct {
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
type ldapServerType struct {
|
||||
Vendorname string
|
||||
Vendorversion string
|
||||
IsGlobalCatalogReady string
|
||||
ForestFunctionality string
|
||||
}
|
||||
|
||||
func LdapUsersToLdapRespUsers(users []ldapUser) []LdapRespUser {
|
||||
returnAnyNotEmpty := func(strs ...string) string {
|
||||
for _, str := range strs {
|
||||
@ -104,6 +113,45 @@ func LdapUsersToLdapRespUsers(users []ldapUser) []LdapRespUser {
|
||||
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) {
|
||||
conn, err := goldap.Dial("tcp", fmt.Sprintf("%s:%d", host, port))
|
||||
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 &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
|
||||
@ -158,10 +210,19 @@ func (l *ldapConn) GetLdapUsers(baseDn string) ([]ldapUser, error) {
|
||||
SearchFilter := "(objectClass=posixAccount)"
|
||||
SearchAttributes := []string{"uidNumber", "uid", "cn", "gidNumber", "entryUUID", "mail", "email",
|
||||
"emailAddress", "telephoneNumber", "mobile", "mobileTelephoneNumber", "registeredAddress", "postalAddress"}
|
||||
|
||||
searchReq := goldap.NewSearchRequest(baseDn,
|
||||
goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
|
||||
SearchFilter, SearchAttributes, nil)
|
||||
SearchFilterMsAD := "(objectClass=user)"
|
||||
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,
|
||||
SearchFilter, SearchAttributes, nil)
|
||||
}
|
||||
searchResult, err := l.Conn.SearchWithPaging(searchReq, 100)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -181,12 +242,16 @@ func (l *ldapConn) GetLdapUsers(baseDn string) ([]ldapUser, error) {
|
||||
ldapUserItem.UidNumber = attribute.Values[0]
|
||||
case "uid":
|
||||
ldapUserItem.Uid = attribute.Values[0]
|
||||
case "sAMAccountName":
|
||||
ldapUserItem.Uid = attribute.Values[0]
|
||||
case "cn":
|
||||
ldapUserItem.Cn = attribute.Values[0]
|
||||
case "gidNumber":
|
||||
ldapUserItem.GidNumber = attribute.Values[0]
|
||||
case "entryUUID":
|
||||
ldapUserItem.Uuid = attribute.Values[0]
|
||||
case "objectGUID":
|
||||
ldapUserItem.Uuid = attribute.Values[0]
|
||||
case "mail":
|
||||
ldapUserItem.Mail = attribute.Values[0]
|
||||
case "email":
|
||||
@ -300,7 +365,7 @@ func DeleteLdap(ldap *Ldap) bool {
|
||||
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 failedUsers []LdapRespUser
|
||||
var uuids []string
|
||||
@ -311,6 +376,25 @@ func SyncLdapUsers(owner string, users []LdapRespUser) (*[]LdapRespUser, *[]Ldap
|
||||
|
||||
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 {
|
||||
found := false
|
||||
if len(existUuids) > 0 {
|
||||
@ -325,15 +409,14 @@ func SyncLdapUsers(owner string, users []LdapRespUser) (*[]LdapRespUser, *[]Ldap
|
||||
Owner: owner,
|
||||
Name: buildLdapUserName(user.Uid, user.UidNumber),
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
Password: "123",
|
||||
DisplayName: user.Cn,
|
||||
Avatar: "https://casbin.org/img/casbin.svg",
|
||||
Avatar: organization.DefaultAvatar,
|
||||
Email: user.Email,
|
||||
Phone: user.Phone,
|
||||
Address: []string{user.Address},
|
||||
Affiliation: "Example Inc.",
|
||||
Tag: "staff",
|
||||
Score: 2000,
|
||||
Affiliation: affiliation,
|
||||
Tag: tag,
|
||||
Score: beego.AppConfig.DefaultInt("initScore", 2000),
|
||||
Ldap: user.Uuid,
|
||||
}) {
|
||||
failedUsers = append(failedUsers, user)
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
type LdapAutoSynchronizer struct {
|
||||
@ -47,7 +48,7 @@ func (l *LdapAutoSynchronizer) StartAutoSync(ldapId string) error {
|
||||
stopChan := make(chan struct{})
|
||||
l.ldapIdToStopChan[ldapId] = stopChan
|
||||
logs.Info(fmt.Sprintf("autoSync started for %s", ldap.Id))
|
||||
go l.syncRoutine(ldap, stopChan)
|
||||
util.SafeGoroutine(func() {l.syncRoutine(ldap, stopChan)})
|
||||
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))
|
||||
continue
|
||||
}
|
||||
existed, failed := SyncLdapUsers(ldap.Owner, LdapUsersToLdapRespUsers(users))
|
||||
existed, failed := SyncLdapUsers(ldap.Owner, LdapUsersToLdapRespUsers(users), ldap.Id)
|
||||
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)
|
||||
} 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),
|
||||
JwksUri: fmt.Sprintf("%s/.well-known/jwks", 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"},
|
||||
GrantTypesSupported: []string{"password", "authorization_code"},
|
||||
SubjectTypesSupported: []string{"public"},
|
||||
@ -105,6 +105,8 @@ func GetJsonWebKeySet() (jose.JSONWebKeySet, error) {
|
||||
jwk.Key = x509Cert.PublicKey
|
||||
jwk.Certificates = []*x509.Certificate{x509Cert}
|
||||
jwk.KeyID = cert.Name
|
||||
jwk.Algorithm = cert.CryptoAlgorithm
|
||||
jwk.Use = "sig"
|
||||
jwks.Keys = append(jwks.Keys, jwk)
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,13 @@ import (
|
||||
"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 {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
@ -36,6 +43,8 @@ type Organization struct {
|
||||
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
||||
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
||||
IsProfilePublic bool `json:"isProfilePublic"`
|
||||
|
||||
AccountItems []*AccountItem `xorm:"varchar(2000)" json:"accountItems"`
|
||||
}
|
||||
|
||||
func GetOrganizationCount(owner, field, value string) int {
|
||||
@ -121,14 +130,18 @@ func UpdateOrganization(id string, organization *Organization) bool {
|
||||
}
|
||||
|
||||
if name != organization.Name {
|
||||
applications := GetApplicationsByOrganizationName("admin", name)
|
||||
for _, application := range applications {
|
||||
go func() {
|
||||
application := new(Application)
|
||||
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)
|
||||
if credManager != nil {
|
||||
hashedPassword := credManager.GetHashedPassword(organization.MasterPassword, "", organization.PasswordSalt)
|
||||
@ -136,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 {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -44,6 +44,16 @@ type Payment struct {
|
||||
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
|
||||
State string `xorm:"varchar(100)" json:"state"`
|
||||
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 {
|
||||
@ -197,6 +207,44 @@ func NotifyPayment(request *http.Request, body []byte, owner string, providerNam
|
||||
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 {
|
||||
return fmt.Sprintf("%s/%s", payment.Owner, payment.Name)
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ type Permission struct {
|
||||
Users []string `xorm:"mediumtext" json:"users"`
|
||||
Roles []string `xorm:"mediumtext" json:"roles"`
|
||||
|
||||
Model string `xorm:"varchar(100)" json:"model"`
|
||||
ResourceType string `xorm:"varchar(100)" json:"resourceType"`
|
||||
Resources []string `xorm:"mediumtext" json:"resources"`
|
||||
Actions []string `xorm:"mediumtext" json:"actions"`
|
||||
|
@ -170,6 +170,7 @@ func BuyProduct(id string, providerName string, user *User, host string) (string
|
||||
|
||||
owner := product.Owner
|
||||
productName := product.Name
|
||||
payerName := fmt.Sprintf("%s | %s", user.Name, user.DisplayName)
|
||||
paymentName := util.GenerateTimeId()
|
||||
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)
|
||||
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 {
|
||||
return "", err
|
||||
}
|
||||
|
@ -32,10 +32,10 @@ func TestProduct(t *testing.T) {
|
||||
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)
|
||||
|
||||
paymentId := util.GenerateTimeId()
|
||||
paymentName := util.GenerateTimeId()
|
||||
returnUrl := ""
|
||||
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 {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -142,8 +142,8 @@ func GetProvider(id string) *Provider {
|
||||
return getProvider(owner, name)
|
||||
}
|
||||
|
||||
func GetDefaultHumanCheckProvider() *Provider {
|
||||
provider := Provider{Owner: "admin", Category: "HumanCheck"}
|
||||
func GetDefaultCaptchaProvider() *Provider {
|
||||
provider := Provider{Owner: "admin", Category: "Captcha"}
|
||||
existed, err := adapter.Engine.Get(&provider)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -172,7 +172,14 @@ func UpdateProvider(id string, provider *Provider) bool {
|
||||
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 {
|
||||
panic(err)
|
||||
}
|
||||
@ -218,3 +225,37 @@ func (p *Provider) getPaymentProvider() (pp.PaymentProvider, *Cert, error) {
|
||||
func (p *Provider) GetId() string {
|
||||
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
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ import (
|
||||
)
|
||||
|
||||
//returns a saml2 response
|
||||
func NewSamlResponse(user *User, host string, publicKey string, destination string, iss string, redirectUri []string) (*etree.Element, error) {
|
||||
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",
|
||||
@ -51,7 +51,7 @@ func NewSamlResponse(user *User, host string, publicKey string, destination stri
|
||||
samlResponse.CreateAttr("Version", "2.0")
|
||||
samlResponse.CreateAttr("IssueInstant", now)
|
||||
samlResponse.CreateAttr("Destination", destination)
|
||||
samlResponse.CreateAttr("InResponseTo", fmt.Sprintf("Casdoor_%s", arId))
|
||||
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")
|
||||
@ -68,7 +68,7 @@ func NewSamlResponse(user *User, host string, publicKey string, destination stri
|
||||
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", fmt.Sprintf("_%s", arId))
|
||||
subjectConfirmationData.CreateAttr("InResponseTo", requestId)
|
||||
subjectConfirmationData.CreateAttr("Recipient", destination)
|
||||
subjectConfirmationData.CreateAttr("NotOnOrAfter", expireTime)
|
||||
condition := assertion.CreateElement("saml:Conditions")
|
||||
@ -225,14 +225,15 @@ func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, e
|
||||
return &d, nil
|
||||
}
|
||||
|
||||
//GenerateSamlResponse generates a SAML2.0 response
|
||||
//parameter samlRequest is saml request in base64 format
|
||||
// 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) {
|
||||
//decode samlRequest
|
||||
// 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)
|
||||
@ -241,42 +242,58 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("err: %s", err.Error())
|
||||
}
|
||||
//verify samlRequest
|
||||
|
||||
// verify samlRequest
|
||||
if valid := CheckRedirectUriValid(application, authnRequest.Issuer.Url); !valid {
|
||||
return "", "", fmt.Errorf("err: invalid issuer url")
|
||||
}
|
||||
|
||||
//get publickey string
|
||||
// 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, application.RedirectUris)
|
||||
// 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.SignEnveloped(samlResponse)
|
||||
//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())
|
||||
}
|
||||
|
||||
doc := etree.NewDocument()
|
||||
doc.SetRoot(signedXML)
|
||||
xmlStr, err := doc.WriteToString()
|
||||
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()
|
||||
}
|
||||
res := base64.StdEncoding.EncodeToString([]byte(xmlStr))
|
||||
// base64 encode
|
||||
res := base64.StdEncoding.EncodeToString(xmlBytes)
|
||||
return res, authnRequest.AssertionConsumerServiceURL, nil
|
||||
}
|
||||
|
||||
//return a saml1.1 response(not 2.0)
|
||||
// NewSamlResponse11 return a saml1.1 response(not 2.0)
|
||||
func NewSamlResponse11(user *User, requestID string, host string) *etree.Element {
|
||||
samlResponse := &etree.Element{
|
||||
Space: "samlp",
|
||||
|
@ -56,6 +56,9 @@ func getUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool
|
||||
// provider.Domain = "http://localhost:8000" or "https://door.casdoor.com"
|
||||
host = util.UrlJoin(provider.Domain, "/files")
|
||||
}
|
||||
if provider.Type == "Azure Blob" {
|
||||
host = fmt.Sprintf("%s/%s", host, provider.Bucket)
|
||||
}
|
||||
|
||||
fileUrl := util.UrlJoin(host, objectKey)
|
||||
if hasTimestamp {
|
||||
|
@ -133,7 +133,11 @@ func UpdateSyncer(id string, syncer *Syncer) bool {
|
||||
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 {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -173,7 +173,18 @@ func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]string) []*Or
|
||||
}
|
||||
|
||||
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" {
|
||||
|
232
object/token.go
232
object/token.go
@ -17,7 +17,6 @@ package object
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
@ -28,7 +27,14 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
hourSeconds = 3600
|
||||
hourSeconds = 3600
|
||||
INVALID_REQUEST = "invalid_request"
|
||||
INVALID_CLIENT = "invalid_client"
|
||||
INVALID_GRANT = "invalid_grant"
|
||||
UNAUTHORIZED_CLIENT = "unauthorized_client"
|
||||
UNSUPPORTED_GRANT_TYPE = "unsupported_grant_type"
|
||||
INVALID_SCOPE = "invalid_scope"
|
||||
ENDPOINT_ERROR = "endpoint_error"
|
||||
)
|
||||
|
||||
type Code struct {
|
||||
@ -63,7 +69,11 @@ type TokenWrapper struct {
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
Scope string `json:"scope"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type TokenError struct {
|
||||
Error string `json:"error"`
|
||||
ErrorDescription string `json:"error_description,omitempty"`
|
||||
}
|
||||
|
||||
type IntrospectionResponse struct {
|
||||
@ -311,59 +321,42 @@ 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, tag string, avatar string) *TokenWrapper {
|
||||
var errString string
|
||||
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string, scope string, username string, password string, host string, tag string, avatar string) interface{} {
|
||||
application := GetApplicationByClientId(clientId)
|
||||
if application == nil {
|
||||
errString = "error: invalid client_id"
|
||||
return &TokenWrapper{
|
||||
AccessToken: errString,
|
||||
TokenType: "",
|
||||
ExpiresIn: 0,
|
||||
Scope: "",
|
||||
Error: errString,
|
||||
return &TokenError{
|
||||
Error: INVALID_CLIENT,
|
||||
ErrorDescription: "client_id is invalid",
|
||||
}
|
||||
}
|
||||
|
||||
//Check if grantType is allowed in the current application
|
||||
|
||||
if !IsGrantTypeValid(grantType, application.GrantTypes) && tag == "" {
|
||||
errString = fmt.Sprintf("error: grant_type: %s is not supported in this application", grantType)
|
||||
return &TokenWrapper{
|
||||
AccessToken: errString,
|
||||
TokenType: "",
|
||||
ExpiresIn: 0,
|
||||
Scope: "",
|
||||
Error: errString,
|
||||
return &TokenError{
|
||||
Error: UNSUPPORTED_GRANT_TYPE,
|
||||
ErrorDescription: fmt.Sprintf("grant_type: %s is not supported in this application", grantType),
|
||||
}
|
||||
}
|
||||
|
||||
var token *Token
|
||||
var err error
|
||||
var tokenError *TokenError
|
||||
switch grantType {
|
||||
case "authorization_code": // Authorization Code Grant
|
||||
token, err = GetAuthorizationCodeToken(application, clientSecret, code, verifier)
|
||||
token, tokenError = GetAuthorizationCodeToken(application, clientSecret, code, verifier)
|
||||
case "password": // Resource Owner Password Credentials Grant
|
||||
token, err = GetPasswordToken(application, username, password, scope, host)
|
||||
token, tokenError = GetPasswordToken(application, username, password, scope, host)
|
||||
case "client_credentials": // Client Credentials Grant
|
||||
token, err = GetClientCredentialsToken(application, clientSecret, scope, host)
|
||||
token, tokenError = GetClientCredentialsToken(application, clientSecret, scope, host)
|
||||
}
|
||||
|
||||
if tag == "wechat_miniprogram" {
|
||||
// Wechat Mini Program
|
||||
token, err = GetWechatMiniProgramToken(application, code, host, username, avatar)
|
||||
token, tokenError = GetWechatMiniProgramToken(application, code, host, username, avatar)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
errString = err.Error()
|
||||
return &TokenWrapper{
|
||||
AccessToken: errString,
|
||||
TokenType: "",
|
||||
ExpiresIn: 0,
|
||||
Scope: "",
|
||||
Error: errString,
|
||||
}
|
||||
if tokenError != nil {
|
||||
return tokenError
|
||||
}
|
||||
|
||||
token.CodeIsUsed = true
|
||||
@ -380,81 +373,59 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
|
||||
return tokenWrapper
|
||||
}
|
||||
|
||||
func RefreshToken(grantType string, refreshToken string, scope string, clientId string, clientSecret string, host string) *TokenWrapper {
|
||||
var errString string
|
||||
func RefreshToken(grantType string, refreshToken string, scope string, clientId string, clientSecret string, host string) interface{} {
|
||||
// check parameters
|
||||
if grantType != "refresh_token" {
|
||||
errString = "error: grant_type should be \"refresh_token\""
|
||||
return &TokenWrapper{
|
||||
AccessToken: errString,
|
||||
TokenType: "",
|
||||
ExpiresIn: 0,
|
||||
Scope: "",
|
||||
Error: errString,
|
||||
return &TokenError{
|
||||
Error: UNSUPPORTED_GRANT_TYPE,
|
||||
ErrorDescription: "grant_type should be refresh_token",
|
||||
}
|
||||
}
|
||||
application := GetApplicationByClientId(clientId)
|
||||
if application == nil {
|
||||
errString = "error: invalid client_id"
|
||||
return &TokenWrapper{
|
||||
AccessToken: errString,
|
||||
TokenType: "",
|
||||
ExpiresIn: 0,
|
||||
Scope: "",
|
||||
Error: errString,
|
||||
return &TokenError{
|
||||
Error: INVALID_CLIENT,
|
||||
ErrorDescription: "client_id is invalid",
|
||||
}
|
||||
}
|
||||
if clientSecret != "" && application.ClientSecret != clientSecret {
|
||||
errString = "error: invalid client_secret"
|
||||
return &TokenWrapper{
|
||||
AccessToken: errString,
|
||||
TokenType: "",
|
||||
ExpiresIn: 0,
|
||||
Scope: "",
|
||||
Error: errString,
|
||||
return &TokenError{
|
||||
Error: INVALID_CLIENT,
|
||||
ErrorDescription: "client_secret is invalid",
|
||||
}
|
||||
}
|
||||
// check whether the refresh token is valid, and has not expired.
|
||||
token := Token{RefreshToken: refreshToken}
|
||||
existed, err := adapter.Engine.Get(&token)
|
||||
if err != nil || !existed {
|
||||
errString = "error: invalid refresh_token"
|
||||
return &TokenWrapper{
|
||||
AccessToken: errString,
|
||||
TokenType: "",
|
||||
ExpiresIn: 0,
|
||||
Scope: "",
|
||||
Error: errString,
|
||||
return &TokenError{
|
||||
Error: INVALID_GRANT,
|
||||
ErrorDescription: "refresh token is invalid, expired or revoked",
|
||||
}
|
||||
}
|
||||
|
||||
cert := getCertByApplication(application)
|
||||
_, err = ParseJwtToken(refreshToken, cert)
|
||||
if err != nil {
|
||||
errString := fmt.Sprintf("error: %s", err.Error())
|
||||
return &TokenWrapper{
|
||||
AccessToken: errString,
|
||||
TokenType: "",
|
||||
ExpiresIn: 0,
|
||||
Scope: "",
|
||||
Error: errString,
|
||||
return &TokenError{
|
||||
Error: INVALID_GRANT,
|
||||
ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
|
||||
}
|
||||
}
|
||||
// generate a new token
|
||||
user := getUser(application.Organization, token.User)
|
||||
if user.IsForbidden {
|
||||
errString = "error: the user is forbidden to sign in, please contact the administrator"
|
||||
return &TokenWrapper{
|
||||
AccessToken: errString,
|
||||
TokenType: "",
|
||||
ExpiresIn: 0,
|
||||
Scope: "",
|
||||
Error: errString,
|
||||
return &TokenError{
|
||||
Error: INVALID_GRANT,
|
||||
ErrorDescription: "the user is forbidden to sign in, please contact the administrator",
|
||||
}
|
||||
}
|
||||
newAccessToken, newRefreshToken, err := generateJwtToken(application, user, "", scope, host)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return &TokenError{
|
||||
Error: ENDPOINT_ERROR,
|
||||
ErrorDescription: fmt.Sprintf("generate jwt token error: %s", err.Error()),
|
||||
}
|
||||
}
|
||||
|
||||
newToken := &Token{
|
||||
@ -508,63 +479,99 @@ func IsGrantTypeValid(method string, grantTypes []string) bool {
|
||||
}
|
||||
|
||||
// Authorization code flow
|
||||
func GetAuthorizationCodeToken(application *Application, clientSecret string, code string, verifier string) (*Token, error) {
|
||||
func GetAuthorizationCodeToken(application *Application, clientSecret string, code string, verifier string) (*Token, *TokenError) {
|
||||
if code == "" {
|
||||
return nil, errors.New("error: authorization code should not be empty")
|
||||
return nil, &TokenError{
|
||||
Error: INVALID_REQUEST,
|
||||
ErrorDescription: "authorization code should not be empty",
|
||||
}
|
||||
}
|
||||
|
||||
token := getTokenByCode(code)
|
||||
if token == nil {
|
||||
return nil, errors.New("error: invalid authorization code")
|
||||
return nil, &TokenError{
|
||||
Error: INVALID_GRANT,
|
||||
ErrorDescription: "authorization code is invalid",
|
||||
}
|
||||
}
|
||||
if token.CodeIsUsed {
|
||||
// anti replay attacks
|
||||
return nil, errors.New("error: authorization code has been used")
|
||||
return nil, &TokenError{
|
||||
Error: INVALID_GRANT,
|
||||
ErrorDescription: "authorization code has been used",
|
||||
}
|
||||
}
|
||||
|
||||
if token.CodeChallenge != "" && pkceChallenge(verifier) != token.CodeChallenge {
|
||||
return nil, errors.New("error: incorrect code_verifier")
|
||||
return nil, &TokenError{
|
||||
Error: INVALID_GRANT,
|
||||
ErrorDescription: "verifier is invalid",
|
||||
}
|
||||
}
|
||||
|
||||
if application.ClientSecret != clientSecret {
|
||||
// when using PKCE, the Client Secret can be empty,
|
||||
// but if it is provided, it must be accurate.
|
||||
if token.CodeChallenge == "" {
|
||||
return nil, errors.New("error: invalid client_secret")
|
||||
return nil, &TokenError{
|
||||
Error: INVALID_CLIENT,
|
||||
ErrorDescription: "client_secret is invalid",
|
||||
}
|
||||
} else {
|
||||
if clientSecret != "" {
|
||||
return nil, errors.New("error: invalid client_secret")
|
||||
return nil, &TokenError{
|
||||
Error: INVALID_CLIENT,
|
||||
ErrorDescription: "client_secret is invalid",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if application.Name != token.Application {
|
||||
return nil, errors.New("error: the token is for wrong application (client_id)")
|
||||
return nil, &TokenError{
|
||||
Error: INVALID_GRANT,
|
||||
ErrorDescription: "the token is for wrong application (client_id)",
|
||||
}
|
||||
}
|
||||
|
||||
if time.Now().Unix() > token.CodeExpireIn {
|
||||
// code must be used within 5 minutes
|
||||
return nil, errors.New("error: authorization code has expired")
|
||||
return nil, &TokenError{
|
||||
Error: INVALID_GRANT,
|
||||
ErrorDescription: "authorization code has expired",
|
||||
}
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// Resource Owner Password Credentials flow
|
||||
func GetPasswordToken(application *Application, username string, password string, scope string, host string) (*Token, error) {
|
||||
func GetPasswordToken(application *Application, username string, password string, scope string, host string) (*Token, *TokenError) {
|
||||
user := getUser(application.Organization, username)
|
||||
if user == nil {
|
||||
return nil, errors.New("error: the user does not exist")
|
||||
return nil, &TokenError{
|
||||
Error: INVALID_GRANT,
|
||||
ErrorDescription: "the user does not exist",
|
||||
}
|
||||
}
|
||||
msg := CheckPassword(user, password)
|
||||
if msg != "" {
|
||||
return nil, errors.New("error: invalid username or password")
|
||||
return nil, &TokenError{
|
||||
Error: INVALID_GRANT,
|
||||
ErrorDescription: "invalid username or password",
|
||||
}
|
||||
}
|
||||
if user.IsForbidden {
|
||||
return nil, errors.New("error: the user is forbidden to sign in, please contact the administrator")
|
||||
return nil, &TokenError{
|
||||
Error: INVALID_GRANT,
|
||||
ErrorDescription: "the user is forbidden to sign in, please contact the administrator",
|
||||
}
|
||||
}
|
||||
accessToken, refreshToken, err := generateJwtToken(application, user, "", scope, host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, &TokenError{
|
||||
Error: ENDPOINT_ERROR,
|
||||
ErrorDescription: fmt.Sprintf("generate jwt token error: %s", err.Error()),
|
||||
}
|
||||
}
|
||||
token := &Token{
|
||||
Owner: application.Owner,
|
||||
@ -586,9 +593,12 @@ func GetPasswordToken(application *Application, username string, password string
|
||||
}
|
||||
|
||||
// Client Credentials flow
|
||||
func GetClientCredentialsToken(application *Application, clientSecret string, scope string, host string) (*Token, error) {
|
||||
func GetClientCredentialsToken(application *Application, clientSecret string, scope string, host string) (*Token, *TokenError) {
|
||||
if application.ClientSecret != clientSecret {
|
||||
return nil, errors.New("error: invalid client_secret")
|
||||
return nil, &TokenError{
|
||||
Error: INVALID_CLIENT,
|
||||
ErrorDescription: "client_secret is invalid",
|
||||
}
|
||||
}
|
||||
nullUser := &User{
|
||||
Owner: application.Owner,
|
||||
@ -597,7 +607,10 @@ func GetClientCredentialsToken(application *Application, clientSecret string, sc
|
||||
}
|
||||
accessToken, _, err := generateJwtToken(application, nullUser, "", scope, host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, &TokenError{
|
||||
Error: ENDPOINT_ERROR,
|
||||
ErrorDescription: fmt.Sprintf("generate jwt token error: %s", err.Error()),
|
||||
}
|
||||
}
|
||||
token := &Token{
|
||||
Owner: application.Owner,
|
||||
@ -643,25 +656,37 @@ func GetTokenByUser(application *Application, user *User, scope string, host str
|
||||
}
|
||||
|
||||
// Wechat Mini Program flow
|
||||
func GetWechatMiniProgramToken(application *Application, code string, host string, username string, avatar string) (*Token, error) {
|
||||
func GetWechatMiniProgramToken(application *Application, code string, host string, username string, avatar string) (*Token, *TokenError) {
|
||||
mpProvider := GetWechatMiniProgramProvider(application)
|
||||
if mpProvider == nil {
|
||||
return nil, errors.New("error: the application does not support wechat mini program")
|
||||
return nil, &TokenError{
|
||||
Error: INVALID_CLIENT,
|
||||
ErrorDescription: "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
|
||||
return nil, &TokenError{
|
||||
Error: INVALID_GRANT,
|
||||
ErrorDescription: fmt.Sprintf("get wechat mini program session error: %s", err.Error()),
|
||||
}
|
||||
}
|
||||
openId, unionId := session.Openid, session.Unionid
|
||||
if openId == "" && unionId == "" {
|
||||
return nil, errors.New("err: WeChat's openid and unionid are empty")
|
||||
return nil, &TokenError{
|
||||
Error: INVALID_REQUEST,
|
||||
ErrorDescription: "the wechat mini program session is invalid",
|
||||
}
|
||||
}
|
||||
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")
|
||||
return nil, &TokenError{
|
||||
Error: INVALID_GRANT,
|
||||
ErrorDescription: "the application does not allow to sign up new account",
|
||||
}
|
||||
}
|
||||
//Add new user
|
||||
var name string
|
||||
@ -691,7 +716,10 @@ func GetWechatMiniProgramToken(application *Application, code string, host strin
|
||||
|
||||
accessToken, refreshToken, err := generateJwtToken(application, user, "", "", host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, &TokenError{
|
||||
Error: ENDPOINT_ERROR,
|
||||
ErrorDescription: fmt.Sprintf("generate jwt token error: %s", err.Error()),
|
||||
}
|
||||
}
|
||||
|
||||
token := &Token{
|
||||
|
@ -94,6 +94,9 @@ type User struct {
|
||||
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"`
|
||||
Slack string `xorm:"slack varchar(100)" json:"slack"`
|
||||
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"`
|
||||
@ -313,6 +316,9 @@ func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) boo
|
||||
return false
|
||||
}
|
||||
|
||||
if user.Password == "***" {
|
||||
user.Password = oldUser.Password
|
||||
}
|
||||
user.UpdateUserHash()
|
||||
|
||||
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}
|
||||
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 {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ func NewAlipayPaymentProvider(appId string, appPublicKey string, appPrivateKey s
|
||||
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
|
||||
|
||||
bm := gopay.BodyMap{}
|
||||
@ -90,3 +90,7 @@ func (pp *AlipayPaymentProvider) Notify(request *http.Request, body []byte, auth
|
||||
|
||||
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"`
|
||||
OrderNo string `json:"orderno"`
|
||||
Amount string `json:"amount"`
|
||||
PayerId string `json:"payerid"`
|
||||
PayerName string `json:"payername"`
|
||||
Xmpch string `json:"xmpch"`
|
||||
Body string `json:"body"`
|
||||
ReturnUrl string `json:"return_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 {
|
||||
@ -87,6 +90,27 @@ type GcResponseBody struct {
|
||||
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 {
|
||||
pp := &GcPaymentProvider{}
|
||||
|
||||
@ -130,16 +154,17 @@ func (pp *GcPaymentProvider) doPost(postBytes []byte) ([]byte, error) {
|
||||
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{
|
||||
OrderDate: util.GenerateSimpleTimeId(),
|
||||
OrderNo: util.GenerateTimeId(),
|
||||
OrderNo: paymentName,
|
||||
Amount: getPriceString(price),
|
||||
PayerId: "",
|
||||
PayerName: "",
|
||||
Xmpch: pp.Xmpch,
|
||||
Body: productDisplayName,
|
||||
ReturnUrl: returnUrl,
|
||||
NotifyUrl: notifyUrl,
|
||||
Remark1: payerName,
|
||||
Remark2: productName,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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"
|
||||
|
||||
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)
|
||||
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 {
|
||||
|
@ -54,17 +54,17 @@ func isAddressOpen(address string) bool {
|
||||
}
|
||||
|
||||
func getProxyHttpClient() *http.Client {
|
||||
sock5Proxy := conf.GetConfigString("sock5Proxy")
|
||||
if sock5Proxy == "" {
|
||||
socks5Proxy := conf.GetConfigString("socks5Proxy")
|
||||
if socks5Proxy == "" {
|
||||
return &http.Client{}
|
||||
}
|
||||
|
||||
if !isAddressOpen(sock5Proxy) {
|
||||
if !isAddressOpen(socks5Proxy) {
|
||||
return &http.Client{}
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/33585587/creating-a-go-socks5-client
|
||||
dialer, err := proxy.SOCKS5("tcp", sock5Proxy, nil, proxy.Direct)
|
||||
dialer, err := proxy.SOCKS5("tcp", socks5Proxy, nil, proxy.Direct)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -76,7 +76,7 @@ func getProxyHttpClient() *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
|
||||
} else {
|
||||
return DefaultHttpClient
|
||||
|
@ -104,6 +104,11 @@ 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") || strings.HasSuffix(urlPath, "/p3/serviceValidate") || strings.HasSuffix(urlPath, "/p3/proxyValidate") || strings.HasSuffix(urlPath, "/samlValidate")) {
|
||||
return "/cas"
|
||||
}
|
||||
|
||||
if strings.HasPrefix(urlPath, "/api/login/oauth") {
|
||||
return "/api/login/oauth"
|
||||
}
|
||||
|
||||
return urlPath
|
||||
}
|
||||
|
||||
|
51
routers/cors_filter.go
Normal file
51
routers/cors_filter.go
Normal file
@ -0,0 +1,51 @@
|
||||
// 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)
|
||||
originConf := conf.GetConfigString("origin")
|
||||
|
||||
if origin != "" && originConf != "" && origin != originConf {
|
||||
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)
|
||||
}
|
||||
|
||||
go object.AddRecord(record)
|
||||
util.SafeGoroutine(func() { object.AddRecord(record) })
|
||||
}
|
||||
|
@ -84,12 +84,19 @@ func initAPI() {
|
||||
beego.Router("/api/add-permission", &controllers.ApiController{}, "POST:AddPermission")
|
||||
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/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword")
|
||||
beego.Router("/api/get-email-and-phone", &controllers.ApiController{}, "POST:GetEmailAndPhone")
|
||||
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/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-ldaps", &controllers.ApiController{}, "POST:GetLdaps")
|
||||
@ -167,6 +174,7 @@ func initAPI() {
|
||||
beego.Router("/api/add-payment", &controllers.ApiController{}, "POST:AddPayment")
|
||||
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/invoice-payment", &controllers.ApiController{}, "POST:InvoicePayment")
|
||||
|
||||
beego.Router("/api/send-email", &controllers.ApiController{}, "POST:SendEmail")
|
||||
beego.Router("/api/send-sms", &controllers.ApiController{}, "POST:SendSms")
|
||||
|
@ -15,8 +15,8 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"github.com/qor/oss"
|
||||
"github.com/qor/oss/aliyun"
|
||||
"github.com/casdoor/oss"
|
||||
"github.com/casdoor/oss/aliyun"
|
||||
)
|
||||
|
||||
func NewAliyunOssStorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
|
||||
|
@ -16,8 +16,8 @@ package storage
|
||||
|
||||
import (
|
||||
awss3 "github.com/aws/aws-sdk-go/service/s3"
|
||||
"github.com/qor/oss"
|
||||
"github.com/qor/oss/s3"
|
||||
"github.com/casdoor/oss"
|
||||
"github.com/casdoor/oss/s3"
|
||||
)
|
||||
|
||||
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"
|
||||
"strings"
|
||||
|
||||
"github.com/qor/oss"
|
||||
"github.com/casdoor/oss"
|
||||
)
|
||||
|
||||
var baseFolder = "files"
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
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 {
|
||||
switch providerType {
|
||||
@ -26,6 +26,8 @@ func GetStorageProvider(providerType string, clientId string, clientSecret strin
|
||||
return NewAliyunOssStorageProvider(clientId, clientSecret, region, bucket, endpoint)
|
||||
case "Tencent Cloud COS":
|
||||
return NewTencentCloudCosStorageProvider(clientId, clientSecret, region, bucket, endpoint)
|
||||
case "Azure Blob":
|
||||
return NewAzureBlobStorageProvider(clientId, clientSecret, region, bucket, endpoint)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -15,8 +15,8 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"github.com/qor/oss"
|
||||
"github.com/qor/oss/tencent"
|
||||
"github.com/casdoor/oss"
|
||||
"github.com/casdoor/oss/tencent"
|
||||
)
|
||||
|
||||
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:
|
||||
- OIDC API
|
||||
operationId: RootController.GetJwks
|
||||
responses:
|
||||
"200":
|
||||
description: ""
|
||||
schema:
|
||||
$ref: '#/definitions/jose.JSONWebKey'
|
||||
/.well-known/openid-configuration:
|
||||
get:
|
||||
tags:
|
||||
- OIDC API
|
||||
description: Get Oidc Discovery
|
||||
operationId: RootController.GetOidcDiscovery
|
||||
responses:
|
||||
"200":
|
||||
description: ""
|
||||
schema:
|
||||
$ref: '#/definitions/object.OidcDiscovery'
|
||||
/api/add-application:
|
||||
post:
|
||||
tags:
|
||||
@ -58,6 +69,24 @@ paths:
|
||||
tags:
|
||||
- Account API
|
||||
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:
|
||||
post:
|
||||
tags:
|
||||
@ -243,11 +272,11 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/api/get-human-check:
|
||||
/api/api/get-captcha:
|
||||
get:
|
||||
tags:
|
||||
- Login API
|
||||
operationId: ApiController.GetHumancheck
|
||||
operationId: ApiController.GetCaptcha
|
||||
/api/api/reset-email-or-phone:
|
||||
post:
|
||||
tags:
|
||||
@ -271,11 +300,11 @@ paths:
|
||||
required: true
|
||||
type: string
|
||||
- in: body
|
||||
name: body
|
||||
name: from
|
||||
description: Details of the email request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/emailForm'
|
||||
$ref: '#/definitions/controllers.EmailForm'
|
||||
responses:
|
||||
"200":
|
||||
description: object
|
||||
@ -299,11 +328,11 @@ paths:
|
||||
required: true
|
||||
type: string
|
||||
- in: body
|
||||
name: body
|
||||
name: from
|
||||
description: Details of the sms request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/smsForm'
|
||||
$ref: '#/definitions/controllers.SmsForm'
|
||||
responses:
|
||||
"200":
|
||||
description: object
|
||||
@ -382,6 +411,24 @@ paths:
|
||||
tags:
|
||||
- Account API
|
||||
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:
|
||||
post:
|
||||
tags:
|
||||
@ -578,6 +625,43 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$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:
|
||||
get:
|
||||
tags:
|
||||
@ -700,6 +784,42 @@ paths:
|
||||
tags:
|
||||
- Account API
|
||||
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:
|
||||
get:
|
||||
tags:
|
||||
@ -901,9 +1021,7 @@ paths:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.Records'
|
||||
$ref: '#/definitions/object.Record'
|
||||
/api/get-records-filter:
|
||||
post:
|
||||
tags:
|
||||
@ -912,18 +1030,17 @@ paths:
|
||||
operationId: ApiController.GetRecordsByFilter
|
||||
parameters:
|
||||
- in: body
|
||||
name: body
|
||||
name: filter
|
||||
description: filter Record message
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/object.Records'
|
||||
type: string
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.Records'
|
||||
$ref: '#/definitions/object.Record'
|
||||
/api/get-resource:
|
||||
get:
|
||||
tags:
|
||||
@ -1216,6 +1333,23 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
$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:
|
||||
post:
|
||||
tags:
|
||||
@ -1224,21 +1358,51 @@ paths:
|
||||
operationId: ApiController.Login
|
||||
parameters:
|
||||
- in: query
|
||||
name: oAuthParams
|
||||
description: oAuth parameters
|
||||
name: clientId
|
||||
description: clientId
|
||||
required: true
|
||||
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
|
||||
name: body
|
||||
name: form
|
||||
description: Login information
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/RequestForm'
|
||||
$ref: '#/definitions/controllers.RequestForm'
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.api_controller.Response'
|
||||
$ref: '#/definitions/Response'
|
||||
/api/login/oauth/access_token:
|
||||
post:
|
||||
tags:
|
||||
@ -1271,6 +1435,14 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.TokenWrapper'
|
||||
"400":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.TokenError'
|
||||
"401":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.TokenError'
|
||||
/api/login/oauth/code:
|
||||
post:
|
||||
tags:
|
||||
@ -1333,6 +1505,14 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.IntrospectionResponse'
|
||||
"400":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.TokenError'
|
||||
"401":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.TokenError'
|
||||
/api/login/oauth/logout:
|
||||
get:
|
||||
tags:
|
||||
@ -1395,6 +1575,14 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.TokenWrapper'
|
||||
"400":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.TokenError'
|
||||
"401":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.TokenError'
|
||||
/api/logout:
|
||||
post:
|
||||
tags:
|
||||
@ -1424,6 +1612,24 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$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:
|
||||
post:
|
||||
tags:
|
||||
@ -1493,42 +1699,6 @@ paths:
|
||||
tags:
|
||||
- Login API
|
||||
/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:
|
||||
tags:
|
||||
- Application API
|
||||
@ -1579,6 +1749,29 @@ paths:
|
||||
tags:
|
||||
- Account API
|
||||
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:
|
||||
post:
|
||||
tags:
|
||||
@ -1830,27 +2023,99 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.Userinfo'
|
||||
/api/verify-captcha:
|
||||
post:
|
||||
tags:
|
||||
- Verification API
|
||||
operationId: ApiController.VerifyCaptcha
|
||||
definitions:
|
||||
2127.0xc00036c600.false:
|
||||
2127.0xc000398090.false:
|
||||
title: "false"
|
||||
type: object
|
||||
2161.0xc00036c630.false:
|
||||
2161.0xc0003980c0.false:
|
||||
title: "false"
|
||||
type: object
|
||||
RequestForm:
|
||||
title: RequestForm
|
||||
type: object
|
||||
Response:
|
||||
title: Response
|
||||
type: object
|
||||
controllers.EmailForm:
|
||||
title: EmailForm
|
||||
type: object
|
||||
properties:
|
||||
content:
|
||||
type: string
|
||||
provider:
|
||||
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:
|
||||
title: Response
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/definitions/2127.0xc00036c600.false'
|
||||
$ref: '#/definitions/2127.0xc000398090.false'
|
||||
data2:
|
||||
$ref: '#/definitions/2161.0xc00036c630.false'
|
||||
$ref: '#/definitions/2161.0xc0003980c0.false'
|
||||
msg:
|
||||
type: string
|
||||
name:
|
||||
@ -1859,25 +2124,33 @@ definitions:
|
||||
type: string
|
||||
sub:
|
||||
type: string
|
||||
controllers.api_controller.Response:
|
||||
title: Response
|
||||
controllers.SmsForm:
|
||||
title: SmsForm
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/definitions/2127.0xc00036c600.false'
|
||||
data2:
|
||||
$ref: '#/definitions/2161.0xc00036c630.false'
|
||||
msg:
|
||||
content:
|
||||
type: string
|
||||
organizationId:
|
||||
type: string
|
||||
receivers:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
jose.JSONWebKey:
|
||||
title: JSONWebKey
|
||||
type: object
|
||||
object.AccountItem:
|
||||
title: AccountItem
|
||||
type: object
|
||||
properties:
|
||||
modifyRule:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
status:
|
||||
viewRule:
|
||||
type: string
|
||||
sub:
|
||||
type: string
|
||||
emailForm:
|
||||
title: emailForm
|
||||
type: object
|
||||
visible:
|
||||
type: boolean
|
||||
object.Adapter:
|
||||
title: Adapter
|
||||
type: object
|
||||
@ -2037,10 +2310,80 @@ definitions:
|
||||
type: string
|
||||
username:
|
||||
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:
|
||||
title: Organization
|
||||
type: object
|
||||
properties:
|
||||
accountItems:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.AccountItem'
|
||||
createdTime:
|
||||
type: string
|
||||
defaultAvatar:
|
||||
@ -2051,6 +2394,8 @@ definitions:
|
||||
type: boolean
|
||||
favicon:
|
||||
type: string
|
||||
isProfilePublic:
|
||||
type: boolean
|
||||
masterPassword:
|
||||
type: string
|
||||
name:
|
||||
@ -2081,6 +2426,16 @@ definitions:
|
||||
type: string
|
||||
displayName:
|
||||
type: string
|
||||
invoiceRemark:
|
||||
type: string
|
||||
invoiceTaxId:
|
||||
type: string
|
||||
invoiceTitle:
|
||||
type: string
|
||||
invoiceType:
|
||||
type: string
|
||||
invoiceUrl:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
name:
|
||||
@ -2091,6 +2446,14 @@ definitions:
|
||||
type: string
|
||||
payUrl:
|
||||
type: string
|
||||
personEmail:
|
||||
type: string
|
||||
personIdCard:
|
||||
type: string
|
||||
personName:
|
||||
type: string
|
||||
personPhone:
|
||||
type: string
|
||||
price:
|
||||
type: number
|
||||
format: double
|
||||
@ -2126,6 +2489,8 @@ definitions:
|
||||
type: string
|
||||
isEnabled:
|
||||
type: boolean
|
||||
model:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
owner:
|
||||
@ -2205,6 +2570,16 @@ definitions:
|
||||
type: string
|
||||
createdTime:
|
||||
type: string
|
||||
customAuthUrl:
|
||||
type: string
|
||||
customLogo:
|
||||
type: string
|
||||
customScope:
|
||||
type: string
|
||||
customTokenUrl:
|
||||
type: string
|
||||
customUserInfoUrl:
|
||||
type: string
|
||||
displayName:
|
||||
type: string
|
||||
domain:
|
||||
@ -2264,9 +2639,35 @@ definitions:
|
||||
type: boolean
|
||||
provider:
|
||||
$ref: '#/definitions/object.Provider'
|
||||
object.Records:
|
||||
title: Records
|
||||
object.Record:
|
||||
title: Record
|
||||
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:
|
||||
title: Role
|
||||
type: object
|
||||
@ -2401,14 +2802,20 @@ definitions:
|
||||
type: string
|
||||
user:
|
||||
type: string
|
||||
object.TokenError:
|
||||
title: TokenError
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
error_description:
|
||||
type: string
|
||||
object.TokenWrapper:
|
||||
title: TokenWrapper
|
||||
type: object
|
||||
properties:
|
||||
access_token:
|
||||
type: string
|
||||
error:
|
||||
type: string
|
||||
expires_in:
|
||||
type: integer
|
||||
format: int64
|
||||
@ -2442,6 +2849,8 @@ definitions:
|
||||
type: string
|
||||
baidu:
|
||||
type: string
|
||||
bilibili:
|
||||
type: string
|
||||
bio:
|
||||
type: string
|
||||
birthday:
|
||||
@ -2452,10 +2861,14 @@ definitions:
|
||||
type: string
|
||||
createdTime:
|
||||
type: string
|
||||
custom:
|
||||
type: string
|
||||
dingtalk:
|
||||
type: string
|
||||
displayName:
|
||||
type: string
|
||||
douyin:
|
||||
type: string
|
||||
education:
|
||||
type: string
|
||||
email:
|
||||
@ -2521,6 +2934,8 @@ definitions:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
okta:
|
||||
type: string
|
||||
owner:
|
||||
type: string
|
||||
password:
|
||||
@ -2558,6 +2973,8 @@ definitions:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
unionId:
|
||||
type: string
|
||||
updatedTime:
|
||||
type: string
|
||||
wechat:
|
||||
@ -2618,9 +3035,6 @@ definitions:
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
smsForm:
|
||||
title: smsForm
|
||||
type: object
|
||||
xorm.Engine:
|
||||
title: Engine
|
||||
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");
|
||||
// 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
|
||||
// limitations under the License.
|
||||
|
||||
package object
|
||||
package util
|
||||
|
||||
type SignupItem struct {
|
||||
Name string `json:"name"`
|
||||
Visible bool `json:"visible"`
|
||||
Required bool `json:"required"`
|
||||
Prompted bool `json:"prompted"`
|
||||
Rule string `json:"rule"`
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
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()
|
||||
}()
|
||||
}
|
2
web/.eslintignore
Normal file
2
web/.eslintignore
Normal file
@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
build
|
59
web/.eslintrc
Normal file
59
web/.eslintrc
Normal file
@ -0,0 +1,59 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 12,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
},
|
||||
"extends": ["eslint:recommended", "plugin:react/recommended"],
|
||||
"rules": {
|
||||
// "eqeqeq": "error",
|
||||
"semi": ["error", "always"],
|
||||
// "indent": ["error", 2],
|
||||
// follow antd's style guide
|
||||
"quotes": ["error", "double"],
|
||||
"jsx-quotes": ["error", "prefer-double"],
|
||||
"space-in-parens": ["error", "never"],
|
||||
"object-curly-spacing": ["error", "never"],
|
||||
"array-bracket-spacing": ["error", "never"],
|
||||
"comma-spacing": ["error", { "before": false, "after": true }],
|
||||
"react/jsx-curly-spacing": [
|
||||
"error",
|
||||
{ "when": "never", "allowMultiline": true, "children": true }
|
||||
],
|
||||
"arrow-spacing": ["error", { "before": true, "after": true }],
|
||||
"space-before-blocks": ["error", "always"],
|
||||
"spaced-comment": ["error", "always"],
|
||||
"react/jsx-tag-spacing": ["error", { "beforeSelfClosing": "always" }],
|
||||
"block-spacing": ["error", "never"],
|
||||
"space-before-function-paren": ["error", "never"],
|
||||
"no-trailing-spaces": ["error", { "ignoreComments": true }],
|
||||
"eol-last": ["error", "always"],
|
||||
// "no-var": ["error"],
|
||||
"curly": ["error", "all"],
|
||||
"brace-style": ["error", "1tbs", { "allowSingleLine": true }],
|
||||
"no-mixed-spaces-and-tabs": "error",
|
||||
|
||||
"react/prop-types": "off",
|
||||
"react/display-name": "off",
|
||||
"react/react-in-jsx-scope": "off",
|
||||
|
||||
// don't use strict mod now, otherwise there are a lot of errors in the codebase
|
||||
"no-unused-vars": "warn",
|
||||
"react/no-deprecated": "warn",
|
||||
"no-case-declarations": "warn",
|
||||
"react/jsx-key": "warn"
|
||||
}
|
||||
}
|
@ -1,38 +1,38 @@
|
||||
const CracoLessPlugin = require('craco-less');
|
||||
const CracoLessPlugin = require("craco-less");
|
||||
|
||||
module.exports = {
|
||||
devServer: {
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:8000',
|
||||
"/api": {
|
||||
target: "http://localhost:8000",
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/swagger': {
|
||||
target: 'http://localhost:8000',
|
||||
"/swagger": {
|
||||
target: "http://localhost:8000",
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/files': {
|
||||
target: 'http://localhost:8000',
|
||||
"/files": {
|
||||
target: "http://localhost:8000",
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/.well-known/openid-configuration': {
|
||||
target: 'http://localhost:8000',
|
||||
"/.well-known/openid-configuration": {
|
||||
target: "http://localhost:8000",
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/cas/serviceValidate': {
|
||||
target: 'http://localhost:8000',
|
||||
"/cas/serviceValidate": {
|
||||
target: "http://localhost:8000",
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/cas/proxyValidate': {
|
||||
target: 'http://localhost:8000',
|
||||
"/cas/proxyValidate": {
|
||||
target: "http://localhost:8000",
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/cas/proxy': {
|
||||
target: 'http://localhost:8000',
|
||||
"/cas/proxy": {
|
||||
target: "http://localhost:8000",
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/cas/validate': {
|
||||
target: 'http://localhost:8000',
|
||||
"/cas/validate": {
|
||||
target: "http://localhost:8000",
|
||||
changeOrigin: true,
|
||||
}
|
||||
},
|
||||
@ -43,7 +43,7 @@ module.exports = {
|
||||
options: {
|
||||
lessLoaderOptions: {
|
||||
lessOptions: {
|
||||
modifyVars: {'@primary-color': 'rgb(45,120,213)'},
|
||||
modifyVars: {"@primary-color": "rgb(45,120,213)"},
|
||||
javascriptEnabled: true,
|
||||
},
|
||||
},
|
||||
|
@ -59,6 +59,8 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^7.0.3"
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^7.11.0",
|
||||
"eslint-plugin-react": "^7.30.1"
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<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" />
|
||||
<!-- <link rel="icon" href="%PUBLIC_URL%/favicon.png" />-->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
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" />
|
||||
<!--
|
||||
|
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;
|
405
web/src/App.js
405
web/src/App.js
@ -12,13 +12,13 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import React, {Component} from 'react';
|
||||
import './App.less';
|
||||
import React, {Component} from "react";
|
||||
import "./App.less";
|
||||
import {Helmet} from "react-helmet";
|
||||
import * as Setting from "./Setting";
|
||||
import {DownOutlined, LogoutOutlined, SettingOutlined} from '@ant-design/icons';
|
||||
import {Avatar, BackTop, Dropdown, Layout, Menu, Card, Result, Button} from 'antd';
|
||||
import {Link, Redirect, Route, Switch, withRouter} from 'react-router-dom'
|
||||
import {DownOutlined, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
|
||||
import {Avatar, BackTop, Dropdown, Layout, Menu, Card, Result, Button} from "antd";
|
||||
import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom";
|
||||
import OrganizationListPage from "./OrganizationListPage";
|
||||
import OrganizationEditPage from "./OrganizationEditPage";
|
||||
import UserListPage from "./UserListPage";
|
||||
@ -63,14 +63,16 @@ import SelfForgetPage from "./auth/SelfForgetPage";
|
||||
import ForgetPage from "./auth/ForgetPage";
|
||||
import * as AuthBackend from "./auth/AuthBackend";
|
||||
import AuthCallback from "./auth/AuthCallback";
|
||||
import SelectLanguageBox from './SelectLanguageBox';
|
||||
import i18next from 'i18next';
|
||||
import SelectLanguageBox from "./SelectLanguageBox";
|
||||
import i18next from "i18next";
|
||||
import PromptPage from "./auth/PromptPage";
|
||||
import OdicDiscoveryPage from "./auth/OidcDiscoveryPage";
|
||||
import SamlCallback from './auth/SamlCallback';
|
||||
import SamlCallback from "./auth/SamlCallback";
|
||||
import CasLogout from "./auth/CasLogout";
|
||||
import ModelListPage from "./ModelListPage";
|
||||
import ModelEditPage from "./ModelEditPage";
|
||||
|
||||
const { Header, Footer } = Layout;
|
||||
const {Header, Footer} = Layout;
|
||||
|
||||
class App extends Component {
|
||||
constructor(props) {
|
||||
@ -108,44 +110,46 @@ class App extends Component {
|
||||
this.setState({
|
||||
uri: uri,
|
||||
});
|
||||
if (uri === '/') {
|
||||
this.setState({ selectedMenuKey: '/' });
|
||||
} else if (uri.includes('/organizations')) {
|
||||
this.setState({ selectedMenuKey: '/organizations' });
|
||||
} else if (uri.includes('/users')) {
|
||||
this.setState({ selectedMenuKey: '/users' });
|
||||
} else if (uri.includes('/roles')) {
|
||||
this.setState({ selectedMenuKey: '/roles' });
|
||||
} else if (uri.includes('/permissions')) {
|
||||
this.setState({ selectedMenuKey: '/permissions' });
|
||||
} else if (uri.includes('/providers')) {
|
||||
this.setState({ selectedMenuKey: '/providers' });
|
||||
} else if (uri.includes('/applications')) {
|
||||
this.setState({ selectedMenuKey: '/applications' });
|
||||
} else if (uri.includes('/resources')) {
|
||||
this.setState({ selectedMenuKey: '/resources' });
|
||||
} else if (uri.includes('/tokens')) {
|
||||
this.setState({ selectedMenuKey: '/tokens' });
|
||||
} else if (uri.includes('/records')) {
|
||||
this.setState({ selectedMenuKey: '/records' });
|
||||
} else if (uri.includes('/webhooks')) {
|
||||
this.setState({ selectedMenuKey: '/webhooks' });
|
||||
} else if (uri.includes('/syncers')) {
|
||||
this.setState({ selectedMenuKey: '/syncers' });
|
||||
} else if (uri.includes('/certs')) {
|
||||
this.setState({ selectedMenuKey: '/certs' });
|
||||
} else if (uri.includes('/products')) {
|
||||
this.setState({ selectedMenuKey: '/products' });
|
||||
} else if (uri.includes('/payments')) {
|
||||
this.setState({ selectedMenuKey: '/payments' });
|
||||
} else if (uri.includes('/signup')) {
|
||||
this.setState({ selectedMenuKey: '/signup' });
|
||||
} else if (uri.includes('/login')) {
|
||||
this.setState({ selectedMenuKey: '/login' });
|
||||
} else if (uri.includes('/result')) {
|
||||
this.setState({ selectedMenuKey: '/result' });
|
||||
if (uri === "/") {
|
||||
this.setState({selectedMenuKey: "/"});
|
||||
} else if (uri.includes("/organizations")) {
|
||||
this.setState({selectedMenuKey: "/organizations"});
|
||||
} else if (uri.includes("/users")) {
|
||||
this.setState({selectedMenuKey: "/users"});
|
||||
} else if (uri.includes("/roles")) {
|
||||
this.setState({selectedMenuKey: "/roles"});
|
||||
} else if (uri.includes("/permissions")) {
|
||||
this.setState({selectedMenuKey: "/permissions"});
|
||||
} else if (uri.includes("/models")) {
|
||||
this.setState({selectedMenuKey: "/models"});
|
||||
} else if (uri.includes("/providers")) {
|
||||
this.setState({selectedMenuKey: "/providers"});
|
||||
} else if (uri.includes("/applications")) {
|
||||
this.setState({selectedMenuKey: "/applications"});
|
||||
} else if (uri.includes("/resources")) {
|
||||
this.setState({selectedMenuKey: "/resources"});
|
||||
} else if (uri.includes("/tokens")) {
|
||||
this.setState({selectedMenuKey: "/tokens"});
|
||||
} else if (uri.includes("/records")) {
|
||||
this.setState({selectedMenuKey: "/records"});
|
||||
} else if (uri.includes("/webhooks")) {
|
||||
this.setState({selectedMenuKey: "/webhooks"});
|
||||
} else if (uri.includes("/syncers")) {
|
||||
this.setState({selectedMenuKey: "/syncers"});
|
||||
} else if (uri.includes("/certs")) {
|
||||
this.setState({selectedMenuKey: "/certs"});
|
||||
} else if (uri.includes("/products")) {
|
||||
this.setState({selectedMenuKey: "/products"});
|
||||
} else if (uri.includes("/payments")) {
|
||||
this.setState({selectedMenuKey: "/payments"});
|
||||
} else if (uri.includes("/signup")) {
|
||||
this.setState({selectedMenuKey: "/signup"});
|
||||
} else if (uri.includes("/login")) {
|
||||
this.setState({selectedMenuKey: "/login"});
|
||||
} else if (uri.includes("/result")) {
|
||||
this.setState({selectedMenuKey: "/result"});
|
||||
} else {
|
||||
this.setState({ selectedMenuKey: -1 });
|
||||
this.setState({selectedMenuKey: -1});
|
||||
}
|
||||
}
|
||||
|
||||
@ -230,12 +234,12 @@ class App extends Component {
|
||||
|
||||
AuthBackend.logout()
|
||||
.then((res) => {
|
||||
if (res.status === 'ok') {
|
||||
if (res.status === "ok") {
|
||||
this.setState({
|
||||
account: null
|
||||
});
|
||||
|
||||
Setting.showMessage("success", `Logged out successfully`);
|
||||
Setting.showMessage("success", "Logged out successfully");
|
||||
let redirectUri = res.data2;
|
||||
if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") {
|
||||
Setting.goToLink(redirectUri);
|
||||
@ -255,9 +259,9 @@ class App extends Component {
|
||||
}
|
||||
|
||||
handleRightDropdownClick(e) {
|
||||
if (e.key === '/account') {
|
||||
this.props.history.push(`/account`);
|
||||
} else if (e.key === '/logout') {
|
||||
if (e.key === "/account") {
|
||||
this.props.history.push("/account");
|
||||
} else if (e.key === "/logout") {
|
||||
this.logout();
|
||||
}
|
||||
}
|
||||
@ -265,16 +269,16 @@ class App extends Component {
|
||||
renderAvatar() {
|
||||
if (this.state.account.avatar === "") {
|
||||
return (
|
||||
<Avatar style={{ backgroundColor: Setting.getAvatarColor(this.state.account.name), verticalAlign: 'middle' }} size="large">
|
||||
<Avatar style={{backgroundColor: Setting.getAvatarColor(this.state.account.name), verticalAlign: "middle"}} size="large">
|
||||
{Setting.getShortName(this.state.account.name)}
|
||||
</Avatar>
|
||||
)
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Avatar src={this.state.account.avatar} style={{verticalAlign: 'middle' }} size="large">
|
||||
<Avatar src={this.state.account.avatar} style={{verticalAlign: "middle"}} size="large">
|
||||
{Setting.getShortName(this.state.account.name)}
|
||||
</Avatar>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -294,7 +298,7 @@ class App extends Component {
|
||||
|
||||
return (
|
||||
<Dropdown key="/rightDropDown" overlay={menu} className="rightDropDown">
|
||||
<div className="ant-dropdown-link" style={{float: 'right', cursor: 'pointer'}}>
|
||||
<div className="ant-dropdown-link" style={{float: "right", cursor: "pointer"}}>
|
||||
|
||||
|
||||
{
|
||||
@ -308,7 +312,7 @@ class App extends Component {
|
||||
|
||||
</div>
|
||||
</Dropdown>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
renderAccount() {
|
||||
@ -382,6 +386,13 @@ class App extends Component {
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
res.push(
|
||||
<Menu.Item key="/models">
|
||||
<Link to="/models">
|
||||
{i18next.t("general:Models")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
res.push(
|
||||
<Menu.Item key="/providers">
|
||||
<Link to="/providers">
|
||||
@ -473,7 +484,7 @@ class App extends Component {
|
||||
|
||||
renderHomeIfLoggedIn(component) {
|
||||
if (this.state.account !== null && this.state.account !== undefined) {
|
||||
return <Redirect to='/' />
|
||||
return <Redirect to="/" />;
|
||||
} else {
|
||||
return component;
|
||||
}
|
||||
@ -482,75 +493,114 @@ class App extends Component {
|
||||
renderLoginIfNotLoggedIn(component) {
|
||||
if (this.state.account === null) {
|
||||
sessionStorage.setItem("from", window.location.pathname);
|
||||
return <Redirect to='/login' />
|
||||
return <Redirect to="/login" />;
|
||||
} else if (this.state.account === undefined) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return component;
|
||||
}
|
||||
}
|
||||
|
||||
isStartPages() {
|
||||
return window.location.pathname.startsWith('/login') ||
|
||||
window.location.pathname.startsWith('/signup') ||
|
||||
window.location.pathname === '/';
|
||||
return window.location.pathname.startsWith("/login") ||
|
||||
window.location.pathname.startsWith("/signup") ||
|
||||
window.location.pathname === "/";
|
||||
}
|
||||
|
||||
renderRouter(){
|
||||
renderRouter() {
|
||||
return(
|
||||
<div>
|
||||
<Switch>
|
||||
<Route exact path="/result" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)}/>
|
||||
<Route exact path="/result/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)}/>
|
||||
<Route exact path="/" render={(props) => this.renderLoginIfNotLoggedIn(<HomePage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/account" render={(props) => this.renderLoginIfNotLoggedIn(<AccountPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/organizations" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationListPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/organizations/:organizationName" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationEditPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/organizations/:organizationName/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/users/:organizationName/:userName" render={(props) => <UserEditPage account={this.state.account} {...props} />}/>
|
||||
<Route exact path="/roles" render={(props) => this.renderLoginIfNotLoggedIn(<RoleListPage 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/:organizationName/:permissionName" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionEditPage 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="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/applications/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationEditPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/resources" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceListPage account={this.state.account} {...props} />)}/>
|
||||
{/*<Route exact path="/resources/:resourceName" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceEditPage account={this.state.account} {...props} />)}/>*/}
|
||||
<Route exact path="/ldap/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapEditPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/ldap/sync/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapSyncPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/tokens/:tokenName" render={(props) => this.renderLoginIfNotLoggedIn(<TokenEditPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/webhooks" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookListPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/webhooks/:webhookName" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookEditPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/syncers" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerListPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/certs" render={(props) => this.renderLoginIfNotLoggedIn(<CertListPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/certs/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/products/:productName" render={(props) => this.renderLoginIfNotLoggedIn(<ProductEditPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/products/:productName/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/payments" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentListPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/payments/:paymentName" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentEditPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/payments/:paymentName/result" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentResultPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />}/>
|
||||
<Route exact path="/result" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} />
|
||||
<Route exact path="/result/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} />
|
||||
<Route exact path="/" render={(props) => this.renderLoginIfNotLoggedIn(<HomePage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/account" render={(props) => this.renderLoginIfNotLoggedIn(<AccountPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/organizations" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/organizations/:organizationName" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/organizations/:organizationName/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/users/:organizationName/:userName" render={(props) => <UserEditPage account={this.state.account} {...props} />} />
|
||||
<Route exact path="/roles" render={(props) => this.renderLoginIfNotLoggedIn(<RoleListPage 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/: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/: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/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/resources" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceListPage account={this.state.account} {...props} />)} />
|
||||
{/* <Route exact path="/resources/:resourceName" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceEditPage account={this.state.account} {...props} />)}/>*/}
|
||||
<Route exact path="/ldap/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/ldap/sync/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapSyncPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/tokens/:tokenName" render={(props) => this.renderLoginIfNotLoggedIn(<TokenEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/webhooks" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/webhooks/:webhookName" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/syncers" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/certs" render={(props) => this.renderLoginIfNotLoggedIn(<CertListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/certs/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/products/:productName" render={(props) => this.renderLoginIfNotLoggedIn(<ProductEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/products/:productName/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/payments" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/payments/:paymentName" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/payments/:paymentName/result" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentResultPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />} />
|
||||
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
|
||||
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
|
||||
</Switch>
|
||||
</div>
|
||||
)
|
||||
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderContent() {
|
||||
if (!Setting.isMobile()) {
|
||||
return (
|
||||
<div style={{display: 'flex', flex: 'auto',width:"100%",flexDirection: 'column'}}>
|
||||
<Layout style={{display: 'flex', alignItems: 'stretch'}}>
|
||||
<Header style={{ padding: '0', marginBottom: '3px'}}>
|
||||
<div style={{display: "flex", flex: "auto", width:"100%", flexDirection: "column"}}>
|
||||
<Layout style={{display: "flex", alignItems: "stretch"}}>
|
||||
<Header style={{padding: "0", marginBottom: "3px"}}>
|
||||
{
|
||||
Setting.isMobile() ? null : (
|
||||
<Link to={"/"}>
|
||||
<div className="logo" />
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
<div>
|
||||
<Menu
|
||||
// theme="dark"
|
||||
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
|
||||
selectedKeys={[`${this.state.selectedMenuKey}`]}
|
||||
style={{lineHeight: "64px", width: "80%", position: "absolute"}}
|
||||
>
|
||||
{
|
||||
this.renderMenu()
|
||||
}
|
||||
</Menu>
|
||||
{
|
||||
this.renderAccount()
|
||||
}
|
||||
<SelectLanguageBox />
|
||||
</div>
|
||||
</Header>
|
||||
<Layout style={{backgroundColor: "#f5f5f5", alignItems: "stretch"}}>
|
||||
<Card className="content-warp-card">
|
||||
{
|
||||
this.renderRouter()
|
||||
}
|
||||
</Card>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return(
|
||||
<div>
|
||||
<Header style={{padding: "0", marginBottom: "3px"}}>
|
||||
{
|
||||
Setting.isMobile() ? null : (
|
||||
<Link to={"/"}>
|
||||
@ -558,66 +608,28 @@ class App extends Component {
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
<div>
|
||||
<Menu
|
||||
// theme="dark"
|
||||
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
|
||||
selectedKeys={[`${this.state.selectedMenuKey}`]}
|
||||
style={{lineHeight: '64px', width: '80%', position: 'absolute'}}
|
||||
>
|
||||
{
|
||||
this.renderMenu()
|
||||
}
|
||||
</Menu>
|
||||
{
|
||||
this.renderAccount()
|
||||
}
|
||||
<SelectLanguageBox/>
|
||||
</div>
|
||||
</Header>
|
||||
<Layout style={{backgroundColor: "#f5f5f5", alignItems: 'stretch'}}>
|
||||
<Card className="content-warp-card">
|
||||
{
|
||||
this.renderRouter()
|
||||
}
|
||||
</Card>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return(
|
||||
<div>
|
||||
<Header style={{ padding: '0', marginBottom: '3px'}}>
|
||||
{
|
||||
Setting.isMobile() ? null : (
|
||||
<Link to={"/"}>
|
||||
<div className="logo" />
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
<Menu
|
||||
<Menu
|
||||
// theme="dark"
|
||||
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
|
||||
selectedKeys={[`${this.state.selectedMenuKey}`]}
|
||||
style={{ lineHeight: '64px' }}
|
||||
>
|
||||
{
|
||||
this.renderMenu()
|
||||
}
|
||||
<div style = {{float: 'right'}}>
|
||||
{
|
||||
this.renderAccount()
|
||||
}
|
||||
<SelectLanguageBox/>
|
||||
</div>
|
||||
</Menu>
|
||||
</Header>
|
||||
{
|
||||
this.renderRouter()
|
||||
}
|
||||
</div>
|
||||
)
|
||||
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
|
||||
selectedKeys={[`${this.state.selectedMenuKey}`]}
|
||||
style={{lineHeight: "64px"}}
|
||||
>
|
||||
{
|
||||
this.renderMenu()
|
||||
}
|
||||
<div style = {{float: "right"}}>
|
||||
{
|
||||
this.renderAccount()
|
||||
}
|
||||
<SelectLanguageBox />
|
||||
</div>
|
||||
</Menu>
|
||||
</Header>
|
||||
{
|
||||
this.renderRouter()
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -628,14 +640,14 @@ class App extends Component {
|
||||
return (
|
||||
<Footer id="footer" style={
|
||||
{
|
||||
borderTop: '1px solid #e8e8e8',
|
||||
backgroundColor: 'white',
|
||||
textAlign: 'center',
|
||||
borderTop: "1px solid #e8e8e8",
|
||||
backgroundColor: "white",
|
||||
textAlign: "center",
|
||||
}
|
||||
}>
|
||||
Made with <span style={{color: 'rgb(255, 255, 255)'}}>❤️</span> by <a style={{fontWeight: "bold", color: "black"}} target="_blank" href="https://casdoor.org" rel="noreferrer">Casdoor</a>
|
||||
Made with <span style={{color: "rgb(255, 255, 255)"}}>❤️</span> by <a style={{fontWeight: "bold", color: "black"}} target="_blank" href="https://casdoor.org" rel="noreferrer">Casdoor</a>
|
||||
</Footer>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
isDoorPages() {
|
||||
@ -650,25 +662,30 @@ class App extends Component {
|
||||
renderPage() {
|
||||
if (this.isDoorPages()) {
|
||||
return (
|
||||
<Switch>
|
||||
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account)}} />)}/>
|
||||
<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="/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/login" render={(props) => {return (<LoginPage type={"cas"} mode={"signup"} account={this.state.account} {...props} />)}} />
|
||||
<Route exact path="/callback" component={AuthCallback}/>
|
||||
<Route exact path="/callback/saml" component={SamlCallback}/>
|
||||
<Route exact path="/forget" render={(props) => this.renderHomeIfLoggedIn(<SelfForgetPage {...props} />)}/>
|
||||
<Route exact path="/forget/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ForgetPage {...props} />)}/>
|
||||
<Route exact path="/prompt" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/prompt/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage account={this.state.account} onUpdateAccount={(account) => {this.onUpdateAccount(account)}} {...props} />)}/>
|
||||
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
|
||||
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>}/>} />
|
||||
</Switch>
|
||||
)
|
||||
<div>
|
||||
<Switch>
|
||||
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />)} />
|
||||
<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="/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/login" render={(props) => {return (<LoginPage type={"cas"} mode={"signup"} account={this.state.account} {...props} />);}} />
|
||||
<Route exact path="/callback" component={AuthCallback} />
|
||||
<Route exact path="/callback/saml" component={SamlCallback} />
|
||||
<Route exact path="/forget" render={(props) => this.renderHomeIfLoggedIn(<SelfForgetPage {...props} />)} />
|
||||
<Route exact path="/forget/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ForgetPage {...props} />)} />
|
||||
<Route exact path="/prompt" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/prompt/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage account={this.state.account} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} {...props} />)} />
|
||||
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
|
||||
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
|
||||
</Switch>
|
||||
{
|
||||
this.renderFooter()
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@ -698,7 +715,7 @@ class App extends Component {
|
||||
this.renderPage()
|
||||
}
|
||||
</React.Fragment>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const organization = this.state.account.organization;
|
||||
@ -712,7 +729,7 @@ class App extends Component {
|
||||
this.renderPage()
|
||||
}
|
||||
</React.Fragment>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,12 +12,14 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import App from './App';
|
||||
import React from "react";
|
||||
import {render} from "@testing-library/react";
|
||||
import App from "./App";
|
||||
|
||||
test('renders learn react link', () => {
|
||||
const { getByText } = render(<App />);
|
||||
// eslint-disable-next-line no-undef
|
||||
test("renders learn react link", () => {
|
||||
const {getByText} = render(<App />);
|
||||
const linkElement = getByText(/learn react/i);
|
||||
// eslint-disable-next-line no-undef
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
||||
|
@ -13,8 +13,8 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Card, Col, Input, Popover, Row, Select, Switch, Upload} from 'antd';
|
||||
import {LinkOutlined, UploadOutlined} from "@ant-design/icons";
|
||||
import {Button, Card, Col, Input, Popover, Row, Select, Switch, Upload} from "antd";
|
||||
import {CopyOutlined, LinkOutlined, UploadOutlined} from "@ant-design/icons";
|
||||
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
||||
import * as CertBackend from "./backend/CertBackend";
|
||||
import * as Setting from "./Setting";
|
||||
@ -28,13 +28,15 @@ import UrlTable from "./UrlTable";
|
||||
import ProviderTable from "./ProviderTable";
|
||||
import SignupTable from "./SignupTable";
|
||||
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";
|
||||
require('codemirror/theme/material-darker.css');
|
||||
require("codemirror/theme/material-darker.css");
|
||||
require("codemirror/mode/htmlmixed/htmlmixed");
|
||||
require("codemirror/mode/xml/xml");
|
||||
|
||||
const { Option } = Select;
|
||||
const {Option} = Select;
|
||||
|
||||
class ApplicationEditPage extends React.Component {
|
||||
constructor(props) {
|
||||
@ -48,6 +50,7 @@ class ApplicationEditPage extends React.Component {
|
||||
providers: [],
|
||||
uploading: false,
|
||||
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||
samlMetadata: null,
|
||||
};
|
||||
}
|
||||
|
||||
@ -56,6 +59,7 @@ class ApplicationEditPage extends React.Component {
|
||||
this.getOrganizations();
|
||||
this.getCerts();
|
||||
this.getProviders();
|
||||
this.getSamlMetadata();
|
||||
}
|
||||
|
||||
getApplication() {
|
||||
@ -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) {
|
||||
if (["expireInHours", "refreshExpireInHours"].includes(key)) {
|
||||
value = Setting.myParseInt(value);
|
||||
@ -131,7 +144,7 @@ class ApplicationEditPage extends React.Component {
|
||||
}
|
||||
}).finally(() => {
|
||||
this.setState({uploading: false});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
renderApplication() {
|
||||
@ -140,262 +153,262 @@ class ApplicationEditPage extends React.Component {
|
||||
<div>
|
||||
{this.state.mode === "add" ? i18next.t("application:New Application") : i18next.t("application:Edit Application")}
|
||||
<Button onClick={() => this.submitApplicationEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||
<Button style={{marginLeft: '20px'}} type="primary" onClick={() => this.submitApplicationEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} onClick={() => this.deleteApplication()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitApplicationEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteApplication()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||
</div>
|
||||
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner">
|
||||
<Row style={{marginTop: '10px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
|
||||
<Row style={{marginTop: "10px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.application.name} disabled={this.state.application.name === "app-built-in"} onChange={e => {
|
||||
this.updateApplicationField('name', e.target.value);
|
||||
this.updateApplicationField("name", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.application.displayName} onChange={e => {
|
||||
this.updateApplicationField('displayName', e.target.value);
|
||||
this.updateApplicationField("displayName", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Logo"), i18next.t("general:Logo - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} style={(Setting.isMobile()) ? {maxWidth:'100%'} :{}}>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
|
||||
<Col span={22} style={(Setting.isMobile()) ? {maxWidth: "100%"} :{}}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
|
||||
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={23} >
|
||||
<Input prefix={<LinkOutlined/>} value={this.state.application.logo} onChange={e => {
|
||||
this.updateApplicationField('logo', e.target.value);
|
||||
<Input prefix={<LinkOutlined />} value={this.state.application.logo} onChange={e => {
|
||||
this.updateApplicationField("logo", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
|
||||
{i18next.t("general:Preview")}:
|
||||
</Col>
|
||||
<Col span={23} >
|
||||
<a target="_blank" rel="noreferrer" href={this.state.application.logo}>
|
||||
<img src={this.state.application.logo} alt={this.state.application.logo} height={90} style={{marginBottom: '20px'}}/>
|
||||
<img src={this.state.application.logo} alt={this.state.application.logo} height={90} style={{marginBottom: "20px"}} />
|
||||
</a>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Home"), i18next.t("general:Home - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input prefix={<LinkOutlined/>} value={this.state.application.homepageUrl} onChange={e => {
|
||||
this.updateApplicationField('homepageUrl', e.target.value);
|
||||
<Input prefix={<LinkOutlined />} value={this.state.application.homepageUrl} onChange={e => {
|
||||
this.updateApplicationField("homepageUrl", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Description"), i18next.t("general:Description - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.application.description} onChange={e => {
|
||||
this.updateApplicationField('description', e.target.value);
|
||||
this.updateApplicationField("description", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: '100%'}} value={this.state.application.organization} onChange={(value => {this.updateApplicationField('organization', value);})}>
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.application.organization} onChange={(value => {this.updateApplicationField("organization", value);})}>
|
||||
{
|
||||
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.application.clientId} onChange={e => {
|
||||
this.updateApplicationField('clientId', e.target.value);
|
||||
this.updateApplicationField("clientId", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.application.clientSecret} onChange={e => {
|
||||
this.updateApplicationField('clientSecret', e.target.value);
|
||||
this.updateApplicationField("clientSecret", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Cert"), i18next.t("general:Cert - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: '100%'}} value={this.state.application.cert} onChange={(value => {this.updateApplicationField('cert', value);})}>
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.application.cert} onChange={(value => {this.updateApplicationField("cert", value);})}>
|
||||
{
|
||||
this.state.certs.map((cert, index) => <Option key={index} value={cert.name}>{cert.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:Redirect URLs"), i18next.t("application:Redirect URLs - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<UrlTable
|
||||
title={i18next.t("application:Redirect URLs")}
|
||||
table={this.state.application.redirectUris}
|
||||
onUpdateTable={(value) => { this.updateApplicationField('redirectUris', value)}}
|
||||
onUpdateTable={(value) => {this.updateApplicationField("redirectUris", value);}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:Token format"), i18next.t("application:Token format - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: '100%'}} value={this.state.application.tokenFormat} onChange={(value => {this.updateApplicationField('tokenFormat', value);})}>
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.application.tokenFormat} onChange={(value => {this.updateApplicationField("tokenFormat", value);})}>
|
||||
{
|
||||
['JWT', 'JWT-Empty']
|
||||
["JWT", "JWT-Empty"]
|
||||
.map((item, index) => <Option key={index} value={item}>{item}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:Token expire"), i18next.t("application:Token expire - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input style={{width: "150px"}} value={this.state.application.expireInHours} suffix="Hours" onChange={e => {
|
||||
this.updateApplicationField('expireInHours', e.target.value);
|
||||
this.updateApplicationField("expireInHours", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:Refresh token expire"), i18next.t("application:Refresh token expire - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input style={{width: "150px"}} value={this.state.application.refreshExpireInHours} suffix="Hours" onChange={e => {
|
||||
this.updateApplicationField('refreshExpireInHours', e.target.value);
|
||||
this.updateApplicationField("refreshExpireInHours", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:Password ON"), i18next.t("application:Password ON - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={1} >
|
||||
<Switch checked={this.state.application.enablePassword} onChange={checked => {
|
||||
this.updateApplicationField('enablePassword', checked);
|
||||
this.updateApplicationField("enablePassword", checked);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:Enable signup"), i18next.t("application:Enable signup - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={1} >
|
||||
<Switch checked={this.state.application.enableSignUp} onChange={checked => {
|
||||
this.updateApplicationField('enableSignUp', checked);
|
||||
this.updateApplicationField("enableSignUp", checked);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:Signin session"), i18next.t("application:Enable signin session - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={1} >
|
||||
<Switch checked={this.state.application.enableSigninSession} onChange={checked => {
|
||||
this.updateApplicationField('enableSigninSession', checked);
|
||||
this.updateApplicationField("enableSigninSession", checked);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:Enable code signin"), i18next.t("application:Enable code signin - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={1} >
|
||||
<Switch checked={this.state.application.enableCodeSignin} onChange={checked => {
|
||||
this.updateApplicationField('enableCodeSignin', checked);
|
||||
this.updateApplicationField("enableCodeSignin", checked);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Signup URL"), i18next.t("general:Signup URL - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input prefix={<LinkOutlined/>} value={this.state.application.signupUrl} onChange={e => {
|
||||
this.updateApplicationField('signupUrl', e.target.value);
|
||||
<Input prefix={<LinkOutlined />} value={this.state.application.signupUrl} onChange={e => {
|
||||
this.updateApplicationField("signupUrl", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Signin URL"), i18next.t("general:Signin URL - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input prefix={<LinkOutlined/>} value={this.state.application.signinUrl} onChange={e => {
|
||||
this.updateApplicationField('signinUrl', e.target.value);
|
||||
<Input prefix={<LinkOutlined />} value={this.state.application.signinUrl} onChange={e => {
|
||||
this.updateApplicationField("signinUrl", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Forget URL"), i18next.t("general:Forget URL - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input prefix={<LinkOutlined/>} value={this.state.application.forgetUrl} onChange={e => {
|
||||
this.updateApplicationField('forgetUrl', e.target.value);
|
||||
<Input prefix={<LinkOutlined />} value={this.state.application.forgetUrl} onChange={e => {
|
||||
this.updateApplicationField("forgetUrl", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Affiliation URL"), i18next.t("general:Affiliation URL - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input prefix={<LinkOutlined/>} value={this.state.application.affiliationUrl} onChange={e => {
|
||||
this.updateApplicationField('affiliationUrl', e.target.value);
|
||||
<Input prefix={<LinkOutlined />} value={this.state.application.affiliationUrl} onChange={e => {
|
||||
this.updateApplicationField("affiliationUrl", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Terms of Use"), i18next.t("provider:Terms of Use - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.application.termsOfUse} style={{marginBottom: "10px"}} onChange={e => {
|
||||
this.updateApplicationField("termsOfUse", e.target.value);
|
||||
}}/>
|
||||
}} />
|
||||
<Upload maxCount={1} accept=".html" showUploadList={false}
|
||||
beforeUpload={file => {return false}} onChange={info => {this.handleUpload(info)}}>
|
||||
<Button icon={<UploadOutlined />} loading={this.state.uploading}>Click to Upload</Button>
|
||||
beforeUpload={file => {return false;}} onChange={info => {this.handleUpload(info);}}>
|
||||
<Button icon={<UploadOutlined />} loading={this.state.uploading}>{i18next.t("general:Click to Upload")}</Button>
|
||||
</Upload>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Signup HTML"), i18next.t("provider:Signup HTML - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
@ -403,7 +416,7 @@ class ApplicationEditPage extends React.Component {
|
||||
<div style={{width: "900px", height: "300px"}} >
|
||||
<CodeMirror
|
||||
value={this.state.application.signupHtml}
|
||||
options={{mode: 'htmlmixed', theme: "material-darker"}}
|
||||
options={{mode: "htmlmixed", theme: "material-darker"}}
|
||||
onBeforeChange={(editor, data, value) => {
|
||||
this.updateApplicationField("signupHtml", value);
|
||||
}}
|
||||
@ -411,13 +424,13 @@ class ApplicationEditPage extends React.Component {
|
||||
</div>
|
||||
} title={i18next.t("provider:Signup HTML - Edit")} trigger="click">
|
||||
<Input value={this.state.application.signupHtml} style={{marginBottom: "10px"}} onChange={e => {
|
||||
this.updateApplicationField("signupHtml", e.target.value)
|
||||
}}/>
|
||||
this.updateApplicationField("signupHtml", e.target.value);
|
||||
}} />
|
||||
</Popover>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Signin HTML"), i18next.t("provider:Signin HTML - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
@ -425,7 +438,7 @@ class ApplicationEditPage extends React.Component {
|
||||
<div style={{width: "900px", height: "300px"}} >
|
||||
<CodeMirror
|
||||
value={this.state.application.signinHtml}
|
||||
options={{mode: 'htmlmixed', theme: "material-darker"}}
|
||||
options={{mode: "htmlmixed", theme: "material-darker"}}
|
||||
onBeforeChange={(editor, data, value) => {
|
||||
this.updateApplicationField("signinHtml", value);
|
||||
}}
|
||||
@ -433,35 +446,66 @@ class ApplicationEditPage extends React.Component {
|
||||
</div>
|
||||
} title={i18next.t("provider:Signin HTML - Edit")} trigger="click">
|
||||
<Input value={this.state.application.signinHtml} style={{marginBottom: "10px"}} onChange={e => {
|
||||
this.updateApplicationField("signinHtml", e.target.value)
|
||||
}}/>
|
||||
this.updateApplicationField("signinHtml", e.target.value);
|
||||
}} />
|
||||
</Popover>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:Grant types"), i18next.t("application:Grant types - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} mode="tags" style={{width: '100%'}}
|
||||
value={this.state.application.grantTypes}
|
||||
onChange={(value => {
|
||||
this.updateApplicationField('grantTypes', value);
|
||||
})} >
|
||||
{
|
||||
[
|
||||
{id: "authorization_code", name: "Authorization Code"},
|
||||
{id: "password", name: "Password"},
|
||||
{id: "client_credentials", name: "Client Credentials"},
|
||||
{id: "token", name: "Token"},
|
||||
{id: "id_token",name:"ID Token"},
|
||||
].map((item, index)=><Option key={index} value={item.id}>{item.name}</Option>)
|
||||
}
|
||||
<Select virtual={false} mode="tags" style={{width: "100%"}}
|
||||
value={this.state.application.grantTypes}
|
||||
onChange={(value => {
|
||||
this.updateApplicationField("grantTypes", value);
|
||||
})} >
|
||||
{
|
||||
[
|
||||
{id: "authorization_code", name: "Authorization Code"},
|
||||
{id: "password", name: "Password"},
|
||||
{id: "client_credentials", name: "Client Credentials"},
|
||||
{id: "token", name: "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>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<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"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Providers"), i18next.t("general:Providers - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
@ -470,62 +514,66 @@ class ApplicationEditPage extends React.Component {
|
||||
table={this.state.application.providers}
|
||||
providers={this.state.providers}
|
||||
application={this.state.application}
|
||||
onUpdateTable={(value) => { this.updateApplicationField('providers', value)}}
|
||||
onUpdateTable={(value) => {this.updateApplicationField("providers", value);}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
|
||||
</Col>
|
||||
{
|
||||
this.renderPreview()
|
||||
this.renderSignupSigninPreview()
|
||||
}
|
||||
</Row>
|
||||
{
|
||||
!this.state.application.enableSignUp ? null : (
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:Signup items"), i18next.t("application:Signup items - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<SignupTable
|
||||
title={i18next.t("application:Signup items")}
|
||||
table={this.state.application.signupItems}
|
||||
onUpdateTable={(value) => { this.updateApplicationField('signupItems', value)}}
|
||||
onUpdateTable={(value) => {this.updateApplicationField("signupItems", value);}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
|
||||
</Col>
|
||||
{
|
||||
this.renderPreview2()
|
||||
this.renderPromptPreview()
|
||||
}
|
||||
</Row>
|
||||
</Card>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
renderPreview() {
|
||||
renderSignupSigninPreview() {
|
||||
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 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) {
|
||||
signUpUrl = signInUrl.replace("/login/oauth/authorize", "/signup/oauth/authorize");
|
||||
}
|
||||
if (!Setting.isMobile()) {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Col span={11} 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>
|
||||
<br/>
|
||||
<br/>
|
||||
<div style={{width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems:"center", overflow:"auto", flexDirection:"column", flex: "auto"}}>
|
||||
<Col span={11}>
|
||||
<Button style={{marginBottom: "10px"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
|
||||
copy(`${window.location.origin}${signUpUrl}`);
|
||||
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 />
|
||||
<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"}}>
|
||||
{
|
||||
this.state.application.enablePassword ? (
|
||||
<SignupPage application={this.state.application} />
|
||||
@ -533,65 +581,46 @@ class ApplicationEditPage extends React.Component {
|
||||
<LoginPage type={"login"} mode={"signup"} application={this.state.application} />
|
||||
)
|
||||
}
|
||||
<div style={maskStyle}></div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={11} style={{display:"flex", flexDirection: "column"}}>
|
||||
<a style={{marginBottom: "10px", display: "flex"}} target="_blank" rel="noreferrer" href={signInUrl}>
|
||||
<Button type="primary">{i18next.t("application:Test signin page..")}</Button>
|
||||
</a>
|
||||
<br/>
|
||||
<br/>
|
||||
<div style={{width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems:"center", overflow:"auto", flexDirection:"column", flex: "auto"}}>
|
||||
<Col span={11}>
|
||||
<Button style={{marginBottom: "10px"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
|
||||
copy(`${window.location.origin}${signInUrl}`);
|
||||
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 />
|
||||
<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"}}>
|
||||
<LoginPage type={"login"} mode={"signin"} application={this.state.application} />
|
||||
<div style={maskStyle}></div>
|
||||
</div>
|
||||
</Col>
|
||||
</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 maskStyle = {position: "absolute", top: "0px", left: "0px", zIndex: 10, height: "100%", width: "100%", background: "rgba(0,0,0,0.4)"};
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Col span={(Setting.isMobile()) ? 24 : 11} style={{display:"flex", flexDirection: "column", flex: "auto"}} >
|
||||
<a style={{marginBottom: "10px"}} target="_blank" rel="noreferrer" href={promptUrl}>
|
||||
<Button type="primary">{i18next.t("application:Test prompt page..")}</Button>
|
||||
</a>
|
||||
<br style={(Setting.isMobile()) ? {display: "none"} : {}} />
|
||||
<br style={(Setting.isMobile()) ? {display: "none"} : {}} />
|
||||
<div style={{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} />
|
||||
</div>
|
||||
</Col>
|
||||
</React.Fragment>
|
||||
)
|
||||
<Col span={11}>
|
||||
<Button style={{marginBottom: "10px"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
|
||||
copy(`${window.location.origin}${promptUrl}`);
|
||||
Setting.showMessage("success", i18next.t("application:Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser"));
|
||||
}}
|
||||
>
|
||||
{i18next.t("application:Copy prompt page URL")}
|
||||
</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} />
|
||||
<div style={maskStyle}></div>
|
||||
</div>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
||||
submitApplicationEdit(willExist) {
|
||||
@ -599,19 +628,19 @@ class ApplicationEditPage extends React.Component {
|
||||
ApplicationBackend.updateApplication(this.state.application.owner, this.state.applicationName, application)
|
||||
.then((res) => {
|
||||
if (res.msg === "") {
|
||||
Setting.showMessage("success", `Successfully saved`);
|
||||
Setting.showMessage("success", "Successfully saved");
|
||||
this.setState({
|
||||
applicationName: this.state.application.name,
|
||||
});
|
||||
|
||||
if (willExist) {
|
||||
this.props.history.push(`/applications`);
|
||||
this.props.history.push("/applications");
|
||||
} else {
|
||||
this.props.history.push(`/applications/${this.state.application.name}`);
|
||||
}
|
||||
} else {
|
||||
Setting.showMessage("error", res.msg);
|
||||
this.updateApplicationField('name', this.state.applicationName);
|
||||
this.updateApplicationField("name", this.state.applicationName);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
@ -622,7 +651,7 @@ class ApplicationEditPage extends React.Component {
|
||||
deleteApplication() {
|
||||
ApplicationBackend.deleteApplication(this.state.application)
|
||||
.then(() => {
|
||||
this.props.history.push(`/applications`);
|
||||
this.props.history.push("/applications");
|
||||
})
|
||||
.catch(error => {
|
||||
Setting.showMessage("error", `Application failed to delete: ${error}`);
|
||||
@ -632,15 +661,15 @@ class ApplicationEditPage extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
this.state.application !== null ? this.renderApplication() : null
|
||||
}
|
||||
<div style={{marginTop: '20px', marginLeft: '40px'}}>
|
||||
<Button size="large" onClick={() => this.submitApplicationEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||
<Button style={{marginLeft: '20px'}} type="primary" size="large" onClick={() => this.submitApplicationEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} size="large" onClick={() => this.deleteApplication()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||
{
|
||||
this.state.application !== null ? this.renderApplication() : null
|
||||
}
|
||||
<div style={{marginTop: "20px", marginLeft: "40px"}}>
|
||||
<Button size="large" onClick={() => this.submitApplicationEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitApplicationEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteApplication()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
import React from "react";
|
||||
import {Link} from "react-router-dom";
|
||||
import {Button, Col, List, Popconfirm, Row, Table, Tooltip} from 'antd';
|
||||
import {Button, Col, List, Popconfirm, Row, Table, Tooltip} from "antd";
|
||||
import {EditOutlined} from "@ant-design/icons";
|
||||
import moment from "moment";
|
||||
import * as Setting from "./Setting";
|
||||
@ -23,7 +23,6 @@ import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
|
||||
class ApplicationListPage extends BaseListPage {
|
||||
|
||||
newApplication() {
|
||||
const randomName = Setting.getRandomName();
|
||||
return {
|
||||
@ -36,14 +35,17 @@ class ApplicationListPage extends BaseListPage {
|
||||
enableSignUp: true,
|
||||
enableSigninSession: false,
|
||||
enableCodeSignin: false,
|
||||
providers: [],
|
||||
enableSamlCompress: false,
|
||||
providers: [
|
||||
{name: "provider_captcha_default", canSignUp: false, canSignIn: false, canUnlink: false, prompted: false, alertType: "None"},
|
||||
],
|
||||
signupItems: [
|
||||
{name: "ID", visible: false, required: true, rule: "Random"},
|
||||
{name: "Username", visible: true, required: true, rule: "None"},
|
||||
{name: "Display name", visible: true, required: true, rule: "None"},
|
||||
{name: "Password", visible: true, required: true, rule: "None"},
|
||||
{name: "Confirm password", visible: true, required: true, rule: "None"},
|
||||
{name: "Email", visible: true, required: true, rule: "None"},
|
||||
{name: "Email", visible: true, required: true, rule: "Normal"},
|
||||
{name: "Phone", visible: true, required: true, rule: "None"},
|
||||
{name: "Agreement", visible: true, required: true, rule: "None"},
|
||||
],
|
||||
@ -51,15 +53,15 @@ class ApplicationListPage extends BaseListPage {
|
||||
redirectUris: ["http://localhost:9000/callback"],
|
||||
tokenFormat: "JWT",
|
||||
expireInHours: 24 * 7,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
addApplication() {
|
||||
const newApplication = this.newApplication();
|
||||
ApplicationBackend.addApplication(newApplication)
|
||||
.then((res) => {
|
||||
this.props.history.push({pathname: `/applications/${newApplication.name}`, mode: "add"});
|
||||
}
|
||||
this.props.history.push({pathname: `/applications/${newApplication.name}`, mode: "add"});
|
||||
}
|
||||
)
|
||||
.catch(error => {
|
||||
Setting.showMessage("error", `Application failed to add: ${error}`);
|
||||
@ -69,12 +71,12 @@ class ApplicationListPage extends BaseListPage {
|
||||
deleteApplication(i) {
|
||||
ApplicationBackend.deleteApplication(this.state.data[i])
|
||||
.then((res) => {
|
||||
Setting.showMessage("success", `Application deleted successfully`);
|
||||
this.setState({
|
||||
data: Setting.deleteRow(this.state.data, i),
|
||||
pagination: {total: this.state.pagination.total - 1},
|
||||
});
|
||||
}
|
||||
Setting.showMessage("success", "Application deleted successfully");
|
||||
this.setState({
|
||||
data: Setting.deleteRow(this.state.data, i),
|
||||
pagination: {total: this.state.pagination.total - 1},
|
||||
});
|
||||
}
|
||||
)
|
||||
.catch(error => {
|
||||
Setting.showMessage("error", `Application failed to delete: ${error}`);
|
||||
@ -85,25 +87,25 @@ class ApplicationListPage extends BaseListPage {
|
||||
const columns = [
|
||||
{
|
||||
title: i18next.t("general:Name"),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: '150px',
|
||||
fixed: 'left',
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
width: "150px",
|
||||
fixed: "left",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps('name'),
|
||||
...this.getColumnSearchProps("name"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/applications/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Created time"),
|
||||
dataIndex: 'createdTime',
|
||||
key: 'createdTime',
|
||||
width: '160px',
|
||||
dataIndex: "createdTime",
|
||||
key: "createdTime",
|
||||
width: "160px",
|
||||
sorter: true,
|
||||
render: (text, record, index) => {
|
||||
return Setting.getFormattedDate(text);
|
||||
@ -111,45 +113,45 @@ class ApplicationListPage extends BaseListPage {
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Display name"),
|
||||
dataIndex: 'displayName',
|
||||
key: 'displayName',
|
||||
dataIndex: "displayName",
|
||||
key: "displayName",
|
||||
// width: '100px',
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps('displayName'),
|
||||
...this.getColumnSearchProps("displayName"),
|
||||
},
|
||||
{
|
||||
title: 'Logo',
|
||||
dataIndex: 'logo',
|
||||
key: 'logo',
|
||||
width: '200px',
|
||||
title: "Logo",
|
||||
dataIndex: "logo",
|
||||
key: "logo",
|
||||
width: "200px",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<a target="_blank" rel="noreferrer" href={text}>
|
||||
<img src={text} alt={text} width={150} />
|
||||
</a>
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Organization"),
|
||||
dataIndex: 'organization',
|
||||
key: 'organization',
|
||||
width: '150px',
|
||||
dataIndex: "organization",
|
||||
key: "organization",
|
||||
width: "150px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps('organization'),
|
||||
...this.getColumnSearchProps("organization"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/organizations/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Providers"),
|
||||
dataIndex: 'providers',
|
||||
key: 'providers',
|
||||
...this.getColumnSearchProps('providers'),
|
||||
dataIndex: "providers",
|
||||
key: "providers",
|
||||
...this.getColumnSearchProps("providers"),
|
||||
// width: '600px',
|
||||
render: (text, record, index) => {
|
||||
const providers = text;
|
||||
@ -177,11 +179,11 @@ class ApplicationListPage extends BaseListPage {
|
||||
</Link>
|
||||
</div>
|
||||
</List.Item>
|
||||
)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
@ -198,28 +200,28 @@ class ApplicationListPage extends BaseListPage {
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Action"),
|
||||
dataIndex: '',
|
||||
key: 'op',
|
||||
width: '170px',
|
||||
dataIndex: "",
|
||||
key: "op",
|
||||
width: "170px",
|
||||
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<div>
|
||||
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/applications/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/applications/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||
<Popconfirm
|
||||
title={`Sure to delete application: ${record.name} ?`}
|
||||
onConfirm={() => this.deleteApplication(index)}
|
||||
disabled={record.name === "app-built-in"}
|
||||
>
|
||||
<Button style={{marginBottom: '10px'}} disabled={record.name === "app-built-in"} type="danger">{i18next.t("general:Delete")}</Button>
|
||||
<Button style={{marginBottom: "10px"}} disabled={record.name === "app-built-in"} type="danger">{i18next.t("general:Delete")}</Button>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
];
|
||||
@ -233,15 +235,15 @@ class ApplicationListPage extends BaseListPage {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table scroll={{x: 'max-content'}} columns={columns} dataSource={applications} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||
title={() => (
|
||||
<div>
|
||||
{i18next.t("general:Applications")}
|
||||
<Button type="primary" size="small" onClick={this.addApplication.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||
</div>
|
||||
)}
|
||||
loading={this.state.loading}
|
||||
onChange={this.handleTableChange}
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={applications} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||
title={() => (
|
||||
<div>
|
||||
{i18next.t("general:Applications")}
|
||||
<Button type="primary" size="small" onClick={this.addApplication.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||
</div>
|
||||
)}
|
||||
loading={this.state.loading}
|
||||
onChange={this.handleTableChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@ -250,7 +252,7 @@ class ApplicationListPage extends BaseListPage {
|
||||
fetch = (params = {}) => {
|
||||
let field = params.searchedColumn, value = params.searchText;
|
||||
let sortField = params.sortField, sortOrder = params.sortOrder;
|
||||
this.setState({ loading: true });
|
||||
this.setState({loading: true});
|
||||
ApplicationBackend.getApplications("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
|
@ -18,121 +18,121 @@ import {SearchOutlined} from "@ant-design/icons";
|
||||
import Highlighter from "react-highlight-words";
|
||||
|
||||
class BaseListPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
data: [],
|
||||
pagination: {
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
},
|
||||
loading: false,
|
||||
searchText: '',
|
||||
searchedColumn: '',
|
||||
};
|
||||
}
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
data: [],
|
||||
pagination: {
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
},
|
||||
loading: false,
|
||||
searchText: "",
|
||||
searchedColumn: "",
|
||||
};
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
const { pagination } = this.state;
|
||||
this.fetch({ pagination });
|
||||
}
|
||||
UNSAFE_componentWillMount() {
|
||||
const {pagination} = this.state;
|
||||
this.fetch({pagination});
|
||||
}
|
||||
|
||||
getColumnSearchProps = dataIndex => ({
|
||||
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => (
|
||||
<div style={{ padding: 8 }}>
|
||||
<Input
|
||||
ref={node => {
|
||||
this.searchInput = node;
|
||||
}}
|
||||
placeholder={`Search ${dataIndex}`}
|
||||
value={selectedKeys[0]}
|
||||
onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
|
||||
onPressEnter={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
|
||||
style={{ marginBottom: 8, display: 'block' }}
|
||||
/>
|
||||
<Space>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
|
||||
icon={<SearchOutlined />}
|
||||
size="small"
|
||||
style={{ width: 90 }}
|
||||
>
|
||||
Search
|
||||
</Button>
|
||||
<Button onClick={() => this.handleReset(clearFilters)} size="small" style={{ width: 90 }}>
|
||||
Reset
|
||||
</Button>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
confirm({ closeDropdown: false });
|
||||
this.setState({
|
||||
searchText: selectedKeys[0],
|
||||
searchedColumn: dataIndex,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Filter
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
),
|
||||
filterIcon: filtered => <SearchOutlined style={{ color: filtered ? '#1890ff' : undefined }} />,
|
||||
onFilter: (value, record) =>
|
||||
record[dataIndex]
|
||||
? record[dataIndex].toString().toLowerCase().includes(value.toLowerCase())
|
||||
: '',
|
||||
onFilterDropdownVisibleChange: visible => {
|
||||
if (visible) {
|
||||
setTimeout(() => this.searchInput.select(), 100);
|
||||
}
|
||||
},
|
||||
render: text =>
|
||||
this.state.searchedColumn === dataIndex ? (
|
||||
<Highlighter
|
||||
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
|
||||
searchWords={[this.state.searchText]}
|
||||
autoEscape
|
||||
textToHighlight={text ? text.toString() : ''}
|
||||
/>
|
||||
) : (
|
||||
text
|
||||
),
|
||||
});
|
||||
getColumnSearchProps = dataIndex => ({
|
||||
filterDropdown: ({setSelectedKeys, selectedKeys, confirm, clearFilters}) => (
|
||||
<div style={{padding: 8}}>
|
||||
<Input
|
||||
ref={node => {
|
||||
this.searchInput = node;
|
||||
}}
|
||||
placeholder={`Search ${dataIndex}`}
|
||||
value={selectedKeys[0]}
|
||||
onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
|
||||
onPressEnter={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
|
||||
style={{marginBottom: 8, display: "block"}}
|
||||
/>
|
||||
<Space>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
|
||||
icon={<SearchOutlined />}
|
||||
size="small"
|
||||
style={{width: 90}}
|
||||
>
|
||||
Search
|
||||
</Button>
|
||||
<Button onClick={() => this.handleReset(clearFilters)} size="small" style={{width: 90}}>
|
||||
Reset
|
||||
</Button>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
confirm({closeDropdown: false});
|
||||
this.setState({
|
||||
searchText: selectedKeys[0],
|
||||
searchedColumn: dataIndex,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Filter
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
),
|
||||
filterIcon: filtered => <SearchOutlined style={{color: filtered ? "#1890ff" : undefined}} />,
|
||||
onFilter: (value, record) =>
|
||||
record[dataIndex]
|
||||
? record[dataIndex].toString().toLowerCase().includes(value.toLowerCase())
|
||||
: "",
|
||||
onFilterDropdownVisibleChange: visible => {
|
||||
if (visible) {
|
||||
setTimeout(() => this.searchInput.select(), 100);
|
||||
}
|
||||
},
|
||||
render: text =>
|
||||
this.state.searchedColumn === dataIndex ? (
|
||||
<Highlighter
|
||||
highlightStyle={{backgroundColor: "#ffc069", padding: 0}}
|
||||
searchWords={[this.state.searchText]}
|
||||
autoEscape
|
||||
textToHighlight={text ? text.toString() : ""}
|
||||
/>
|
||||
) : (
|
||||
text
|
||||
),
|
||||
});
|
||||
|
||||
handleSearch = (selectedKeys, confirm, dataIndex) => {
|
||||
this.fetch({searchText: selectedKeys[0], searchedColumn: dataIndex, pagination: this.state.pagination});
|
||||
};
|
||||
handleSearch = (selectedKeys, confirm, dataIndex) => {
|
||||
this.fetch({searchText: selectedKeys[0], searchedColumn: dataIndex, pagination: this.state.pagination});
|
||||
};
|
||||
|
||||
handleReset = clearFilters => {
|
||||
clearFilters();
|
||||
const { pagination } = this.state;
|
||||
this.fetch({ pagination });
|
||||
};
|
||||
handleReset = clearFilters => {
|
||||
clearFilters();
|
||||
const {pagination} = this.state;
|
||||
this.fetch({pagination});
|
||||
};
|
||||
|
||||
handleTableChange = (pagination, filters, sorter) => {
|
||||
this.fetch({
|
||||
sortField: sorter.field,
|
||||
sortOrder: sorter.order,
|
||||
pagination,
|
||||
...filters,
|
||||
searchText: this.state.searchText,
|
||||
searchedColumn: this.state.searchedColumn,
|
||||
});
|
||||
};
|
||||
handleTableChange = (pagination, filters, sorter) => {
|
||||
this.fetch({
|
||||
sortField: sorter.field,
|
||||
sortOrder: sorter.order,
|
||||
pagination,
|
||||
...filters,
|
||||
searchText: this.state.searchText,
|
||||
searchedColumn: this.state.searchedColumn,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
this.renderTable(this.state.data)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
this.renderTable(this.state.data)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default BaseListPage;
|
||||
export default BaseListPage;
|
||||
|
@ -13,15 +13,15 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Card, Col, Input, InputNumber, Row, Select} from 'antd';
|
||||
import {Button, Card, Col, Input, InputNumber, Row, Select} from "antd";
|
||||
import * as CertBackend from "./backend/CertBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import i18next from "i18next";
|
||||
import copy from "copy-to-clipboard";
|
||||
import FileSaver from "file-saver";
|
||||
|
||||
const { Option } = Select;
|
||||
const { TextArea } = Input;
|
||||
const {Option} = Select;
|
||||
const {TextArea} = Input;
|
||||
|
||||
class CertEditPage extends React.Component {
|
||||
constructor(props) {
|
||||
@ -70,104 +70,104 @@ class CertEditPage extends React.Component {
|
||||
<div>
|
||||
{this.state.mode === "add" ? i18next.t("cert:New Cert") : i18next.t("cert:Edit Cert")}
|
||||
<Button onClick={() => this.submitCertEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||
<Button style={{marginLeft: '20px'}} type="primary" onClick={() => this.submitCertEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} onClick={() => this.deleteCert()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitCertEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteCert()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||
</div>
|
||||
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner">
|
||||
<Row style={{marginTop: '10px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
|
||||
<Row style={{marginTop: "10px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.cert.name} onChange={e => {
|
||||
this.updateCertField('name', e.target.value);
|
||||
this.updateCertField("name", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.cert.displayName} onChange={e => {
|
||||
this.updateCertField('displayName', e.target.value);
|
||||
this.updateCertField("displayName", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("cert:Scope"), i18next.t("cert:Scope - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: '100%'}} value={this.state.cert.scope} onChange={(value => {
|
||||
this.updateCertField('scope', value);
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.cert.scope} onChange={(value => {
|
||||
this.updateCertField("scope", value);
|
||||
})}>
|
||||
{
|
||||
[
|
||||
{id: 'JWT', name: 'JWT'},
|
||||
{id: "JWT", name: "JWT"},
|
||||
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("cert:Type"), i18next.t("cert:Type - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: '100%'}} value={this.state.cert.type} onChange={(value => {
|
||||
this.updateCertField('type', value);
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.cert.type} onChange={(value => {
|
||||
this.updateCertField("type", value);
|
||||
})}>
|
||||
{
|
||||
[
|
||||
{id: 'x509', name: 'x509'},
|
||||
{id: "x509", name: "x509"},
|
||||
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("cert:Crypto algorithm"), i18next.t("cert:Crypto algorithm - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: '100%'}} value={this.state.cert.cryptoAlgorithm} onChange={(value => {
|
||||
this.updateCertField('cryptoAlgorithm', value);
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.cert.cryptoAlgorithm} onChange={(value => {
|
||||
this.updateCertField("cryptoAlgorithm", value);
|
||||
})}>
|
||||
{
|
||||
[
|
||||
{id: 'RSA', name: 'RSA'},
|
||||
{id: "RS256", name: "RS256"},
|
||||
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("cert:Bit size"), i18next.t("cert:Bit size - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<InputNumber value={this.state.cert.bitSize} onChange={value => {
|
||||
this.updateCertField('bitSize', value);
|
||||
this.updateCertField("bitSize", value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("cert:Expire in years"), i18next.t("cert:Expire in years - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<InputNumber value={this.state.cert.expireInYears} onChange={value => {
|
||||
this.updateCertField('expireInYears', value);
|
||||
this.updateCertField("expireInYears", value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("cert:Public key"), i18next.t("cert:Public key - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={9} >
|
||||
<Button style={{marginRight: '10px', marginBottom: '10px'}} onClick={() => {
|
||||
<Button style={{marginRight: "10px", marginBottom: "10px"}} onClick={() => {
|
||||
copy(this.state.cert.publicKey);
|
||||
Setting.showMessage("success", i18next.t("cert:Public key copied to clipboard successfully"));
|
||||
}}
|
||||
@ -182,15 +182,15 @@ class CertEditPage extends React.Component {
|
||||
{i18next.t("cert:Download public key")}
|
||||
</Button>
|
||||
<TextArea autoSize={{minRows: 30, maxRows: 30}} value={this.state.cert.publicKey} onChange={e => {
|
||||
this.updateCertField('publicKey', e.target.value);
|
||||
this.updateCertField("publicKey", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
<Col span={1} />
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("cert:Private key"), i18next.t("cert:Private key - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={9} >
|
||||
<Button style={{marginRight: '10px', marginBottom: '10px'}} onClick={() => {
|
||||
<Button style={{marginRight: "10px", marginBottom: "10px"}} onClick={() => {
|
||||
copy(this.state.cert.privateKey);
|
||||
Setting.showMessage("success", i18next.t("cert:Private key copied to clipboard successfully"));
|
||||
}}
|
||||
@ -205,12 +205,12 @@ class CertEditPage extends React.Component {
|
||||
{i18next.t("cert:Download private key")}
|
||||
</Button>
|
||||
<TextArea autoSize={{minRows: 30, maxRows: 30}} value={this.state.cert.privateKey} onChange={e => {
|
||||
this.updateCertField('privateKey', e.target.value);
|
||||
this.updateCertField("privateKey", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
submitCertEdit(willExist) {
|
||||
@ -218,19 +218,19 @@ class CertEditPage extends React.Component {
|
||||
CertBackend.updateCert(this.state.cert.owner, this.state.certName, cert)
|
||||
.then((res) => {
|
||||
if (res.msg === "") {
|
||||
Setting.showMessage("success", `Successfully saved`);
|
||||
Setting.showMessage("success", "Successfully saved");
|
||||
this.setState({
|
||||
certName: this.state.cert.name,
|
||||
});
|
||||
|
||||
if (willExist) {
|
||||
this.props.history.push(`/certs`);
|
||||
this.props.history.push("/certs");
|
||||
} else {
|
||||
this.props.history.push(`/certs/${this.state.cert.name}`);
|
||||
}
|
||||
} else {
|
||||
Setting.showMessage("error", res.msg);
|
||||
this.updateCertField('name', this.state.certName);
|
||||
this.updateCertField("name", this.state.certName);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
@ -241,7 +241,7 @@ class CertEditPage extends React.Component {
|
||||
deleteCert() {
|
||||
CertBackend.deleteCert(this.state.cert)
|
||||
.then(() => {
|
||||
this.props.history.push(`/certs`);
|
||||
this.props.history.push("/certs");
|
||||
})
|
||||
.catch(error => {
|
||||
Setting.showMessage("error", `Cert failed to delete: ${error}`);
|
||||
@ -254,10 +254,10 @@ class CertEditPage extends React.Component {
|
||||
{
|
||||
this.state.cert !== null ? this.renderCert() : null
|
||||
}
|
||||
<div style={{marginTop: '20px', marginLeft: '40px'}}>
|
||||
<div style={{marginTop: "20px", marginLeft: "40px"}}>
|
||||
<Button size="large" onClick={() => this.submitCertEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||
<Button style={{marginLeft: '20px'}} type="primary" size="large" onClick={() => this.submitCertEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} size="large" onClick={() => this.deleteCert()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitCertEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteCert()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user