mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-25 17:00:29 +08:00
Compare commits
82 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
376bac15dc | ||
![]() |
8d0e92edef | ||
![]() |
0075b7af52 | ||
![]() |
2c57bece39 | ||
![]() |
2e42511bc4 | ||
![]() |
ae4ab9902b | ||
![]() |
065b235dc5 | ||
![]() |
63c09a879f | ||
![]() |
61c80e790f | ||
![]() |
be91ff47aa | ||
![]() |
b4c18eb7a4 | ||
![]() |
0f483fb65b | ||
![]() |
ebe9889d58 | ||
![]() |
ee42fcac8e | ||
![]() |
6187b48f61 | ||
![]() |
2020955270 | ||
![]() |
1b5a8f8e57 | ||
![]() |
ff94e5164a | ||
![]() |
15a6fd2b52 | ||
![]() |
37b6b50751 | ||
![]() |
efe5431f54 | ||
![]() |
e9159902eb | ||
![]() |
604e2757c8 | ||
![]() |
88c5aae9e9 | ||
![]() |
3d0cf8788b | ||
![]() |
e78ea2546f | ||
![]() |
f7705931f7 | ||
![]() |
5d8b710bf7 | ||
![]() |
b85ad896bf | ||
![]() |
42c2210178 | ||
![]() |
d52caed3a9 | ||
![]() |
27d8cd758d | ||
![]() |
98f77960de | ||
![]() |
e5b71a08ae | ||
![]() |
3ad4b7a43c | ||
![]() |
c5c3a08aa9 | ||
![]() |
8efd964835 | ||
![]() |
5dac87a4c3 | ||
![]() |
49c3266400 | ||
![]() |
39548d5d72 | ||
![]() |
1c949e415e | ||
![]() |
1b840a2e9f | ||
![]() |
c9849d8b55 | ||
![]() |
b747f5e27c | ||
![]() |
8b340105c1 | ||
![]() |
43b1006f11 | ||
![]() |
78efc9c2d0 | ||
![]() |
c4089eacb7 | ||
![]() |
4acba2d493 | ||
![]() |
fc0ca4cceb | ||
![]() |
912d9d0c01 | ||
![]() |
8e48bddf5f | ||
![]() |
c05fb77224 | ||
![]() |
9af9ead939 | ||
![]() |
f5590c42f7 | ||
![]() |
5597f99e3c | ||
![]() |
ea005aaf4d | ||
![]() |
e5c1f560c5 | ||
![]() |
20fc7d1b58 | ||
![]() |
cf3b46130b | ||
![]() |
cab51fae9c | ||
![]() |
b867872da4 | ||
![]() |
305867f49a | ||
![]() |
3f90c18a19 | ||
![]() |
9e5a64c021 | ||
![]() |
4263af6f2c | ||
![]() |
3e92d761b9 | ||
![]() |
0e41568f62 | ||
![]() |
fb7e2729c6 | ||
![]() |
28b9154d7e | ||
![]() |
b0b3eb0805 | ||
![]() |
73bd9dd517 | ||
![]() |
0bc8c2d15f | ||
![]() |
7b78e60265 | ||
![]() |
7464f9a8ad | ||
![]() |
d3a7a062d3 | ||
![]() |
67a0264411 | ||
![]() |
a6a055cc83 | ||
![]() |
a89a7f9eb7 | ||
![]() |
287f60353c | ||
![]() |
530330bd66 | ||
![]() |
70a1428972 |
152
README.md
152
README.md
@@ -42,166 +42,66 @@
|
|||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Online demo
|
## Online demo
|
||||||
|
|
||||||
Deployed site: https://door.casdoor.com/
|
- International: https://door.casdoor.org (read-only)
|
||||||
|
- Asian mirror: https://door.casdoor.com (read-only)
|
||||||
|
- Asian mirror: https://demo.casdoor.com (read-write, will restore for every 5 minutes)
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
Run your own casdoor program in a few minutes.
|
|
||||||
|
|
||||||
### Download
|
|
||||||
|
|
||||||
There are two methods, get code via go subcommand `get`:
|
## Documentation
|
||||||
|
|
||||||
```shell
|
- International: https://casdoor.org
|
||||||
go get github.com/casdoor/casdoor
|
- Asian mirror: https://docs.casdoor.cn
|
||||||
```
|
|
||||||
|
|
||||||
or `git`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/casdoor/casdoor
|
|
||||||
```
|
|
||||||
|
|
||||||
Finally, change directory:
|
## Install
|
||||||
|
|
||||||
```bash
|
- By source code: https://casdoor.org/docs/basic/server-installation
|
||||||
cd casdoor/
|
- By Docker: https://casdoor.org/docs/basic/try-with-docker
|
||||||
```
|
|
||||||
|
|
||||||
We provide two start up methods for all kinds of users.
|
|
||||||
|
|
||||||
### Manual
|
|
||||||
|
|
||||||
#### Simple configuration
|
## How to connect to Casdoor?
|
||||||
Casdoor requires a running Relational database to be operational.Thus you need to modify configuration to point out the location of database.
|
|
||||||
|
|
||||||
Edit `conf/app.conf`, modify `dataSourceName` to correct database info, which follows this format:
|
https://casdoor.org/docs/how-to-connect/overview
|
||||||
|
|
||||||
```bash
|
|
||||||
username:password@tcp(database_ip:database_port)/
|
|
||||||
```
|
|
||||||
|
|
||||||
Then create an empty schema (database) named `casdoor` in your relational database. After the program runs for the first time, it will automatically create tables in this schema.
|
|
||||||
|
|
||||||
You can also edit `main.go`, modify `false` to `true`. It will automatically create the schema (database) named `casdoor` in this database.
|
## Casdoor Public API
|
||||||
|
|
||||||
```bash
|
- Docs: https://casdoor.org/docs/basic/public-api
|
||||||
createDatabase := flag.Bool("createDatabase", false, "true if you need casdoor to create database")
|
- Swagger: https://door.casdoor.com/swagger
|
||||||
```
|
|
||||||
|
|
||||||
#### Run
|
|
||||||
|
|
||||||
Casdoor provides two run modes, the difference is binary size and user prompt.
|
|
||||||
|
|
||||||
##### Dev Mode
|
## Integrations
|
||||||
|
|
||||||
Edit `conf/app.conf`, set `runmode=dev`. Firstly build front-end files:
|
https://casdoor.org/docs/integration/apisix
|
||||||
|
|
||||||
```bash
|
|
||||||
cd web/ && yarn && yarn run start
|
|
||||||
```
|
|
||||||
*❗ A word of caution ❗: Casdoor's front-end is built using yarn. You should use `yarn` instead of `npm`. It has a potential failure during building the files if you use `npm`.*
|
|
||||||
|
|
||||||
Then build back-end binary file, change directory to root(Relative to casdoor):
|
## How to contact?
|
||||||
|
|
||||||
```bash
|
- Gitter: https://gitter.im/casbin/casdoor
|
||||||
go run main.go
|
- Forum: https://forum.casbin.com
|
||||||
```
|
- Contact: https://tawk.to/chat/623352fea34c2456412b8c51/1fuc7od6e
|
||||||
|
|
||||||
That's it! Try to visit http://127.0.0.1:7001/. :small_airplane:
|
|
||||||
**But make sure you always request the backend port 8000 when you are using SDKs.**
|
|
||||||
|
|
||||||
##### Production Mode
|
|
||||||
|
|
||||||
Edit `conf/app.conf`, set `runmode=prod`. Firstly build front-end files:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd web/ && yarn && yarn run build
|
|
||||||
```
|
|
||||||
|
|
||||||
Then build back-end binary file, change directory to root(Relative to casdoor):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go build main.go && sudo ./main
|
|
||||||
```
|
|
||||||
|
|
||||||
> Notice, you should visit back-end port, default 8000. Now try to visit **http://SERVER_IP:8000/**
|
|
||||||
|
|
||||||
### Docker
|
|
||||||
|
|
||||||
Casdoor provide 2 kinds of image:
|
|
||||||
- casbin/casdoor-all-in-one, in which casdoor binary, a mysql database and all necessary configurations are packed up. This image is for new user to have a trial on casdoor quickly. **With this image you can start a casdoor immediately with one single command (or two) without any complex configuration**. **Note: we DO NOT recommend you to use this image in productive environment**
|
|
||||||
|
|
||||||
- casbin/casdoor: normal & graceful casdoor image with only casdoor and environment installed.
|
|
||||||
|
|
||||||
This method requires [docker](https://docs.docker.com/get-docker/) and [docker-compose](https://docs.docker.com/compose/install/) to be installed first.
|
|
||||||
|
|
||||||
### Start casdoor with casbin/casdoor-all-in-one
|
|
||||||
if the image is not pulled, pull it from dockerhub
|
|
||||||
```shell
|
|
||||||
docker pull casbin/casdoor-all-in-one
|
|
||||||
```
|
|
||||||
Start it with
|
|
||||||
```shell
|
|
||||||
docker run -p 8000:8000 casbin/casdoor-all-in-one
|
|
||||||
```
|
|
||||||
Now you can visit http://localhost:8000 and have a try. Default account and password is 'admin' and '123'. Go for it!
|
|
||||||
|
|
||||||
### Start casdoor with casbin/casdoor
|
|
||||||
#### modify the configurations
|
|
||||||
For the convenience of your first attempt, docker-compose.yml contains commands to start a database via docker.
|
|
||||||
|
|
||||||
Thus edit `conf/app.conf` to point out the location of database(db:3306), modify `dataSourceName` to the fixed content:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
dataSourceName = root:123456@tcp(db:3306)/
|
|
||||||
```
|
|
||||||
|
|
||||||
> If you need to modify `conf/app.conf`, you need to re-run `docker-compose up`.
|
|
||||||
|
|
||||||
#### Run
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker-compose up
|
|
||||||
```
|
|
||||||
|
|
||||||
### K8S
|
|
||||||
You could use helm to deploy casdoor in k8s. At first, you should modify the [configmap](./manifests/casdoor/templates/configmap.yaml) for your application.
|
|
||||||
And then run bellow command to deploy it.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
IMG_TAG=latest make deploy
|
|
||||||
```
|
|
||||||
|
|
||||||
And undeploy it with:
|
|
||||||
```bash
|
|
||||||
make undeploy
|
|
||||||
```
|
|
||||||
|
|
||||||
That's it! Try to visit http://localhost:8000/. :small_airplane:
|
|
||||||
|
|
||||||
## Detailed documentation
|
|
||||||
|
|
||||||
We also provide a complete [document](https://casdoor.org/) as a reference.
|
|
||||||
|
|
||||||
## Other examples
|
|
||||||
|
|
||||||
These all use casdoor as a centralized authentication platform.
|
|
||||||
|
|
||||||
- [Casnode](https://github.com/casbin/casnode): Next-generation forum software based on React + Golang.
|
|
||||||
- [Casbin-OA](https://github.com/casbin/casbin-oa): A full-featured OA(Office Assistant) system.
|
|
||||||
- ......
|
|
||||||
|
|
||||||
## Contribute
|
## Contribute
|
||||||
|
|
||||||
For casdoor, if you have any questions, you can give Issues, or you can also directly start Pull Requests(but we recommend giving issues first to communicate with the community).
|
For casdoor, if you have any questions, you can give Issues, or you can also directly start Pull Requests(but we recommend giving issues first to communicate with the community).
|
||||||
|
|
||||||
### I18n notice
|
### I18n translation
|
||||||
|
|
||||||
If you are contributing to casdoor, please note that we use [Crowdin](https://crowdin.com/project/casdoor-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.
|
If you are contributing to casdoor, please note that we use [Crowdin](https://crowdin.com/project/casdoor-web) as translating platform and i18next as translating tool. When you add some words using i18next in the ```web/``` directory, please remember to add what you have added to the ```web/src/locales/en/data.json``` file.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[Apache-2.0](https://github.com/casdoor/casdoor/blob/master/LICENSE)
|
[Apache-2.0](https://github.com/casdoor/casdoor/blob/master/LICENSE)
|
||||||
|
|
||||||
|
@@ -80,21 +80,23 @@ p, *, *, GET, /api/get-app-login, *, *
|
|||||||
p, *, *, POST, /api/logout, *, *
|
p, *, *, POST, /api/logout, *, *
|
||||||
p, *, *, GET, /api/get-account, *, *
|
p, *, *, GET, /api/get-account, *, *
|
||||||
p, *, *, GET, /api/userinfo, *, *
|
p, *, *, GET, /api/userinfo, *, *
|
||||||
p, *, *, POST, /api/login/oauth/access_token, *, *
|
p, *, *, *, /api/login/oauth, *, *
|
||||||
p, *, *, POST, /api/login/oauth/refresh_token, *, *
|
|
||||||
p, *, *, GET, /api/login/oauth/logout, *, *
|
|
||||||
p, *, *, GET, /api/get-application, *, *
|
p, *, *, GET, /api/get-application, *, *
|
||||||
|
p, *, *, GET, /api/get-applications, *, *
|
||||||
p, *, *, GET, /api/get-user, *, *
|
p, *, *, GET, /api/get-user, *, *
|
||||||
p, *, *, GET, /api/get-user-application, *, *
|
p, *, *, GET, /api/get-user-application, *, *
|
||||||
p, *, *, GET, /api/get-resources, *, *
|
p, *, *, GET, /api/get-resources, *, *
|
||||||
p, *, *, GET, /api/get-product, *, *
|
p, *, *, GET, /api/get-product, *, *
|
||||||
p, *, *, POST, /api/buy-product, *, *
|
p, *, *, POST, /api/buy-product, *, *
|
||||||
p, *, *, GET, /api/get-payment, *, *
|
p, *, *, GET, /api/get-payment, *, *
|
||||||
|
p, *, *, POST, /api/update-payment, *, *
|
||||||
|
p, *, *, POST, /api/invoice-payment, *, *
|
||||||
p, *, *, GET, /api/get-providers, *, *
|
p, *, *, GET, /api/get-providers, *, *
|
||||||
p, *, *, POST, /api/unlink, *, *
|
p, *, *, POST, /api/unlink, *, *
|
||||||
p, *, *, POST, /api/set-password, *, *
|
p, *, *, POST, /api/set-password, *, *
|
||||||
p, *, *, POST, /api/send-verification-code, *, *
|
p, *, *, POST, /api/send-verification-code, *, *
|
||||||
p, *, *, GET, /api/get-human-check, *, *
|
p, *, *, GET, /api/get-captcha, *, *
|
||||||
|
p, *, *, POST, /api/verify-captcha, *, *
|
||||||
p, *, *, POST, /api/reset-email-or-phone, *, *
|
p, *, *, POST, /api/reset-email-or-phone, *, *
|
||||||
p, *, *, POST, /api/upload-resource, *, *
|
p, *, *, POST, /api/upload-resource, *, *
|
||||||
p, *, *, GET, /.well-known/openid-configuration, *, *
|
p, *, *, GET, /.well-known/openid-configuration, *, *
|
||||||
|
6
build.sh
6
build.sh
@@ -1,11 +1,11 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
#try to connect to google to determine whether user need to use proxy
|
#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 ]
|
if [ $? == 0 ]
|
||||||
then
|
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 .
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o server .
|
||||||
else
|
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 .
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOPROXY=https://goproxy.cn,direct go build -ldflags="-w -s" -o server .
|
||||||
fi
|
fi
|
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
|
||||||
|
}
|
60
captcha/hcaptcha.go
Normal file
60
captcha/hcaptcha.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
// 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"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
captchaResp := &captchaResponse{}
|
||||||
|
err = json.Unmarshal(body, captchaResp)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return captchaResp.Success, nil
|
||||||
|
}
|
30
captcha/provider.go
Normal file
30
captcha/provider.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// 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()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
60
captcha/recaptcha.go
Normal file
60
captcha/recaptcha.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
// 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"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
captchaResp := &captchaResponse{}
|
||||||
|
err = json.Unmarshal(body, captchaResp)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return captchaResp.Success, nil
|
||||||
|
}
|
@@ -17,6 +17,7 @@ package conf
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -61,7 +62,12 @@ func GetBeegoConfDataSourceName() string {
|
|||||||
|
|
||||||
runningInDocker := os.Getenv("RUNNING_IN_DOCKER")
|
runningInDocker := os.Getenv("RUNNING_IN_DOCKER")
|
||||||
if runningInDocker == "true" {
|
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
|
return dataSourceName
|
||||||
|
@@ -75,12 +75,14 @@ type Response struct {
|
|||||||
Data2 interface{} `json:"data2"`
|
Data2 interface{} `json:"data2"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HumanCheck struct {
|
type Captcha struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
AppKey string `json:"appKey"`
|
AppKey string `json:"appKey"`
|
||||||
Scene string `json:"scene"`
|
Scene string `json:"scene"`
|
||||||
CaptchaId string `json:"captchaId"`
|
CaptchaId string `json:"captchaId"`
|
||||||
CaptchaImage interface{} `json:"captchaImage"`
|
CaptchaImage []byte `json:"captchaImage"`
|
||||||
|
ClientId string `json:"clientId"`
|
||||||
|
ClientSecret string `json:"clientSecret"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signup
|
// Signup
|
||||||
@@ -116,7 +118,7 @@ func (c *ApiController) Signup() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if application.IsSignupItemVisible("Email") && form.Email != "" {
|
if application.IsSignupItemVisible("Email") && application.GetSignupItemRule("Email") != "No verification" && form.Email != "" {
|
||||||
checkResult := object.CheckVerificationCode(form.Email, form.EmailCode)
|
checkResult := object.CheckVerificationCode(form.Email, form.EmailCode)
|
||||||
if len(checkResult) != 0 {
|
if len(checkResult) != 0 {
|
||||||
c.ResponseError(fmt.Sprintf("Email: %s", checkResult))
|
c.ResponseError(fmt.Sprintf("Email: %s", checkResult))
|
||||||
@@ -210,7 +212,7 @@ func (c *ApiController) Signup() {
|
|||||||
record := object.NewRecord(c.Ctx)
|
record := object.NewRecord(c.Ctx)
|
||||||
record.Organization = application.Organization
|
record.Organization = application.Organization
|
||||||
record.User = user.Name
|
record.User = user.Name
|
||||||
go object.AddRecord(record)
|
util.SafeGoroutine(func() { object.AddRecord(record) })
|
||||||
|
|
||||||
userId := fmt.Sprintf("%s/%s", user.Owner, user.Name)
|
userId := fmt.Sprintf("%s/%s", user.Owner, user.Name)
|
||||||
util.LogInfo(c.Ctx, "API: [%s] is signed up as new user", userId)
|
util.LogInfo(c.Ctx, "API: [%s] is signed up as new user", userId)
|
||||||
@@ -285,25 +287,36 @@ func (c *ApiController) GetUserinfo() {
|
|||||||
resp, err := object.GetUserInfo(userId, scope, aud, host)
|
resp, err := object.GetUserInfo(userId, scope, aud, host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
}
|
}
|
||||||
c.Data["json"] = resp
|
c.Data["json"] = resp
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetHumanCheck ...
|
// GetCaptcha ...
|
||||||
// @Tag Login API
|
// @Tag Login API
|
||||||
// @Title GetHumancheck
|
// @Title GetCaptcha
|
||||||
// @router /api/get-human-check [get]
|
// @router /api/get-captcha [get]
|
||||||
func (c *ApiController) GetHumanCheck() {
|
func (c *ApiController) GetCaptcha() {
|
||||||
c.Data["json"] = HumanCheck{Type: "none"}
|
applicationId := c.Input().Get("applicationId")
|
||||||
|
isCurrentProvider := c.Input().Get("isCurrentProvider")
|
||||||
|
|
||||||
provider := object.GetDefaultHumanCheckProvider()
|
captchaProvider, err := object.GetCaptchaProviderByApplication(applicationId, isCurrentProvider)
|
||||||
if provider == nil {
|
if err != nil {
|
||||||
id, img := object.GetCaptcha()
|
c.ResponseError(err.Error())
|
||||||
c.Data["json"] = HumanCheck{Type: "captcha", CaptchaId: id, CaptchaImage: img}
|
|
||||||
c.ServeJSON()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ServeJSON()
|
if captchaProvider != nil {
|
||||||
|
if captchaProvider.Type == "Default" {
|
||||||
|
id, img := object.GetCaptcha()
|
||||||
|
c.ResponseOk(Captcha{Type: captchaProvider.Type, CaptchaId: id, CaptchaImage: img})
|
||||||
|
return
|
||||||
|
} else if captchaProvider.Type != "" {
|
||||||
|
c.ResponseOk(Captcha{Type: captchaProvider.Type, ClientId: captchaProvider.ClientId, ClientSecret: captchaProvider.ClientSecret})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(Captcha{Type: "none"})
|
||||||
}
|
}
|
||||||
|
@@ -28,6 +28,7 @@ import (
|
|||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casdoor/casdoor/proxy"
|
"github.com/casdoor/casdoor/proxy"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
func codeToResponse(code *object.Code) *Response {
|
func codeToResponse(code *object.Code) *Response {
|
||||||
@@ -132,8 +133,8 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
|||||||
// @Param redirectUri query string true "redirect uri"
|
// @Param redirectUri query string true "redirect uri"
|
||||||
// @Param scope query string true "scope"
|
// @Param scope query string true "scope"
|
||||||
// @Param state query string true "state"
|
// @Param state query string true "state"
|
||||||
// @Success 200 {object} controllers.api_controller.Response The Response object
|
// @Success 200 {object} Response The Response object
|
||||||
// @router /update-application [get]
|
// @router /get-app-login [get]
|
||||||
func (c *ApiController) GetApplicationLogin() {
|
func (c *ApiController) GetApplicationLogin() {
|
||||||
clientId := c.Input().Get("clientId")
|
clientId := c.Input().Get("clientId")
|
||||||
responseType := c.Input().Get("responseType")
|
responseType := c.Input().Get("responseType")
|
||||||
@@ -162,9 +163,16 @@ func setHttpClient(idProvider idp.IdProvider, providerType string) {
|
|||||||
// @Title Login
|
// @Title Login
|
||||||
// @Tag Login API
|
// @Tag Login API
|
||||||
// @Description login
|
// @Description login
|
||||||
// @Param oAuthParams query string true "oAuth parameters"
|
// @Param clientId query string true clientId
|
||||||
// @Param body body RequestForm true "Login information"
|
// @Param responseType query string true responseType
|
||||||
// @Success 200 {object} controllers.api_controller.Response The Response object
|
// @Param redirectUri query string true redirectUri
|
||||||
|
// @Param scope query string false scope
|
||||||
|
// @Param state query string false state
|
||||||
|
// @Param nonce query string false nonce
|
||||||
|
// @Param code_challenge_method query string false code_challenge_method
|
||||||
|
// @Param code_challenge query string false code_challenge
|
||||||
|
// @Param form body controllers.RequestForm true "Login information"
|
||||||
|
// @Success 200 {object} Response The Response object
|
||||||
// @router /login [post]
|
// @router /login [post]
|
||||||
func (c *ApiController) Login() {
|
func (c *ApiController) Login() {
|
||||||
resp := &Response{}
|
resp := &Response{}
|
||||||
@@ -222,7 +230,11 @@ func (c *ApiController) Login() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// disable the verification code
|
// 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)
|
user = object.GetUserByFields(form.Organization, form.Username)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
@@ -248,7 +260,7 @@ func (c *ApiController) Login() {
|
|||||||
record := object.NewRecord(c.Ctx)
|
record := object.NewRecord(c.Ctx)
|
||||||
record.Organization = application.Organization
|
record.Organization = application.Organization
|
||||||
record.User = user.Name
|
record.User = user.Name
|
||||||
go object.AddRecord(record)
|
util.SafeGoroutine(func() { object.AddRecord(record) })
|
||||||
}
|
}
|
||||||
} else if form.Provider != "" {
|
} else if form.Provider != "" {
|
||||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||||
@@ -283,7 +295,7 @@ func (c *ApiController) Login() {
|
|||||||
clientSecret = provider.ClientSecret2
|
clientSecret = provider.ClientSecret2
|
||||||
}
|
}
|
||||||
|
|
||||||
idProvider := idp.GetIdProvider(provider.Type, provider.SubType, clientId, clientSecret, provider.AppId, form.RedirectUri, provider.Domain)
|
idProvider := idp.GetIdProvider(provider.Type, provider.SubType, clientId, clientSecret, provider.AppId, form.RedirectUri, provider.Domain, provider.CustomAuthUrl, provider.CustomTokenUrl, provider.CustomUserInfoUrl)
|
||||||
if idProvider == nil {
|
if idProvider == nil {
|
||||||
c.ResponseError(fmt.Sprintf("The provider type: %s is not supported", provider.Type))
|
c.ResponseError(fmt.Sprintf("The provider type: %s is not supported", provider.Type))
|
||||||
return
|
return
|
||||||
@@ -321,12 +333,6 @@ func (c *ApiController) Login() {
|
|||||||
user = object.GetUser(fmt.Sprintf("%s/%s", application.Organization, userInfo.Id))
|
user = object.GetUser(fmt.Sprintf("%s/%s", application.Organization, userInfo.Id))
|
||||||
} else if provider.Category == "OAuth" {
|
} else if provider.Category == "OAuth" {
|
||||||
user = object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
|
user = object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
|
||||||
if user == nil {
|
|
||||||
user = object.GetUserByField(application.Organization, provider.Type, userInfo.Username)
|
|
||||||
}
|
|
||||||
if user == nil {
|
|
||||||
user = object.GetUserByField(application.Organization, "name", userInfo.Username)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if user != nil && user.IsDeleted == false {
|
if user != nil && user.IsDeleted == false {
|
||||||
@@ -341,7 +347,7 @@ func (c *ApiController) Login() {
|
|||||||
record := object.NewRecord(c.Ctx)
|
record := object.NewRecord(c.Ctx)
|
||||||
record.Organization = application.Organization
|
record.Organization = application.Organization
|
||||||
record.User = user.Name
|
record.User = user.Name
|
||||||
go object.AddRecord(record)
|
util.SafeGoroutine(func() { object.AddRecord(record) })
|
||||||
} else if provider.Category == "OAuth" {
|
} else if provider.Category == "OAuth" {
|
||||||
// Sign up via OAuth
|
// Sign up via OAuth
|
||||||
if !application.EnableSignUp {
|
if !application.EnableSignUp {
|
||||||
@@ -354,6 +360,19 @@ func (c *ApiController) Login() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle username conflicts
|
||||||
|
tmpUser := object.GetUser(fmt.Sprintf("%s/%s", application.Organization, userInfo.Username))
|
||||||
|
if tmpUser != nil {
|
||||||
|
uid, err := uuid.NewRandom()
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uidStr := strings.Split(uid.String(), "-")
|
||||||
|
userInfo.Username = fmt.Sprintf("%s_%s", userInfo.Username, uidStr[1])
|
||||||
|
}
|
||||||
|
|
||||||
properties := map[string]string{}
|
properties := map[string]string{}
|
||||||
properties["no"] = strconv.Itoa(len(object.GetUsers(application.Organization)) + 2)
|
properties["no"] = strconv.Itoa(len(object.GetUsers(application.Organization)) + 2)
|
||||||
user = &object.User{
|
user = &object.User{
|
||||||
@@ -390,7 +409,13 @@ func (c *ApiController) Login() {
|
|||||||
record := object.NewRecord(c.Ctx)
|
record := object.NewRecord(c.Ctx)
|
||||||
record.Organization = application.Organization
|
record.Organization = application.Organization
|
||||||
record.User = user.Name
|
record.User = user.Name
|
||||||
go object.AddRecord(record)
|
util.SafeGoroutine(func() { object.AddRecord(record) })
|
||||||
|
|
||||||
|
record2 := object.NewRecord(c.Ctx)
|
||||||
|
record2.Action = "signup"
|
||||||
|
record2.Organization = application.Organization
|
||||||
|
record2.User = user.Name
|
||||||
|
util.SafeGoroutine(func() { object.AddRecord(record2) })
|
||||||
} else if provider.Category == "SAML" {
|
} else if provider.Category == "SAML" {
|
||||||
resp = &Response{Status: "error", Msg: "The account does not exist"}
|
resp = &Response{Status: "error", Msg: "The account does not exist"}
|
||||||
}
|
}
|
||||||
@@ -403,9 +428,6 @@ func (c *ApiController) Login() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
oldUser := object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
|
oldUser := object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
|
||||||
if oldUser == nil {
|
|
||||||
oldUser = object.GetUserByField(application.Organization, provider.Type, userInfo.Username)
|
|
||||||
}
|
|
||||||
if oldUser != nil {
|
if oldUser != nil {
|
||||||
c.ResponseError(fmt.Sprintf("The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)", provider.Type, userInfo.Username, userInfo.DisplayName, oldUser.Name, oldUser.DisplayName))
|
c.ResponseError(fmt.Sprintf("The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)", provider.Type, userInfo.Username, userInfo.DisplayName, oldUser.Name, oldUser.DisplayName))
|
||||||
return
|
return
|
||||||
@@ -434,6 +456,11 @@ func (c *ApiController) Login() {
|
|||||||
|
|
||||||
user := c.getCurrentUser()
|
user := c.getCurrentUser()
|
||||||
resp = c.HandleLoggedIn(application, user, &form)
|
resp = c.HandleLoggedIn(application, user, &form)
|
||||||
|
|
||||||
|
record := object.NewRecord(c.Ctx)
|
||||||
|
record.Organization = application.Organization
|
||||||
|
record.User = user.Name
|
||||||
|
util.SafeGoroutine(func() { object.AddRecord(record) })
|
||||||
} else {
|
} else {
|
||||||
c.ResponseError(fmt.Sprintf("unknown authentication type (not password or provider), form = %s", util.StructToJson(form)))
|
c.ResponseError(fmt.Sprintf("unknown authentication type (not password or provider), form = %s", util.StructToJson(form)))
|
||||||
return
|
return
|
||||||
|
@@ -215,7 +215,7 @@ func (c *ApiController) SyncLdapUsers() {
|
|||||||
|
|
||||||
object.UpdateLdapSyncTime(ldapId)
|
object.UpdateLdapSyncTime(ldapId)
|
||||||
|
|
||||||
exist, failed := object.SyncLdapUsers(owner, users)
|
exist, failed := object.SyncLdapUsers(owner, users, ldapId)
|
||||||
c.Data["json"] = &Response{Status: "ok", Data: &LdapSyncResp{
|
c.Data["json"] = &Response{Status: "ok", Data: &LdapSyncResp{
|
||||||
Exist: *exist,
|
Exist: *exist,
|
||||||
Failed: *failed,
|
Failed: *failed,
|
||||||
|
120
controllers/model.go
Normal file
120
controllers/model.go
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/utils/pagination"
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetModels
|
||||||
|
// @Title GetModels
|
||||||
|
// @Tag Model API
|
||||||
|
// @Description get models
|
||||||
|
// @Param owner query string true "The owner of models"
|
||||||
|
// @Success 200 {array} object.Model The Response object
|
||||||
|
// @router /get-models [get]
|
||||||
|
func (c *ApiController) GetModels() {
|
||||||
|
owner := c.Input().Get("owner")
|
||||||
|
limit := c.Input().Get("pageSize")
|
||||||
|
page := c.Input().Get("p")
|
||||||
|
field := c.Input().Get("field")
|
||||||
|
value := c.Input().Get("value")
|
||||||
|
sortField := c.Input().Get("sortField")
|
||||||
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
if limit == "" || page == "" {
|
||||||
|
c.Data["json"] = object.GetModels(owner)
|
||||||
|
c.ServeJSON()
|
||||||
|
} else {
|
||||||
|
limit := util.ParseInt(limit)
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetModelCount(owner, field, value)))
|
||||||
|
models := object.GetPaginationModels(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
c.ResponseOk(models, paginator.Nums())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetModel
|
||||||
|
// @Title GetModel
|
||||||
|
// @Tag Model API
|
||||||
|
// @Description get model
|
||||||
|
// @Param id query string true "The id of the model"
|
||||||
|
// @Success 200 {object} object.Model The Response object
|
||||||
|
// @router /get-model [get]
|
||||||
|
func (c *ApiController) GetModel() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
c.Data["json"] = object.GetModel(id)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateModel
|
||||||
|
// @Title UpdateModel
|
||||||
|
// @Tag Model API
|
||||||
|
// @Description update model
|
||||||
|
// @Param id query string true "The id of the model"
|
||||||
|
// @Param body body object.Model true "The details of the model"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /update-model [post]
|
||||||
|
func (c *ApiController) UpdateModel() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
var model object.Model
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &model)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.UpdateModel(id, &model))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddModel
|
||||||
|
// @Title AddModel
|
||||||
|
// @Tag Model API
|
||||||
|
// @Description add model
|
||||||
|
// @Param body body object.Model true "The details of the model"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /add-model [post]
|
||||||
|
func (c *ApiController) AddModel() {
|
||||||
|
var model object.Model
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &model)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.AddModel(&model))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteModel
|
||||||
|
// @Title DeleteModel
|
||||||
|
// @Tag Model API
|
||||||
|
// @Description delete model
|
||||||
|
// @Param body body object.Model true "The details of the model"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /delete-model [post]
|
||||||
|
func (c *ApiController) DeleteModel() {
|
||||||
|
var model object.Model
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &model)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.DeleteModel(&model))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
@@ -18,6 +18,8 @@ import "github.com/casdoor/casdoor/object"
|
|||||||
|
|
||||||
// @Title GetOidcDiscovery
|
// @Title GetOidcDiscovery
|
||||||
// @Tag OIDC API
|
// @Tag OIDC API
|
||||||
|
// @Description Get Oidc Discovery
|
||||||
|
// @Success 200 {object} object.OidcDiscovery
|
||||||
// @router /.well-known/openid-configuration [get]
|
// @router /.well-known/openid-configuration [get]
|
||||||
func (c *RootController) GetOidcDiscovery() {
|
func (c *RootController) GetOidcDiscovery() {
|
||||||
host := c.Ctx.Request.Host
|
host := c.Ctx.Request.Host
|
||||||
@@ -27,6 +29,7 @@ func (c *RootController) GetOidcDiscovery() {
|
|||||||
|
|
||||||
// @Title GetJwks
|
// @Title GetJwks
|
||||||
// @Tag OIDC API
|
// @Tag OIDC API
|
||||||
|
// @Success 200 {object} jose.JSONWebKey
|
||||||
// @router /.well-known/jwks [get]
|
// @router /.well-known/jwks [get]
|
||||||
func (c *RootController) GetJwks() {
|
func (c *RootController) GetJwks() {
|
||||||
jwks, err := object.GetJsonWebKeySet()
|
jwks, err := object.GetJsonWebKeySet()
|
||||||
|
@@ -158,3 +158,20 @@ func (c *ApiController) NotifyPayment() {
|
|||||||
panic(fmt.Errorf("NotifyPayment() failed: %v", ok))
|
panic(fmt.Errorf("NotifyPayment() failed: %v", ok))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Title InvoicePayment
|
||||||
|
// @Tag Payment API
|
||||||
|
// @Description invoice payment
|
||||||
|
// @Param id query string true "The id of the payment"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /invoice-payment [post]
|
||||||
|
func (c *ApiController) InvoicePayment() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
payment := object.GetPayment(id)
|
||||||
|
invoiceUrl, err := object.InvoicePayment(payment)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
}
|
||||||
|
c.ResponseOk(invoiceUrl)
|
||||||
|
}
|
||||||
|
@@ -26,7 +26,7 @@ import (
|
|||||||
// @Description get all records
|
// @Description get all records
|
||||||
// @Param pageSize query string true "The size of each page"
|
// @Param pageSize query string true "The size of each page"
|
||||||
// @Param p query string true "The number of the page"
|
// @Param p query string true "The number of the page"
|
||||||
// @Success 200 {array} object.Records The Response object
|
// @Success 200 {object} object.Record The Response object
|
||||||
// @router /get-records [get]
|
// @router /get-records [get]
|
||||||
func (c *ApiController) GetRecords() {
|
func (c *ApiController) GetRecords() {
|
||||||
limit := c.Input().Get("pageSize")
|
limit := c.Input().Get("pageSize")
|
||||||
@@ -50,8 +50,8 @@ func (c *ApiController) GetRecords() {
|
|||||||
// @Tag Record API
|
// @Tag Record API
|
||||||
// @Title GetRecordsByFilter
|
// @Title GetRecordsByFilter
|
||||||
// @Description get records by filter
|
// @Description get records by filter
|
||||||
// @Param body body object.Records true "filter Record message"
|
// @Param filter body string true "filter Record message"
|
||||||
// @Success 200 {array} object.Records The Response object
|
// @Success 200 {object} object.Record The Response object
|
||||||
// @router /get-records-filter [post]
|
// @router /get-records-filter [post]
|
||||||
func (c *ApiController) GetRecordsByFilter() {
|
func (c *ApiController) GetRecordsByFilter() {
|
||||||
body := string(c.Ctx.Input.RequestBody)
|
body := string(c.Ctx.Input.RequestBody)
|
||||||
|
@@ -26,6 +26,7 @@ func (c *ApiController) GetSamlMeta() {
|
|||||||
application := object.GetApplication(paramApp)
|
application := object.GetApplication(paramApp)
|
||||||
if application == nil {
|
if application == nil {
|
||||||
c.ResponseError(fmt.Sprintf("err: application %s not found", paramApp))
|
c.ResponseError(fmt.Sprintf("err: application %s not found", paramApp))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
metadata, _ := object.GetSamlMeta(application, host)
|
metadata, _ := object.GetSamlMeta(application, host)
|
||||||
c.Data["xml"] = metadata
|
c.Data["xml"] = metadata
|
||||||
|
@@ -25,13 +25,26 @@ import (
|
|||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type EmailForm struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
Sender string `json:"sender"`
|
||||||
|
Receivers []string `json:"receivers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SmsForm struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
Receivers []string `json:"receivers"`
|
||||||
|
OrgId string `json:"organizationId"` // e.g. "admin/built-in"
|
||||||
|
}
|
||||||
|
|
||||||
// SendEmail
|
// SendEmail
|
||||||
// @Title SendEmail
|
// @Title SendEmail
|
||||||
// @Tag Service API
|
// @Tag Service API
|
||||||
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
|
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
|
||||||
// @Param clientId query string true "The clientId of the application"
|
// @Param clientId query string true "The clientId of the application"
|
||||||
// @Param clientSecret query string true "The clientSecret of the application"
|
// @Param clientSecret query string true "The clientSecret of the application"
|
||||||
// @Param body body emailForm true "Details of the email request"
|
// @Param from body controllers.EmailForm true "Details of the email request"
|
||||||
// @Success 200 {object} Response object
|
// @Success 200 {object} Response object
|
||||||
// @router /api/send-email [post]
|
// @router /api/send-email [post]
|
||||||
func (c *ApiController) SendEmail() {
|
func (c *ApiController) SendEmail() {
|
||||||
@@ -40,12 +53,8 @@ func (c *ApiController) SendEmail() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var emailForm struct {
|
var emailForm EmailForm
|
||||||
Title string `json:"title"`
|
|
||||||
Content string `json:"content"`
|
|
||||||
Sender string `json:"sender"`
|
|
||||||
Receivers []string `json:"receivers"`
|
|
||||||
}
|
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &emailForm)
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &emailForm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
@@ -86,7 +95,7 @@ func (c *ApiController) SendEmail() {
|
|||||||
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
|
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
|
||||||
// @Param clientId query string true "The clientId of the application"
|
// @Param clientId query string true "The clientId of the application"
|
||||||
// @Param clientSecret query string true "The clientSecret of the application"
|
// @Param clientSecret query string true "The clientSecret of the application"
|
||||||
// @Param body body smsForm true "Details of the sms request"
|
// @Param from body controllers.SmsForm true "Details of the sms request"
|
||||||
// @Success 200 {object} Response object
|
// @Success 200 {object} Response object
|
||||||
// @router /api/send-sms [post]
|
// @router /api/send-sms [post]
|
||||||
func (c *ApiController) SendSms() {
|
func (c *ApiController) SendSms() {
|
||||||
@@ -95,11 +104,7 @@ func (c *ApiController) SendSms() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var smsForm struct {
|
var smsForm SmsForm
|
||||||
Content string `json:"content"`
|
|
||||||
Receivers []string `json:"receivers"`
|
|
||||||
OrgId string `json:"organizationId"` // e.g. "admin/built-in"
|
|
||||||
}
|
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &smsForm)
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &smsForm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
|
@@ -16,7 +16,6 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/astaxie/beego/utils/pagination"
|
"github.com/astaxie/beego/utils/pagination"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
@@ -114,3 +113,18 @@ func (c *ApiController) DeleteSyncer() {
|
|||||||
c.Data["json"] = wrapActionResponse(object.DeleteSyncer(&syncer))
|
c.Data["json"] = wrapActionResponse(object.DeleteSyncer(&syncer))
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Title RunSyncer
|
||||||
|
// @Tag Syncer API
|
||||||
|
// @Description run syncer
|
||||||
|
// @Param body body object.Syncer true "The details of the syncer"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /run-syncer [get]
|
||||||
|
func (c *ApiController) RunSyncer() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
syncer := object.GetSyncer(id)
|
||||||
|
|
||||||
|
object.RunSyncer(syncer)
|
||||||
|
|
||||||
|
c.ResponseOk()
|
||||||
|
}
|
||||||
|
@@ -230,7 +230,7 @@ func (c *ApiController) RefreshToken() {
|
|||||||
clientSecret = tokenRequest.ClientSecret
|
clientSecret = tokenRequest.ClientSecret
|
||||||
grantType = tokenRequest.GrantType
|
grantType = tokenRequest.GrantType
|
||||||
scope = tokenRequest.Scope
|
scope = tokenRequest.Scope
|
||||||
|
refreshToken = tokenRequest.RefreshToken
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,21 +275,20 @@ func (c *ApiController) IntrospectToken() {
|
|||||||
tokenValue := c.Input().Get("token")
|
tokenValue := c.Input().Get("token")
|
||||||
clientId, clientSecret, ok := c.Ctx.Request.BasicAuth()
|
clientId, clientSecret, ok := c.Ctx.Request.BasicAuth()
|
||||||
if !ok {
|
if !ok {
|
||||||
util.LogWarning(c.Ctx, "Basic Authorization parses failed")
|
clientId = c.Input().Get("client_id")
|
||||||
c.Data["json"] = Response{Status: "error", Msg: "Unauthorized operation"}
|
clientSecret = c.Input().Get("client_secret")
|
||||||
c.ServeJSON()
|
if clientId == "" || clientSecret == "" {
|
||||||
return
|
c.ResponseError("empty clientId or clientSecret")
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
application := object.GetApplicationByClientId(clientId)
|
application := object.GetApplicationByClientId(clientId)
|
||||||
if application == nil || application.ClientSecret != clientSecret {
|
if application == nil || application.ClientSecret != clientSecret {
|
||||||
util.LogWarning(c.Ctx, "Basic Authorization failed")
|
c.ResponseError("invalid application or wrong clientSecret")
|
||||||
c.Data["json"] = Response{Status: "error", Msg: "Unauthorized operation"}
|
|
||||||
c.ServeJSON()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
token := object.GetTokenByTokenAndApplication(tokenValue, application.Name)
|
token := object.GetTokenByTokenAndApplication(tokenValue, application.Name)
|
||||||
if token == nil {
|
if token == nil {
|
||||||
util.LogWarning(c.Ctx, "application: %s can not find token", application.Name)
|
|
||||||
c.Data["json"] = &object.IntrospectionResponse{Active: false}
|
c.Data["json"] = &object.IntrospectionResponse{Active: false}
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
return
|
return
|
||||||
@@ -299,7 +298,6 @@ func (c *ApiController) IntrospectToken() {
|
|||||||
// and token revoked case. but we not implement
|
// and token revoked case. but we not implement
|
||||||
// TODO: 2022-03-03 add token revoked check, when we implemented the Token Revocation(rfc7009) Specs.
|
// TODO: 2022-03-03 add token revoked check, when we implemented the Token Revocation(rfc7009) Specs.
|
||||||
// refs: https://tools.ietf.org/html/rfc7009
|
// refs: https://tools.ietf.org/html/rfc7009
|
||||||
util.LogWarning(c.Ctx, "token invalid")
|
|
||||||
c.Data["json"] = &object.IntrospectionResponse{Active: false}
|
c.Data["json"] = &object.IntrospectionResponse{Active: false}
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
return
|
return
|
||||||
|
@@ -25,4 +25,5 @@ type TokenRequest struct {
|
|||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
Tag string `json:"tag"`
|
Tag string `json:"tag"`
|
||||||
Avatar string `json:"avatar"`
|
Avatar string `json:"avatar"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
}
|
}
|
||||||
|
@@ -87,6 +87,17 @@ func (c *ApiController) GetUser() {
|
|||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
owner := c.Input().Get("owner")
|
owner := c.Input().Get("owner")
|
||||||
email := c.Input().Get("email")
|
email := c.Input().Get("email")
|
||||||
|
userOwner, _ := util.GetOwnerAndNameFromId(id)
|
||||||
|
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", userOwner))
|
||||||
|
|
||||||
|
if !organization.IsProfilePublic {
|
||||||
|
requestUserId := c.GetSessionUsername()
|
||||||
|
hasPermission, err := object.CheckUserPermission(requestUserId, id, false)
|
||||||
|
if !hasPermission {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var user *object.User
|
var user *object.User
|
||||||
if email == "" {
|
if email == "" {
|
||||||
@@ -233,39 +244,15 @@ func (c *ApiController) SetPassword() {
|
|||||||
newPassword := c.Ctx.Request.Form.Get("newPassword")
|
newPassword := c.Ctx.Request.Form.Get("newPassword")
|
||||||
|
|
||||||
requestUserId := c.GetSessionUsername()
|
requestUserId := c.GetSessionUsername()
|
||||||
if requestUserId == "" {
|
|
||||||
c.ResponseError("Please login first")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
userId := fmt.Sprintf("%s/%s", userOwner, userName)
|
userId := fmt.Sprintf("%s/%s", userOwner, userName)
|
||||||
targetUser := object.GetUser(userId)
|
|
||||||
if targetUser == nil {
|
hasPermission, err := object.CheckUserPermission(requestUserId, userId, true)
|
||||||
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId))
|
if !hasPermission {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
hasPermission := false
|
targetUser := object.GetUser(userId)
|
||||||
if strings.HasPrefix(requestUserId, "app/") {
|
|
||||||
hasPermission = true
|
|
||||||
} else {
|
|
||||||
requestUser := object.GetUser(requestUserId)
|
|
||||||
if requestUser == nil {
|
|
||||||
c.ResponseError("Session outdated. Please login again.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if requestUser.IsGlobalAdmin {
|
|
||||||
hasPermission = true
|
|
||||||
} else if requestUserId == userId {
|
|
||||||
hasPermission = true
|
|
||||||
} else if targetUser.Owner == requestUser.Owner && requestUser.IsAdmin {
|
|
||||||
hasPermission = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !hasPermission {
|
|
||||||
c.ResponseError("You don't have the permission to do this.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if oldPassword != "" {
|
if oldPassword != "" {
|
||||||
msg := object.CheckPassword(targetUser, oldPassword)
|
msg := object.CheckPassword(targetUser, oldPassword)
|
||||||
|
@@ -19,6 +19,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/captcha"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
@@ -48,20 +49,28 @@ func (c *ApiController) SendVerificationCode() {
|
|||||||
checkUser := c.Ctx.Request.Form.Get("checkUser")
|
checkUser := c.Ctx.Request.Form.Get("checkUser")
|
||||||
remoteAddr := util.GetIPFromRequest(c.Ctx.Request)
|
remoteAddr := util.GetIPFromRequest(c.Ctx.Request)
|
||||||
|
|
||||||
if len(destType) == 0 || len(dest) == 0 || len(orgId) == 0 || !strings.Contains(orgId, "/") || len(checkType) == 0 || len(checkId) == 0 || len(checkKey) == 0 {
|
if len(destType) == 0 || len(dest) == 0 || len(orgId) == 0 || !strings.Contains(orgId, "/") || len(checkType) == 0 {
|
||||||
c.ResponseError("Missing parameter.")
|
c.ResponseError("Missing parameter.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
isHuman := false
|
captchaProvider := captcha.GetCaptchaProvider(checkType)
|
||||||
captchaProvider := object.GetDefaultHumanCheckProvider()
|
|
||||||
if captchaProvider == nil {
|
|
||||||
isHuman = object.VerifyCaptcha(checkId, checkKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isHuman {
|
if captchaProvider != nil {
|
||||||
c.ResponseError("Turing test failed.")
|
if checkKey == "" {
|
||||||
return
|
c.ResponseError("Missing parameter: checkKey.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isHuman, err := captchaProvider.VerifyCaptcha(checkKey, checkId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError("Failed to verify captcha: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isHuman {
|
||||||
|
c.ResponseError("Turing test failed.")
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
user := c.getCurrentUser()
|
user := c.getCurrentUser()
|
||||||
@@ -173,3 +182,36 @@ func (c *ApiController) ResetEmailOrPhone() {
|
|||||||
c.Data["json"] = Response{Status: "ok"}
|
c.Data["json"] = Response{Status: "ok"}
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VerifyCaptcha ...
|
||||||
|
// @Title VerifyCaptcha
|
||||||
|
// @Tag Verification API
|
||||||
|
// @router /verify-captcha [post]
|
||||||
|
func (c *ApiController) VerifyCaptcha() {
|
||||||
|
captchaType := c.Ctx.Request.Form.Get("captchaType")
|
||||||
|
|
||||||
|
captchaToken := c.Ctx.Request.Form.Get("captchaToken")
|
||||||
|
clientSecret := c.Ctx.Request.Form.Get("clientSecret")
|
||||||
|
if captchaToken == "" {
|
||||||
|
c.ResponseError("Missing parameter: captchaToken.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if clientSecret == "" {
|
||||||
|
c.ResponseError("Missing parameter: clientSecret.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
provider := captcha.GetCaptchaProvider(captchaType)
|
||||||
|
if provider == nil {
|
||||||
|
c.ResponseError("Invalid captcha provider.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isValid, err := provider.VerifyCaptcha(captchaToken, clientSecret)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError("Failed to verify captcha: %v", err)
|
||||||
|
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()
|
return NewBcryptCredManager()
|
||||||
} else if passwordType == "pbkdf2-salt" {
|
} else if passwordType == "pbkdf2-salt" {
|
||||||
return NewPbkdf2SaltCredManager()
|
return NewPbkdf2SaltCredManager()
|
||||||
|
} else if passwordType == "argon2id" {
|
||||||
|
return NewArgon2idCredManager()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -32,8 +32,8 @@ func getMd5HexDigest(s string) string {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMd5UserSaltCredManager() *Sha256SaltCredManager {
|
func NewMd5UserSaltCredManager() *Md5UserSaltCredManager {
|
||||||
cm := &Sha256SaltCredManager{}
|
cm := &Md5UserSaltCredManager{}
|
||||||
return cm
|
return cm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -5,14 +5,13 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ./
|
context: ./
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
entrypoint: /bin/sh -c './server --createDatabase=true'
|
||||||
ports:
|
ports:
|
||||||
- "8000:8000"
|
- "8000:8000"
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
environment:
|
environment:
|
||||||
RUNNING_IN_DOCKER: "true"
|
RUNNING_IN_DOCKER: "true"
|
||||||
extra_hosts:
|
|
||||||
- "host.docker.internal:host-gateway"
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./conf:/conf/
|
- ./conf:/conf/
|
||||||
db:
|
db:
|
||||||
|
13
go.mod
13
go.mod
@@ -4,15 +4,15 @@ go 1.16
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b
|
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/astaxie/beego v1.12.3
|
||||||
github.com/aws/aws-sdk-go v1.37.30
|
github.com/aws/aws-sdk-go v1.44.4
|
||||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
|
|
||||||
github.com/beevik/etree v1.1.0
|
github.com/beevik/etree v1.1.0
|
||||||
github.com/casbin/casbin/v2 v2.30.1
|
github.com/casbin/casbin/v2 v2.30.1
|
||||||
github.com/casbin/xorm-adapter/v2 v2.5.1
|
github.com/casbin/xorm-adapter/v2 v2.5.1
|
||||||
github.com/casdoor/go-sms-sender v0.2.0
|
github.com/casdoor/go-sms-sender v0.2.0
|
||||||
github.com/casdoor/goth v1.69.0-FIX1
|
github.com/casdoor/goth v1.69.0-FIX1
|
||||||
|
github.com/casdoor/oss v1.2.0
|
||||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||||
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
|
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
|
||||||
github.com/go-ldap/ldap/v3 v3.3.0
|
github.com/go-ldap/ldap/v3 v3.3.0
|
||||||
@@ -20,12 +20,10 @@ require (
|
|||||||
github.com/go-sql-driver/mysql v1.5.0
|
github.com/go-sql-driver/mysql v1.5.0
|
||||||
github.com/golang-jwt/jwt/v4 v4.2.0
|
github.com/golang-jwt/jwt/v4 v4.2.0
|
||||||
github.com/google/uuid v1.2.0
|
github.com/google/uuid v1.2.0
|
||||||
github.com/jinzhu/configor v1.2.1 // indirect
|
|
||||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||||
github.com/lestrrat-go/jwx v0.9.0
|
github.com/lestrrat-go/jwx v0.9.0
|
||||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
||||||
github.com/qiangmzsx/string-adapter/v2 v2.1.0
|
github.com/qiangmzsx/string-adapter/v2 v2.1.0
|
||||||
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76
|
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/russellhaering/gosaml2 v0.6.0
|
github.com/russellhaering/gosaml2 v0.6.0
|
||||||
github.com/russellhaering/goxmldsig v1.1.1
|
github.com/russellhaering/goxmldsig v1.1.1
|
||||||
@@ -35,14 +33,13 @@ require (
|
|||||||
github.com/tealeg/xlsx v1.0.5
|
github.com/tealeg/xlsx v1.0.5
|
||||||
github.com/thanhpk/randstr v1.0.4
|
github.com/thanhpk/randstr v1.0.4
|
||||||
golang.org/x/crypto v0.0.0-20220208233918-bba287dce954
|
golang.org/x/crypto v0.0.0-20220208233918-bba287dce954
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
|
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd
|
||||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
|
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
|
||||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
|
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
|
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
|
||||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0
|
gopkg.in/square/go-jose.v2 v2.6.0
|
||||||
gopkg.in/yaml.v2 v2.3.0 // indirect
|
gopkg.in/yaml.v2 v2.3.0 // indirect
|
||||||
xorm.io/core v0.7.2
|
xorm.io/core v0.7.2
|
||||||
xorm.io/xorm v1.0.3
|
xorm.io/xorm v1.0.4
|
||||||
)
|
)
|
||||||
|
76
go.sum
76
go.sum
@@ -35,6 +35,21 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
|
|||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
||||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
||||||
|
github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U=
|
||||||
|
github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
|
||||||
|
github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk=
|
||||||
|
github.com/Azure/azure-storage-blob-go v0.15.0/go.mod h1:vbjsVbX0dlxnRc4FFMPsS9BsJWPcne7GB7onqlPvz58=
|
||||||
|
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
|
||||||
|
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||||
|
github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q=
|
||||||
|
github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
|
||||||
|
github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
|
||||||
|
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
|
||||||
|
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
|
||||||
|
github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
|
||||||
|
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
|
||||||
|
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
|
||||||
|
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||||
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
|
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
|
||||||
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||||
@@ -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/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
|
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387 h1:loy0fjI90vF44BPW4ZYOkE3tDkGTy7yHURusOJimt+I=
|
||||||
|
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387/go.mod h1:GuR5j/NW7AU7tDAQUDGCtpiPxWIOy/c3kiRDnlwiCHc=
|
||||||
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||||
github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
|
github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
|
||||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1075 h1:Z0SzZttfYI/raZ5O9WF3cezZJTSW4Yz4Kow9uWdyRwg=
|
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1075 h1:Z0SzZttfYI/raZ5O9WF3cezZJTSW4Yz4Kow9uWdyRwg=
|
||||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1075/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA=
|
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1075/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA=
|
||||||
github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible h1:Ft+KeWIJxFP76LqgJbvtOA1qBIoC8vGkTV3QeCOeJC4=
|
github.com/aliyun/aliyun-oss-go-sdk v2.2.2+incompatible h1:9gWa46nstkJ9miBReJcN8Gq34cBFbzSpQZVVT9N09TM=
|
||||||
github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
github.com/aliyun/aliyun-oss-go-sdk v2.2.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||||
github.com/astaxie/beego v1.12.3 h1:SAQkdD2ePye+v8Gn1r4X6IKZM1wd28EyUOVQ3PDSOOQ=
|
github.com/astaxie/beego v1.12.3 h1:SAQkdD2ePye+v8Gn1r4X6IKZM1wd28EyUOVQ3PDSOOQ=
|
||||||
github.com/astaxie/beego v1.12.3/go.mod h1:p3qIm0Ryx7zeBHLljmd7omloyca1s4yu1a8kM1FkpIA=
|
github.com/astaxie/beego v1.12.3/go.mod h1:p3qIm0Ryx7zeBHLljmd7omloyca1s4yu1a8kM1FkpIA=
|
||||||
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
|
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
|
||||||
github.com/aws/aws-sdk-go v1.37.30 h1:fZeVg3QuTkWE/dEvPQbK6AL32+3G9ofJfGFSPS1XLH0=
|
github.com/aws/aws-sdk-go v1.44.4 h1:ePN0CVJMdiz2vYUcJH96eyxRrtKGSDMgyhP6rah2OgE=
|
||||||
github.com/aws/aws-sdk-go v1.37.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
github.com/aws/aws-sdk-go v1.44.4/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
||||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA=
|
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA=
|
||||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
|
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
|
||||||
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
|
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
|
||||||
@@ -85,6 +102,8 @@ github.com/casdoor/go-sms-sender v0.2.0 h1:52bin4EBOPzOee64s9UK7jxd22FODvT9/+Y/Z
|
|||||||
github.com/casdoor/go-sms-sender v0.2.0/go.mod h1:fsZsNnALvFIo+HFcE1U/oCQv4ZT42FdglXKMsEm3WSk=
|
github.com/casdoor/go-sms-sender v0.2.0/go.mod h1:fsZsNnALvFIo+HFcE1U/oCQv4ZT42FdglXKMsEm3WSk=
|
||||||
github.com/casdoor/goth v1.69.0-FIX1 h1:24Y3tfaJxWGJbxickGe3F9y2c8X1PgsQynhxGXV1f9Q=
|
github.com/casdoor/goth v1.69.0-FIX1 h1:24Y3tfaJxWGJbxickGe3F9y2c8X1PgsQynhxGXV1f9Q=
|
||||||
github.com/casdoor/goth v1.69.0-FIX1/go.mod h1:Om55nRo8CkeDkPSNBbzXW4G5uI28ZUkSk5S69dPek3s=
|
github.com/casdoor/goth v1.69.0-FIX1/go.mod h1:Om55nRo8CkeDkPSNBbzXW4G5uI28ZUkSk5S69dPek3s=
|
||||||
|
github.com/casdoor/oss v1.2.0 h1:ozLAE+nnNdFQBWbzH8U9spzaO8h8NrB57lBcdyMUUQ8=
|
||||||
|
github.com/casdoor/oss v1.2.0/go.mod h1:qii35VBuxnR/uEuYSKpS0aJ8htQFOcCVsZ4FHgHLuss=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
@@ -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.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
|
||||||
|
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw=
|
github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw=
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8=
|
github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8=
|
||||||
@@ -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-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
github.com/go-pay/gopay v1.5.72 h1:3zm64xMBhJBa8rXbm//q5UiGgOa4WO5XYEnU394N2Zw=
|
github.com/go-pay/gopay v1.5.72 h1:3zm64xMBhJBa8rXbm//q5UiGgOa4WO5XYEnU394N2Zw=
|
||||||
github.com/go-pay/gopay v1.5.72/go.mod h1:0qOGIJuFW7PKDOjmecwKyW0mgsVImgwB9yPJj0ilpn8=
|
github.com/go-pay/gopay v1.5.72/go.mod h1:0qOGIJuFW7PKDOjmecwKyW0mgsVImgwB9yPJj0ilpn8=
|
||||||
|
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||||
|
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||||
|
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||||
|
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||||
|
github.com/go-playground/validator/v10 v10.8.0/go.mod h1:9JhgTzTaE31GZDpH/HSvHiRJrJ3iKAgqqH0Bl/Ocjdk=
|
||||||
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||||
@@ -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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ=
|
github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ=
|
||||||
|
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||||
github.com/lestrrat-go/jwx v0.9.0 h1:Fnd0EWzTm0kFrBPzE/PEPp9nzllES5buMkksPMjEKpM=
|
github.com/lestrrat-go/jwx v0.9.0 h1:Fnd0EWzTm0kFrBPzE/PEPp9nzllES5buMkksPMjEKpM=
|
||||||
github.com/lestrrat-go/jwx v0.9.0/go.mod h1:iEoxlYfZjvoGpuWwxUz+eR5e6KTJGsaRcy/YNA/UnBk=
|
github.com/lestrrat-go/jwx v0.9.0/go.mod h1:iEoxlYfZjvoGpuWwxUz+eR5e6KTJGsaRcy/YNA/UnBk=
|
||||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
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 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
|
||||||
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
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 h1:DQw0ZP7NbNlFGcKbcE/IVSOAFzScxRtLpd0rLMzLhq0=
|
||||||
github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA=
|
github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA=
|
||||||
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911 h1:erppMjjp69Rertg1zlgRbLJH1u+eCmRPxKjMZ5I8/Ro=
|
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911 h1:erppMjjp69Rertg1zlgRbLJH1u+eCmRPxKjMZ5I8/Ro=
|
||||||
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To=
|
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To=
|
||||||
|
github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI=
|
||||||
|
github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
|
||||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
||||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
||||||
@@ -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/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||||
github.com/qiangmzsx/string-adapter/v2 v2.1.0 h1:q0y8TPa/sTwtriJPRe8gWL++PuZ+XbOUuvKU+hvtTYs=
|
github.com/qiangmzsx/string-adapter/v2 v2.1.0 h1:q0y8TPa/sTwtriJPRe8gWL++PuZ+XbOUuvKU+hvtTYs=
|
||||||
github.com/qiangmzsx/string-adapter/v2 v2.1.0/go.mod h1:PElPB7b7HnGKTsuADAffFpOQXHqjEGJz1+U1a6yR5wA=
|
github.com/qiangmzsx/string-adapter/v2 v2.1.0/go.mod h1:PElPB7b7HnGKTsuADAffFpOQXHqjEGJz1+U1a6yR5wA=
|
||||||
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76 h1:J2Xj92efYLxPl3BiibgEDEUiMsCBzwTurE/8JjD8CG4=
|
github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk=
|
||||||
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76/go.mod h1:JhtPzUhP5KGtCB2yksmxuYAD4hEWw4qGQJpucjsm3U0=
|
github.com/qiniu/go-sdk/v7 v7.12.1/go.mod h1:btsaOc8CA3hdVloULfFdDgDc+g4f3TDZEFsDY0BLE+w=
|
||||||
|
github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
@@ -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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.0.0-20220208233918-bba287dce954 h1:BkypuErRT9A9I/iljuaG3/zdMjd/J6m8tKKJQtGfSdA=
|
golang.org/x/crypto v0.0.0-20220208233918-bba287dce954 h1:BkypuErRT9A9I/iljuaG3/zdMjd/J6m8tKKJQtGfSdA=
|
||||||
golang.org/x/crypto v0.0.0-20220208233918-bba287dce954/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220208233918-bba287dce954/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
@@ -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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
@@ -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-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
|
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
|
||||||
|
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@@ -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-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -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-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -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-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211020174200-9d6173849985/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
|
||||||
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
|
golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w=
|
||||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
@@ -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/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
||||||
xorm.io/core v0.7.2 h1:mEO22A2Z7a3fPaZMk6gKL/jMD80iiyNwRrX5HOv3XLw=
|
xorm.io/core v0.7.2 h1:mEO22A2Z7a3fPaZMk6gKL/jMD80iiyNwRrX5HOv3XLw=
|
||||||
xorm.io/core v0.7.2/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM=
|
xorm.io/core v0.7.2/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM=
|
||||||
xorm.io/xorm v1.0.3 h1:3dALAohvINu2mfEix5a5x5ZmSVGSljinoSGgvGbaZp0=
|
|
||||||
xorm.io/xorm v1.0.3/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4=
|
xorm.io/xorm v1.0.3/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4=
|
||||||
|
xorm.io/xorm v1.0.4 h1:UBXA4I3NhiyjXfPqxXUkS2t5hMta9SSPATeMMaZg9oA=
|
||||||
|
xorm.io/xorm v1.0.4/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4=
|
||||||
|
221
idp/bilibili.go
Normal file
221
idp/bilibili.go
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package idp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BilibiliIdProvider struct {
|
||||||
|
Client *http.Client
|
||||||
|
Config *oauth2.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBilibiliIdProvider(clientId string, clientSecret string, redirectUrl string) *BilibiliIdProvider {
|
||||||
|
idp := &BilibiliIdProvider{}
|
||||||
|
|
||||||
|
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||||
|
idp.Config = config
|
||||||
|
|
||||||
|
return idp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *BilibiliIdProvider) SetHttpClient(client *http.Client) {
|
||||||
|
idp.Client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
|
||||||
|
func (idp *BilibiliIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||||
|
var endpoint = oauth2.Endpoint{
|
||||||
|
TokenURL: "https://api.bilibili.com/x/account-oauth2/v1/token",
|
||||||
|
AuthURL: "http://member.bilibili.com/arcopen/fn/user/account/info",
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = &oauth2.Config{
|
||||||
|
Scopes: []string{"", ""},
|
||||||
|
Endpoint: endpoint,
|
||||||
|
ClientID: clientId,
|
||||||
|
ClientSecret: clientSecret,
|
||||||
|
RedirectURL: redirectUrl,
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
type BilibiliProviderToken struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BilibiliIdProviderTokenResponse struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
TTL int `json:"ttl"`
|
||||||
|
Data BilibiliProviderToken `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "0",
|
||||||
|
"ttl": 1,
|
||||||
|
"data": {
|
||||||
|
"access_token": "d30bedaa4d8eb3128cf35ddc1030e27d",
|
||||||
|
"expires_in": 1630220614,
|
||||||
|
"refresh_token": "WxFDKwqScZIQDm4iWmKDvetyFugM6HkX"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
// GetToken use code get access_token (*operation of getting code ought to be done in front)
|
||||||
|
// get more detail via: https://openhome.bilibili.com/doc/4/eaf0e2b5-bde9-b9a0-9be1-019bb455701c
|
||||||
|
func (idp *BilibiliIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||||
|
pTokenParams := &struct {
|
||||||
|
ClientId string `json:"client_id"`
|
||||||
|
ClientSecret string `json:"client_secret"`
|
||||||
|
GrantType string `json:"grant_type"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
}{
|
||||||
|
idp.Config.ClientID,
|
||||||
|
idp.Config.ClientSecret,
|
||||||
|
"authorization_code",
|
||||||
|
code,
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := idp.postWithBody(pTokenParams, idp.Config.Endpoint.TokenURL)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &BilibiliIdProviderTokenResponse{}
|
||||||
|
err = json.Unmarshal(data, response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.Code != 0 {
|
||||||
|
return nil, fmt.Errorf("pToken.Errcode = %d, pToken.Errmsg = %s", response.Code, response.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
token := &oauth2.Token{
|
||||||
|
AccessToken: response.Data.AccessToken,
|
||||||
|
Expiry: time.Unix(time.Now().Unix()+int64(response.Data.ExpiresIn), 0),
|
||||||
|
RefreshToken: response.Data.RefreshToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "0",
|
||||||
|
"ttl": 1,
|
||||||
|
"data": {
|
||||||
|
"name":"bilibili",
|
||||||
|
"face":"http://i0.hdslb.com/bfs/face/e1c99895a9f9df4f260a70dc7e227bcb46cf319c.jpg",
|
||||||
|
"openid":"9205eeaa1879skxys969ed47874f225c3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
type BilibiliUserInfo struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Face string `json:"face"`
|
||||||
|
OpenId string `json:"openid`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BilibiliUserInfoResponse struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
TTL int `json:"ttl"`
|
||||||
|
Data BilibiliUserInfo `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserInfo Use access_token to get UserInfo
|
||||||
|
// get more detail via: https://openhome.bilibili.com/doc/4/feb66f99-7d87-c206-00e7-d84164cd701c
|
||||||
|
func (idp *BilibiliIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||||
|
accessToken := token.AccessToken
|
||||||
|
clientId := idp.Config.ClientID
|
||||||
|
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("client_id", clientId)
|
||||||
|
params.Add("access_token", accessToken)
|
||||||
|
|
||||||
|
userInfoUrl := fmt.Sprintf("%s?%s", idp.Config.Endpoint.AuthURL, params.Encode())
|
||||||
|
|
||||||
|
resp, err := idp.Client.Get(userInfoUrl)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bUserInfoResponse := &BilibiliUserInfoResponse{}
|
||||||
|
if err = json.Unmarshal(data, bUserInfoResponse); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if bUserInfoResponse.Code != 0 {
|
||||||
|
return nil, fmt.Errorf("userinfo.Errcode = %d, userinfo.Errmsg = %s", bUserInfoResponse.Code, bUserInfoResponse.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo := &UserInfo{
|
||||||
|
Id: bUserInfoResponse.Data.OpenId,
|
||||||
|
Username: bUserInfoResponse.Data.Name,
|
||||||
|
DisplayName: bUserInfoResponse.Data.Name,
|
||||||
|
AvatarUrl: bUserInfoResponse.Data.Face,
|
||||||
|
}
|
||||||
|
|
||||||
|
return userInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *BilibiliIdProvider) postWithBody(body interface{}, url string) ([]byte, error) {
|
||||||
|
bs, err := json.Marshal(body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r := strings.NewReader(string(bs))
|
||||||
|
resp, err := idp.Client.Post(url, "application/json;charset=UTF-8", r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func(Body io.ReadCloser) {
|
||||||
|
err := Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}(resp.Body)
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
@@ -131,6 +131,7 @@ func (idp *CasdoorIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
data, err := ioutil.ReadAll(resp.Body)
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
109
idp/custom.go
Normal file
109
idp/custom.go
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package idp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
_ "net/url"
|
||||||
|
_ "time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CustomIdProvider struct {
|
||||||
|
Client *http.Client
|
||||||
|
Config *oauth2.Config
|
||||||
|
UserInfoUrl string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCustomIdProvider(clientId string, clientSecret string, redirectUrl string, authUrl string, tokenUrl string, userInfoUrl string) *CustomIdProvider {
|
||||||
|
idp := &CustomIdProvider{}
|
||||||
|
idp.UserInfoUrl = userInfoUrl
|
||||||
|
|
||||||
|
var config = &oauth2.Config{
|
||||||
|
ClientID: clientId,
|
||||||
|
ClientSecret: clientSecret,
|
||||||
|
RedirectURL: redirectUrl,
|
||||||
|
Endpoint: oauth2.Endpoint{
|
||||||
|
AuthURL: authUrl,
|
||||||
|
TokenURL: tokenUrl,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
idp.Config = config
|
||||||
|
|
||||||
|
return idp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *CustomIdProvider) SetHttpClient(client *http.Client) {
|
||||||
|
idp.Client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *CustomIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||||
|
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, idp.Client)
|
||||||
|
return idp.Config.Exchange(ctx, code)
|
||||||
|
}
|
||||||
|
|
||||||
|
type CustomUserInfo struct {
|
||||||
|
Id string `json:"sub"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
DisplayName string `json:"preferred_username"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
AvatarUrl string `json:"picture"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *CustomIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||||
|
ctUserinfo := &CustomUserInfo{}
|
||||||
|
accessToken := token.AccessToken
|
||||||
|
request, err := http.NewRequest("GET", idp.UserInfoUrl, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
//add accessToken to request header
|
||||||
|
request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", accessToken))
|
||||||
|
resp, err := idp.Client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(data, ctUserinfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctUserinfo.Status != "" {
|
||||||
|
return nil, fmt.Errorf("err: %s", ctUserinfo.Msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo := &UserInfo{
|
||||||
|
Id: ctUserinfo.Id,
|
||||||
|
Username: ctUserinfo.Name,
|
||||||
|
DisplayName: ctUserinfo.DisplayName,
|
||||||
|
Email: ctUserinfo.Email,
|
||||||
|
AvatarUrl: ctUserinfo.AvatarUrl,
|
||||||
|
}
|
||||||
|
return userInfo, nil
|
||||||
|
}
|
@@ -143,6 +143,7 @@ func (idp *DingTalkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
data, err := ioutil.ReadAll(resp.Body)
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
198
idp/douyin.go
Normal file
198
idp/douyin.go
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package idp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DouyinIdProvider struct {
|
||||||
|
Client *http.Client
|
||||||
|
Config *oauth2.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDouyinIdProvider(clientId string, clientSecret string, redirectUrl string) *DouyinIdProvider {
|
||||||
|
idp := &DouyinIdProvider{}
|
||||||
|
idp.Config = idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||||
|
return idp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *DouyinIdProvider) SetHttpClient(client *http.Client) {
|
||||||
|
idp.Client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *DouyinIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||||
|
var endpoint = oauth2.Endpoint{
|
||||||
|
TokenURL: "https://open.douyin.com/oauth/access_token",
|
||||||
|
AuthURL: "https://open.douyin.com/platform/oauth/connect",
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = &oauth2.Config{
|
||||||
|
Scopes: []string{"user_info"},
|
||||||
|
Endpoint: endpoint,
|
||||||
|
ClientID: clientId,
|
||||||
|
ClientSecret: clientSecret,
|
||||||
|
RedirectURL: redirectUrl,
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
// get more details via: https://open.douyin.com/platform/doc?doc=docs/openapi/account-permission/get-access-token
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"access_token": "access_token",
|
||||||
|
"description": "",
|
||||||
|
"error_code": "0",
|
||||||
|
"expires_in": "86400",
|
||||||
|
"open_id": "aaa-bbb-ccc",
|
||||||
|
"refresh_expires_in": "86400",
|
||||||
|
"refresh_token": "refresh_token",
|
||||||
|
"scope": "user_info"
|
||||||
|
},
|
||||||
|
"message": "<nil>"
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
type DouyinTokenResp struct {
|
||||||
|
Data struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
ExpiresIn int64 `json:"expires_in"`
|
||||||
|
OpenId string `json:"open_id"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
Scope string `json:"scope"`
|
||||||
|
} `json:"data"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetToken use code to get access_token
|
||||||
|
// get more details via: https://open.douyin.com/platform/doc?doc=docs/openapi/account-permission/get-access-token
|
||||||
|
func (idp *DouyinIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||||
|
payload := url.Values{}
|
||||||
|
payload.Set("code", code)
|
||||||
|
payload.Set("grant_type", "authorization_code")
|
||||||
|
payload.Set("client_key", idp.Config.ClientID)
|
||||||
|
payload.Set("client_secret", idp.Config.ClientSecret)
|
||||||
|
resp, err := idp.Client.PostForm(idp.Config.Endpoint.TokenURL, payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tokenResp := &DouyinTokenResp{}
|
||||||
|
err = json.Unmarshal(data, tokenResp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("fail to unmarshal token response: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
token := &oauth2.Token{
|
||||||
|
AccessToken: tokenResp.Data.AccessToken,
|
||||||
|
RefreshToken: tokenResp.Data.RefreshToken,
|
||||||
|
Expiry: time.Unix(time.Now().Unix()+tokenResp.Data.ExpiresIn, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
raw := make(map[string]interface{})
|
||||||
|
raw["open_id"] = tokenResp.Data.OpenId
|
||||||
|
token = token.WithExtra(raw)
|
||||||
|
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// get more details via: https://open.douyin.com/platform/doc?doc=docs/openapi/account-management/get-account-open-info
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"avatar": "https://example.com/x.jpeg",
|
||||||
|
"city": "上海",
|
||||||
|
"country": "中国",
|
||||||
|
"description": "",
|
||||||
|
"e_account_role": "<nil>",
|
||||||
|
"error_code": "0",
|
||||||
|
"gender": "<nil>",
|
||||||
|
"nickname": "张伟",
|
||||||
|
"open_id": "0da22181-d833-447f-995f-1beefea5bef3",
|
||||||
|
"province": "上海",
|
||||||
|
"union_id": "1ad4e099-4a0c-47d1-a410-bffb4f2f64a4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
type DouyinUserInfo struct {
|
||||||
|
Data struct {
|
||||||
|
Avatar string `json:"avatar"`
|
||||||
|
City string `json:"city"`
|
||||||
|
Country string `json:"country"`
|
||||||
|
// 0->unknown, 1->male, 2->female
|
||||||
|
Gender int64 `json:"gender"`
|
||||||
|
Nickname string `json:"nickname"`
|
||||||
|
OpenId string `json:"open_id"`
|
||||||
|
Province string `json:"province"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserInfo use token to get user profile
|
||||||
|
func (idp *DouyinIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||||
|
body := &struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
OpenId string `json:"open_id"`
|
||||||
|
}{token.AccessToken, token.Extra("open_id").(string)}
|
||||||
|
data, err := json.Marshal(body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest("GET", "https://open.douyin.com/oauth/userinfo/", bytes.NewReader(data))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Add("access-token", token.AccessToken)
|
||||||
|
req.Header.Add("Accept", "application/json")
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
|
resp, err := idp.Client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
respBody, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var douyinUserInfo DouyinUserInfo
|
||||||
|
err = json.Unmarshal(respBody, &douyinUserInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo := UserInfo{
|
||||||
|
Id: douyinUserInfo.Data.OpenId,
|
||||||
|
Username: douyinUserInfo.Data.Nickname,
|
||||||
|
DisplayName: douyinUserInfo.Data.Nickname,
|
||||||
|
AvatarUrl: douyinUserInfo.Data.Avatar,
|
||||||
|
}
|
||||||
|
return &userInfo, nil
|
||||||
|
}
|
@@ -169,8 +169,11 @@ func (idp *LarkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
|||||||
req.Header.Set("Authorization", "Bearer "+token.AccessToken)
|
req.Header.Set("Authorization", "Bearer "+token.AccessToken)
|
||||||
|
|
||||||
resp, err := idp.Client.Do(req)
|
resp, err := idp.Client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
data, err = ioutil.ReadAll(resp.Body)
|
data, err = ioutil.ReadAll(resp.Body)
|
||||||
err = resp.Body.Close()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
200
idp/okta.go
Normal file
200
idp/okta.go
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package idp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OktaIdProvider struct {
|
||||||
|
Client *http.Client
|
||||||
|
Config *oauth2.Config
|
||||||
|
Host string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOktaIdProvider(clientId string, clientSecret string, redirectUrl string, hostUrl string) *OktaIdProvider {
|
||||||
|
idp := &OktaIdProvider{}
|
||||||
|
|
||||||
|
config := idp.getConfig(hostUrl, clientId, clientSecret, redirectUrl)
|
||||||
|
config.ClientID = clientId
|
||||||
|
config.ClientSecret = clientSecret
|
||||||
|
config.RedirectURL = redirectUrl
|
||||||
|
idp.Config = config
|
||||||
|
idp.Host = hostUrl
|
||||||
|
return idp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *OktaIdProvider) SetHttpClient(client *http.Client) {
|
||||||
|
idp.Client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *OktaIdProvider) getConfig(hostUrl string, clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||||
|
var endpoint = oauth2.Endpoint{
|
||||||
|
TokenURL: fmt.Sprintf("%s/v1/token", hostUrl),
|
||||||
|
AuthURL: fmt.Sprintf("%s/v1/authorize", hostUrl),
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = &oauth2.Config{
|
||||||
|
// openid is required for authentication requests
|
||||||
|
// get more details via: https://developer.okta.com/docs/reference/api/oidc/#reserved-scopes
|
||||||
|
Scopes: []string{"openid", "profile", "email"},
|
||||||
|
Endpoint: endpoint,
|
||||||
|
ClientID: clientId,
|
||||||
|
ClientSecret: clientSecret,
|
||||||
|
RedirectURL: redirectUrl,
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
// get more details via: https://developer.okta.com/docs/reference/api/oidc/#token
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"access_token" : "eyJhbGciOiJSUzI1NiJ9.eyJ2ZXIiOjEsImlzcyI6Imh0dHA6Ly9yYWluLm9rdGExLmNvbToxODAyIiwiaWF0IjoxNDQ5Nj
|
||||||
|
I0MDI2LCJleHAiOjE0NDk2Mjc2MjYsImp0aSI6IlVmU0lURzZCVVNfdHA3N21BTjJxIiwic2NvcGVzIjpbIm9wZW5pZCIsI
|
||||||
|
mVtYWlsIl0sImNsaWVudF9pZCI6InVBYXVub2ZXa2FESnh1a0NGZUJ4IiwidXNlcl9pZCI6IjAwdWlkNEJ4WHc2STZUVjRt
|
||||||
|
MGczIn0.HaBu5oQxdVCIvea88HPgr2O5evqZlCT4UXH4UKhJnZ5px-ArNRqwhxXWhHJisslswjPpMkx1IgrudQIjzGYbtLF
|
||||||
|
jrrg2ueiU5-YfmKuJuD6O2yPWGTsV7X6i7ABT6P-t8PRz_RNbk-U1GXWIEkNnEWbPqYDAm_Ofh7iW0Y8WDA5ez1jbtMvd-o
|
||||||
|
XMvJLctRiACrTMLJQ2e5HkbUFxgXQ_rFPNHJbNSUBDLqdi2rg_ND64DLRlXRY7hupNsvWGo0gF4WEUk8IZeaLjKw8UoIs-E
|
||||||
|
TEwJlAMcvkhoVVOsN5dPAaEKvbyvPC1hUGXb4uuThlwdD3ECJrtwgKqLqcWonNtiw",
|
||||||
|
"token_type" : "Bearer",
|
||||||
|
"expires_in" : 3600,
|
||||||
|
"scope" : "openid email",
|
||||||
|
"refresh_token" : "a9VpZDRCeFh3Nkk2VdY",
|
||||||
|
"id_token" : "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIwMHVpZDRCeFh3Nkk2VFY0bTBnMyIsImVtYWlsIjoid2VibWFzdGVyQGNsb3VkaXR1ZG
|
||||||
|
UubmV0IiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInZlciI6MSwiaXNzIjoiaHR0cDovL3JhaW4ub2t0YTEuY29tOjE4MDIiLCJsb
|
||||||
|
2dpbiI6ImFkbWluaXN0cmF0b3IxQGNsb3VkaXR1ZGUubmV0IiwiYXVkIjoidUFhdW5vZldrYURKeHVrQ0ZlQngiLCJpYXQiOjE0
|
||||||
|
NDk2MjQwMjYsImV4cCI6MTQ0OTYyNzYyNiwiYW1yIjpbInB3ZCJdLCJqdGkiOiI0ZUFXSk9DTUIzU1g4WGV3RGZWUiIsImF1dGh
|
||||||
|
fdGltZSI6MTQ0OTYyNDAyNiwiYXRfaGFzaCI6ImNwcUtmZFFBNWVIODkxRmY1b0pyX1EifQ.Btw6bUbZhRa89DsBb8KmL9rfhku
|
||||||
|
--_mbNC2pgC8yu8obJnwO12nFBepui9KzbpJhGM91PqJwi_AylE6rp-ehamfnUAO4JL14PkemF45Pn3u_6KKwxJnxcWxLvMuuis
|
||||||
|
nvIs7NScKpOAab6ayZU0VL8W6XAijQmnYTtMWQfSuaaR8rYOaWHrffh3OypvDdrQuYacbkT0csxdrayXfBG3UF5-ZAlhfch1fhF
|
||||||
|
T3yZFdWwzkSDc0BGygfiFyNhCezfyT454wbciSZgrA9ROeHkfPCaX7KCFO8GgQEkGRoQntFBNjluFhNLJIUkEFovEDlfuB4tv_M
|
||||||
|
8BM75celdy3jkpOurg"
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
type OktaToken struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
Scope string `json:"scope"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
IdToken string `json:"id_token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetToken use code to get access_token
|
||||||
|
// get more details via: https://developer.okta.com/docs/reference/api/oidc/#token
|
||||||
|
func (idp *OktaIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||||
|
payload := url.Values{}
|
||||||
|
payload.Set("code", code)
|
||||||
|
payload.Set("grant_type", "authorization_code")
|
||||||
|
payload.Set("client_id", idp.Config.ClientID)
|
||||||
|
payload.Set("client_secret", idp.Config.ClientSecret)
|
||||||
|
payload.Set("redirect_uri", idp.Config.RedirectURL)
|
||||||
|
resp, err := idp.Client.PostForm(idp.Config.Endpoint.TokenURL, payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pToken := &OktaToken{}
|
||||||
|
err = json.Unmarshal(data, pToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("fail to unmarshal token response: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
token := &oauth2.Token{
|
||||||
|
AccessToken: pToken.AccessToken,
|
||||||
|
TokenType: "Bearer",
|
||||||
|
RefreshToken: pToken.RefreshToken,
|
||||||
|
Expiry: time.Unix(time.Now().Unix()+int64(pToken.ExpiresIn), 0),
|
||||||
|
}
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// get more details via: https://developer.okta.com/docs/reference/api/oidc/#userinfo
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"sub": "00uid4BxXw6I6TV4m0g3",
|
||||||
|
"name" :"John Doe",
|
||||||
|
"nickname":"Jimmy",
|
||||||
|
"given_name":"John",
|
||||||
|
"middle_name":"James",
|
||||||
|
"family_name":"Doe",
|
||||||
|
"profile":"https://example.com/john.doe",
|
||||||
|
"zoneinfo":"America/Los_Angeles",
|
||||||
|
"locale":"en-US",
|
||||||
|
"updated_at":1311280970,
|
||||||
|
"email":"john.doe@example.com",
|
||||||
|
"email_verified":true,
|
||||||
|
"address" : { "street_address":"123 Hollywood Blvd.", "locality":"Los Angeles", "region":"CA", "postal_code":"90210", "country":"US" },
|
||||||
|
"phone_number":"+1 (425) 555-1212"
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
type OktaUserInfo struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
PreferredUsername string `json:"preferred_username"`
|
||||||
|
Picture string `json:"picture"`
|
||||||
|
Sub string `json:"sub"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserInfo use token to get user profile
|
||||||
|
// get more details via: https://developer.okta.com/docs/reference/api/oidc/#userinfo
|
||||||
|
func (idp *OktaIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||||
|
req, err := http.NewRequest("GET", fmt.Sprintf("%s/v1/userinfo", idp.Host), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token.AccessToken))
|
||||||
|
req.Header.Add("Accept", "application/json")
|
||||||
|
resp, err := idp.Client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var oktaUserInfo OktaUserInfo
|
||||||
|
err = json.Unmarshal(body, &oktaUserInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo := UserInfo{
|
||||||
|
Id: oktaUserInfo.Sub,
|
||||||
|
Username: oktaUserInfo.PreferredUsername,
|
||||||
|
DisplayName: oktaUserInfo.Name,
|
||||||
|
Email: oktaUserInfo.Email,
|
||||||
|
AvatarUrl: oktaUserInfo.Picture,
|
||||||
|
}
|
||||||
|
return &userInfo, nil
|
||||||
|
}
|
@@ -35,7 +35,7 @@ type IdProvider interface {
|
|||||||
GetUserInfo(token *oauth2.Token) (*UserInfo, error)
|
GetUserInfo(token *oauth2.Token) (*UserInfo, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetIdProvider(typ string, subType string, clientId string, clientSecret string, appId string, redirectUrl string, hostUrl string) IdProvider {
|
func GetIdProvider(typ string, subType string, clientId string, clientSecret string, appId string, redirectUrl string, hostUrl string, authUrl string, tokenUrl string, userInfoUrl string) IdProvider {
|
||||||
if typ == "GitHub" {
|
if typ == "GitHub" {
|
||||||
return NewGithubIdProvider(clientId, clientSecret, redirectUrl)
|
return NewGithubIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
} else if typ == "Google" {
|
} else if typ == "Google" {
|
||||||
@@ -72,6 +72,8 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
|
|||||||
return NewBaiduIdProvider(clientId, clientSecret, redirectUrl)
|
return NewBaiduIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
} else if typ == "Alipay" {
|
} else if typ == "Alipay" {
|
||||||
return NewAlipayIdProvider(clientId, clientSecret, redirectUrl)
|
return NewAlipayIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
|
} else if typ == "Custom" {
|
||||||
|
return NewCustomIdProvider(clientId, clientSecret, redirectUrl, authUrl, tokenUrl, userInfoUrl)
|
||||||
} else if typ == "Infoflow" {
|
} else if typ == "Infoflow" {
|
||||||
if subType == "Internal" {
|
if subType == "Internal" {
|
||||||
return NewInfoflowInternalIdProvider(clientId, clientSecret, appId, redirectUrl)
|
return NewInfoflowInternalIdProvider(clientId, clientSecret, appId, redirectUrl)
|
||||||
@@ -82,8 +84,14 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
|
|||||||
}
|
}
|
||||||
} else if typ == "Casdoor" {
|
} else if typ == "Casdoor" {
|
||||||
return NewCasdoorIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
|
return NewCasdoorIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
|
||||||
|
} else if typ == "Okta" {
|
||||||
|
return NewOktaIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
|
||||||
|
} else if typ == "Douyin" {
|
||||||
|
return NewDouyinIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
} else if isGothSupport(typ) {
|
} else if isGothSupport(typ) {
|
||||||
return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl)
|
return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl)
|
||||||
|
} else if typ == "Bilibili" {
|
||||||
|
return NewBilibiliIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
5
main.go
5
main.go
@@ -27,10 +27,11 @@ import (
|
|||||||
"github.com/casdoor/casdoor/proxy"
|
"github.com/casdoor/casdoor/proxy"
|
||||||
"github.com/casdoor/casdoor/routers"
|
"github.com/casdoor/casdoor/routers"
|
||||||
_ "github.com/casdoor/casdoor/routers"
|
_ "github.com/casdoor/casdoor/routers"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
createDatabase := flag.Bool("createDatabase", false, "true if you need casdoor to create database")
|
createDatabase := flag.Bool("createDatabase", false, "true if you need Casdoor to create database")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
object.InitAdapter(*createDatabase)
|
object.InitAdapter(*createDatabase)
|
||||||
@@ -40,7 +41,7 @@ func main() {
|
|||||||
proxy.InitHttpClient()
|
proxy.InitHttpClient()
|
||||||
authz.InitAuthz()
|
authz.InitAuthz()
|
||||||
|
|
||||||
go object.RunSyncUsersJob()
|
util.SafeGoroutine(func() {object.RunSyncUsersJob()})
|
||||||
|
|
||||||
//beego.DelStaticPath("/static")
|
//beego.DelStaticPath("/static")
|
||||||
beego.SetStaticPath("/static", "web/build/static")
|
beego.SetStaticPath("/static", "web/build/static")
|
||||||
|
@@ -138,6 +138,11 @@ func (a *Adapter) createTable() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = a.Engine.Sync2(new(Model))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
err = a.Engine.Sync2(new(Provider))
|
err = a.Engine.Sync2(new(Provider))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@@ -22,6 +22,14 @@ import (
|
|||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type SignupItem struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Visible bool `json:"visible"`
|
||||||
|
Required bool `json:"required"`
|
||||||
|
Prompted bool `json:"prompted"`
|
||||||
|
Rule string `json:"rule"`
|
||||||
|
}
|
||||||
|
|
||||||
type Application struct {
|
type Application struct {
|
||||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||||
@@ -257,7 +265,11 @@ func UpdateApplication(id string, application *Application) bool {
|
|||||||
providerItem.Provider = nil
|
providerItem.Provider = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(application)
|
session := adapter.Engine.ID(core.PK{owner, name}).AllCols()
|
||||||
|
if application.ClientSecret == "***" {
|
||||||
|
session.Omit("client_secret")
|
||||||
|
}
|
||||||
|
affected, err := session.Update(application)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@@ -17,6 +17,7 @@ package object
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/cred"
|
"github.com/casdoor/casdoor/cred"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
@@ -195,3 +196,37 @@ func CheckUserPassword(organization string, username string, password string) (*
|
|||||||
func filterField(field string) bool {
|
func filterField(field string) bool {
|
||||||
return reFieldWhiteList.MatchString(field)
|
return reFieldWhiteList.MatchString(field)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CheckUserPermission(requestUserId, userId string, strict bool) (bool, error) {
|
||||||
|
if requestUserId == "" {
|
||||||
|
return false, fmt.Errorf("please login first")
|
||||||
|
}
|
||||||
|
|
||||||
|
targetUser := GetUser(userId)
|
||||||
|
if targetUser == nil {
|
||||||
|
return false, fmt.Errorf("the user: %s doesn't exist", userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
hasPermission := false
|
||||||
|
if strings.HasPrefix(requestUserId, "app/") {
|
||||||
|
hasPermission = true
|
||||||
|
} else {
|
||||||
|
requestUser := GetUser(requestUserId)
|
||||||
|
if requestUser == nil {
|
||||||
|
return false, fmt.Errorf("session outdated, please login again")
|
||||||
|
}
|
||||||
|
if requestUser.IsGlobalAdmin {
|
||||||
|
hasPermission = true
|
||||||
|
} else if requestUserId == userId {
|
||||||
|
hasPermission = true
|
||||||
|
} else if targetUser.Owner == requestUser.Owner {
|
||||||
|
if strict {
|
||||||
|
hasPermission = requestUser.IsAdmin
|
||||||
|
} else {
|
||||||
|
hasPermission = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasPermission, fmt.Errorf("you don't have the permission to do this")
|
||||||
|
}
|
@@ -47,6 +47,31 @@ func initBuiltInOrganization() bool {
|
|||||||
PhonePrefix: "86",
|
PhonePrefix: "86",
|
||||||
DefaultAvatar: "https://casbin.org/img/casbin.svg",
|
DefaultAvatar: "https://casbin.org/img/casbin.svg",
|
||||||
Tags: []string{},
|
Tags: []string{},
|
||||||
|
AccountItems: []*AccountItem{
|
||||||
|
{Name: "Organization", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||||
|
{Name: "ID", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
|
||||||
|
{Name: "Name", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||||
|
{Name: "Display name", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||||
|
{Name: "Avatar", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||||
|
{Name: "User type", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||||
|
{Name: "Password", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||||
|
{Name: "Email", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||||
|
{Name: "Phone", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||||
|
{Name: "Country/Region", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||||
|
{Name: "Location", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||||
|
{Name: "Affiliation", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||||
|
{Name: "Title", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||||
|
{Name: "Homepage", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||||
|
{Name: "Bio", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||||
|
{Name: "Tag", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||||
|
{Name: "Signup application", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||||
|
{Name: "3rd-party logins", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||||
|
{Name: "Properties", Visible: false, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||||
|
{Name: "Is admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||||
|
{Name: "Is global admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||||
|
{Name: "Is forbidden", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||||
|
{Name: "Is deleted", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
AddOrganization(organization)
|
AddOrganization(organization)
|
||||||
return false
|
return false
|
||||||
@@ -109,7 +134,7 @@ func initBuiltInApplication() {
|
|||||||
{Name: "Display name", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
{Name: "Display name", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
||||||
{Name: "Password", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
{Name: "Password", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
||||||
{Name: "Confirm password", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
{Name: "Confirm password", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
||||||
{Name: "Email", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
{Name: "Email", Visible: true, Required: true, Prompted: false, Rule: "Normal"},
|
||||||
{Name: "Phone", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
{Name: "Phone", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
||||||
{Name: "Agreement", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
{Name: "Agreement", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
||||||
},
|
},
|
||||||
@@ -147,7 +172,7 @@ func initBuiltInCert() {
|
|||||||
DisplayName: "Built-in Cert",
|
DisplayName: "Built-in Cert",
|
||||||
Scope: "JWT",
|
Scope: "JWT",
|
||||||
Type: "x509",
|
Type: "x509",
|
||||||
CryptoAlgorithm: "RSA",
|
CryptoAlgorithm: "RS256",
|
||||||
BitSize: 4096,
|
BitSize: 4096,
|
||||||
ExpireInYears: 20,
|
ExpireInYears: 20,
|
||||||
PublicKey: tokenJwtPublicKey,
|
PublicKey: tokenJwtPublicKey,
|
||||||
|
105
object/ldap.go
105
object/ldap.go
@@ -19,6 +19,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
goldap "github.com/go-ldap/ldap/v3"
|
goldap "github.com/go-ldap/ldap/v3"
|
||||||
"github.com/thanhpk/randstr"
|
"github.com/thanhpk/randstr"
|
||||||
@@ -42,6 +43,7 @@ type Ldap struct {
|
|||||||
|
|
||||||
type ldapConn struct {
|
type ldapConn struct {
|
||||||
Conn *goldap.Conn
|
Conn *goldap.Conn
|
||||||
|
IsAD bool
|
||||||
}
|
}
|
||||||
|
|
||||||
//type ldapGroup struct {
|
//type ldapGroup struct {
|
||||||
@@ -78,6 +80,13 @@ type LdapRespUser struct {
|
|||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ldapServerType struct {
|
||||||
|
Vendorname string
|
||||||
|
Vendorversion string
|
||||||
|
IsGlobalCatalogReady string
|
||||||
|
ForestFunctionality string
|
||||||
|
}
|
||||||
|
|
||||||
func LdapUsersToLdapRespUsers(users []ldapUser) []LdapRespUser {
|
func LdapUsersToLdapRespUsers(users []ldapUser) []LdapRespUser {
|
||||||
returnAnyNotEmpty := func(strs ...string) string {
|
returnAnyNotEmpty := func(strs ...string) string {
|
||||||
for _, str := range strs {
|
for _, str := range strs {
|
||||||
@@ -104,6 +113,45 @@ func LdapUsersToLdapRespUsers(users []ldapUser) []LdapRespUser {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isMicrosoftAD(Conn *goldap.Conn) (bool, error) {
|
||||||
|
SearchFilter := "(objectclass=*)"
|
||||||
|
SearchAttributes := []string{"vendorname", "vendorversion", "isGlobalCatalogReady", "forestFunctionality"}
|
||||||
|
|
||||||
|
searchReq := goldap.NewSearchRequest("",
|
||||||
|
goldap.ScopeBaseObject, goldap.NeverDerefAliases, 0, 0, false,
|
||||||
|
SearchFilter, SearchAttributes, nil)
|
||||||
|
searchResult, err := Conn.Search(searchReq)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if len(searchResult.Entries) == 0 {
|
||||||
|
return false, errors.New("no result")
|
||||||
|
}
|
||||||
|
isMicrosoft := false
|
||||||
|
var ldapServerType ldapServerType
|
||||||
|
for _, entry := range searchResult.Entries {
|
||||||
|
for _, attribute := range entry.Attributes {
|
||||||
|
switch attribute.Name {
|
||||||
|
case "vendorname":
|
||||||
|
ldapServerType.Vendorname = attribute.Values[0]
|
||||||
|
case "vendorversion":
|
||||||
|
ldapServerType.Vendorversion = attribute.Values[0]
|
||||||
|
case "isGlobalCatalogReady":
|
||||||
|
ldapServerType.IsGlobalCatalogReady = attribute.Values[0]
|
||||||
|
case "forestFunctionality":
|
||||||
|
ldapServerType.ForestFunctionality = attribute.Values[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ldapServerType.Vendorname == "" &&
|
||||||
|
ldapServerType.Vendorversion == "" &&
|
||||||
|
ldapServerType.IsGlobalCatalogReady == "TRUE" &&
|
||||||
|
ldapServerType.ForestFunctionality != "" {
|
||||||
|
isMicrosoft = true
|
||||||
|
}
|
||||||
|
return isMicrosoft, err
|
||||||
|
}
|
||||||
|
|
||||||
func GetLdapConn(host string, port int, adminUser string, adminPasswd string) (*ldapConn, error) {
|
func GetLdapConn(host string, port int, adminUser string, adminPasswd string) (*ldapConn, error) {
|
||||||
conn, err := goldap.Dial("tcp", fmt.Sprintf("%s:%d", host, port))
|
conn, err := goldap.Dial("tcp", fmt.Sprintf("%s:%d", host, port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -115,7 +163,11 @@ func GetLdapConn(host string, port int, adminUser string, adminPasswd string) (*
|
|||||||
return nil, fmt.Errorf("fail to login Ldap server with [%s]", adminUser)
|
return nil, fmt.Errorf("fail to login Ldap server with [%s]", adminUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ldapConn{Conn: conn}, nil
|
isAD, err := isMicrosoftAD(conn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("fail to get Ldap server type [%s]", adminUser)
|
||||||
|
}
|
||||||
|
return &ldapConn{Conn: conn, IsAD: isAD}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//FIXME: The Base DN does not necessarily contain the Group
|
//FIXME: The Base DN does not necessarily contain the Group
|
||||||
@@ -158,10 +210,19 @@ func (l *ldapConn) GetLdapUsers(baseDn string) ([]ldapUser, error) {
|
|||||||
SearchFilter := "(objectClass=posixAccount)"
|
SearchFilter := "(objectClass=posixAccount)"
|
||||||
SearchAttributes := []string{"uidNumber", "uid", "cn", "gidNumber", "entryUUID", "mail", "email",
|
SearchAttributes := []string{"uidNumber", "uid", "cn", "gidNumber", "entryUUID", "mail", "email",
|
||||||
"emailAddress", "telephoneNumber", "mobile", "mobileTelephoneNumber", "registeredAddress", "postalAddress"}
|
"emailAddress", "telephoneNumber", "mobile", "mobileTelephoneNumber", "registeredAddress", "postalAddress"}
|
||||||
|
SearchFilterMsAD := "(objectClass=user)"
|
||||||
searchReq := goldap.NewSearchRequest(baseDn,
|
SearchAttributesMsAD := []string{"uidNumber", "sAMAccountName", "cn", "gidNumber", "entryUUID", "mail", "email",
|
||||||
goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
|
"emailAddress", "telephoneNumber", "mobile", "mobileTelephoneNumber", "registeredAddress", "postalAddress"}
|
||||||
SearchFilter, SearchAttributes, nil)
|
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)
|
searchResult, err := l.Conn.SearchWithPaging(searchReq, 100)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -181,12 +242,16 @@ func (l *ldapConn) GetLdapUsers(baseDn string) ([]ldapUser, error) {
|
|||||||
ldapUserItem.UidNumber = attribute.Values[0]
|
ldapUserItem.UidNumber = attribute.Values[0]
|
||||||
case "uid":
|
case "uid":
|
||||||
ldapUserItem.Uid = attribute.Values[0]
|
ldapUserItem.Uid = attribute.Values[0]
|
||||||
|
case "sAMAccountName":
|
||||||
|
ldapUserItem.Uid = attribute.Values[0]
|
||||||
case "cn":
|
case "cn":
|
||||||
ldapUserItem.Cn = attribute.Values[0]
|
ldapUserItem.Cn = attribute.Values[0]
|
||||||
case "gidNumber":
|
case "gidNumber":
|
||||||
ldapUserItem.GidNumber = attribute.Values[0]
|
ldapUserItem.GidNumber = attribute.Values[0]
|
||||||
case "entryUUID":
|
case "entryUUID":
|
||||||
ldapUserItem.Uuid = attribute.Values[0]
|
ldapUserItem.Uuid = attribute.Values[0]
|
||||||
|
case "objectGUID":
|
||||||
|
ldapUserItem.Uuid = attribute.Values[0]
|
||||||
case "mail":
|
case "mail":
|
||||||
ldapUserItem.Mail = attribute.Values[0]
|
ldapUserItem.Mail = attribute.Values[0]
|
||||||
case "email":
|
case "email":
|
||||||
@@ -300,7 +365,7 @@ func DeleteLdap(ldap *Ldap) bool {
|
|||||||
return affected != 0
|
return affected != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func SyncLdapUsers(owner string, users []LdapRespUser) (*[]LdapRespUser, *[]LdapRespUser) {
|
func SyncLdapUsers(owner string, users []LdapRespUser, ldapId string) (*[]LdapRespUser, *[]LdapRespUser) {
|
||||||
var existUsers []LdapRespUser
|
var existUsers []LdapRespUser
|
||||||
var failedUsers []LdapRespUser
|
var failedUsers []LdapRespUser
|
||||||
var uuids []string
|
var uuids []string
|
||||||
@@ -311,6 +376,25 @@ func SyncLdapUsers(owner string, users []LdapRespUser) (*[]LdapRespUser, *[]Ldap
|
|||||||
|
|
||||||
existUuids := CheckLdapUuidExist(owner, uuids)
|
existUuids := CheckLdapUuidExist(owner, uuids)
|
||||||
|
|
||||||
|
organization := getOrganization("admin", owner)
|
||||||
|
ldap := GetLdap(ldapId)
|
||||||
|
|
||||||
|
var dc []string
|
||||||
|
for _, basedn := range strings.Split(ldap.BaseDn, ",") {
|
||||||
|
if strings.Contains(basedn, "dc=") {
|
||||||
|
dc = append(dc, basedn[3:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
affiliation := strings.Join(dc, ".")
|
||||||
|
|
||||||
|
var ou []string
|
||||||
|
for _, admin := range strings.Split(ldap.Admin, ",") {
|
||||||
|
if strings.Contains(admin, "ou=") {
|
||||||
|
ou = append(ou, admin[3:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tag := strings.Join(ou, ".")
|
||||||
|
|
||||||
for _, user := range users {
|
for _, user := range users {
|
||||||
found := false
|
found := false
|
||||||
if len(existUuids) > 0 {
|
if len(existUuids) > 0 {
|
||||||
@@ -325,15 +409,14 @@ func SyncLdapUsers(owner string, users []LdapRespUser) (*[]LdapRespUser, *[]Ldap
|
|||||||
Owner: owner,
|
Owner: owner,
|
||||||
Name: buildLdapUserName(user.Uid, user.UidNumber),
|
Name: buildLdapUserName(user.Uid, user.UidNumber),
|
||||||
CreatedTime: util.GetCurrentTime(),
|
CreatedTime: util.GetCurrentTime(),
|
||||||
Password: "123",
|
|
||||||
DisplayName: user.Cn,
|
DisplayName: user.Cn,
|
||||||
Avatar: "https://casbin.org/img/casbin.svg",
|
Avatar: organization.DefaultAvatar,
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
Phone: user.Phone,
|
Phone: user.Phone,
|
||||||
Address: []string{user.Address},
|
Address: []string{user.Address},
|
||||||
Affiliation: "Example Inc.",
|
Affiliation: affiliation,
|
||||||
Tag: "staff",
|
Tag: tag,
|
||||||
Score: 2000,
|
Score: beego.AppConfig.DefaultInt("initScore", 2000),
|
||||||
Ldap: user.Uuid,
|
Ldap: user.Uuid,
|
||||||
}) {
|
}) {
|
||||||
failedUsers = append(failedUsers, user)
|
failedUsers = append(failedUsers, user)
|
||||||
|
@@ -6,6 +6,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/astaxie/beego/logs"
|
"github.com/astaxie/beego/logs"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LdapAutoSynchronizer struct {
|
type LdapAutoSynchronizer struct {
|
||||||
@@ -47,7 +48,7 @@ func (l *LdapAutoSynchronizer) StartAutoSync(ldapId string) error {
|
|||||||
stopChan := make(chan struct{})
|
stopChan := make(chan struct{})
|
||||||
l.ldapIdToStopChan[ldapId] = stopChan
|
l.ldapIdToStopChan[ldapId] = stopChan
|
||||||
logs.Info(fmt.Sprintf("autoSync started for %s", ldap.Id))
|
logs.Info(fmt.Sprintf("autoSync started for %s", ldap.Id))
|
||||||
go l.syncRoutine(ldap, stopChan)
|
util.SafeGoroutine(func() {l.syncRoutine(ldap, stopChan)})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +86,7 @@ func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) {
|
|||||||
logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
|
logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
existed, failed := SyncLdapUsers(ldap.Owner, LdapUsersToLdapRespUsers(users))
|
existed, failed := SyncLdapUsers(ldap.Owner, LdapUsersToLdapRespUsers(users), ldap.Id)
|
||||||
if len(*failed) != 0 {
|
if len(*failed) != 0 {
|
||||||
logs.Warning(fmt.Sprintf("ldap autosync,%d new users,but %d user failed during :", len(users)-len(*existed)-len(*failed), len(*failed)), *failed)
|
logs.Warning(fmt.Sprintf("ldap autosync,%d new users,but %d user failed during :", len(users)-len(*existed)-len(*failed), len(*failed)), *failed)
|
||||||
} else {
|
} else {
|
||||||
|
122
object/model.go
Normal file
122
object/model.go
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
"xorm.io/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Model struct {
|
||||||
|
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||||
|
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||||
|
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||||
|
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||||
|
|
||||||
|
ModelText string `xorm:"mediumtext" json:"modelText"`
|
||||||
|
IsEnabled bool `json:"isEnabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetModelCount(owner, field, value string) int {
|
||||||
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
|
count, err := session.Count(&Model{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetModels(owner string) []*Model {
|
||||||
|
models := []*Model{}
|
||||||
|
err := adapter.Engine.Desc("created_time").Find(&models, &Model{Owner: owner})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return models
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPaginationModels(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Model {
|
||||||
|
models := []*Model{}
|
||||||
|
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||||
|
err := session.Find(&models)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return models
|
||||||
|
}
|
||||||
|
|
||||||
|
func getModel(owner string, name string) *Model {
|
||||||
|
if owner == "" || name == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
model := Model{Owner: owner, Name: name}
|
||||||
|
existed, err := adapter.Engine.Get(&model)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &model
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetModel(id string) *Model {
|
||||||
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
|
return getModel(owner, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateModel(id string, model *Model) bool {
|
||||||
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
|
if getModel(owner, name) == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(model)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddModel(model *Model) bool {
|
||||||
|
affected, err := adapter.Engine.Insert(model)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteModel(model *Model) bool {
|
||||||
|
affected, err := adapter.Engine.ID(core.PK{model.Owner, model.Name}).Delete(&Model{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (model *Model) GetId() string {
|
||||||
|
return fmt.Sprintf("%s/%s", model.Owner, model.Name)
|
||||||
|
}
|
@@ -76,7 +76,7 @@ func GetOidcDiscovery(host string) OidcDiscovery {
|
|||||||
UserinfoEndpoint: fmt.Sprintf("%s/api/userinfo", originBackend),
|
UserinfoEndpoint: fmt.Sprintf("%s/api/userinfo", originBackend),
|
||||||
JwksUri: fmt.Sprintf("%s/.well-known/jwks", originBackend),
|
JwksUri: fmt.Sprintf("%s/.well-known/jwks", originBackend),
|
||||||
IntrospectionEndpoint: fmt.Sprintf("%s/api/login/oauth/introspect", originBackend),
|
IntrospectionEndpoint: fmt.Sprintf("%s/api/login/oauth/introspect", originBackend),
|
||||||
ResponseTypesSupported: []string{"id_token"},
|
ResponseTypesSupported: []string{"code", "token", "id_token", "code token", "code id_token", "token id_token", "code token id_token", "none"},
|
||||||
ResponseModesSupported: []string{"login", "code", "link"},
|
ResponseModesSupported: []string{"login", "code", "link"},
|
||||||
GrantTypesSupported: []string{"password", "authorization_code"},
|
GrantTypesSupported: []string{"password", "authorization_code"},
|
||||||
SubjectTypesSupported: []string{"public"},
|
SubjectTypesSupported: []string{"public"},
|
||||||
@@ -105,6 +105,8 @@ func GetJsonWebKeySet() (jose.JSONWebKeySet, error) {
|
|||||||
jwk.Key = x509Cert.PublicKey
|
jwk.Key = x509Cert.PublicKey
|
||||||
jwk.Certificates = []*x509.Certificate{x509Cert}
|
jwk.Certificates = []*x509.Certificate{x509Cert}
|
||||||
jwk.KeyID = cert.Name
|
jwk.KeyID = cert.Name
|
||||||
|
jwk.Algorithm = cert.CryptoAlgorithm
|
||||||
|
jwk.Use = "sig"
|
||||||
jwks.Keys = append(jwks.Keys, jwk)
|
jwks.Keys = append(jwks.Keys, jwk)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -20,6 +20,13 @@ import (
|
|||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type AccountItem struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Visible bool `json:"visible"`
|
||||||
|
ViewRule string `json:"viewRule"`
|
||||||
|
ModifyRule string `json:"modifyRule"`
|
||||||
|
}
|
||||||
|
|
||||||
type Organization struct {
|
type Organization struct {
|
||||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||||
@@ -35,6 +42,9 @@ type Organization struct {
|
|||||||
Tags []string `xorm:"mediumtext" json:"tags"`
|
Tags []string `xorm:"mediumtext" json:"tags"`
|
||||||
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
||||||
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
||||||
|
IsProfilePublic bool `json:"isProfilePublic"`
|
||||||
|
|
||||||
|
AccountItems []*AccountItem `xorm:"varchar(2000)" json:"accountItems"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOrganizationCount(owner, field, value string) int {
|
func GetOrganizationCount(owner, field, value string) int {
|
||||||
@@ -120,14 +130,18 @@ func UpdateOrganization(id string, organization *Organization) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if name != organization.Name {
|
if name != organization.Name {
|
||||||
applications := GetApplicationsByOrganizationName("admin", name)
|
go func() {
|
||||||
for _, application := range applications {
|
application := new(Application)
|
||||||
application.Organization = organization.Name
|
application.Organization = organization.Name
|
||||||
UpdateApplication(application.GetId(), application)
|
_, _ = adapter.Engine.Where("organization=?", name).Update(application)
|
||||||
}
|
|
||||||
|
user := new(User)
|
||||||
|
user.Owner = organization.Name
|
||||||
|
_, _ = adapter.Engine.Where("owner=?", name).Update(user)
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
if organization.MasterPassword != "" {
|
if organization.MasterPassword != "" && organization.MasterPassword != "***" {
|
||||||
credManager := cred.GetCredManager(organization.PasswordType)
|
credManager := cred.GetCredManager(organization.PasswordType)
|
||||||
if credManager != nil {
|
if credManager != nil {
|
||||||
hashedPassword := credManager.GetHashedPassword(organization.MasterPassword, "", organization.PasswordSalt)
|
hashedPassword := credManager.GetHashedPassword(organization.MasterPassword, "", organization.PasswordSalt)
|
||||||
@@ -135,7 +149,11 @@ func UpdateOrganization(id string, organization *Organization) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(organization)
|
session := adapter.Engine.ID(core.PK{owner, name}).AllCols()
|
||||||
|
if organization.MasterPassword == "***" {
|
||||||
|
session.Omit("master_password")
|
||||||
|
}
|
||||||
|
affected, err := session.Update(organization)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@@ -44,6 +44,16 @@ type Payment struct {
|
|||||||
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
|
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
|
||||||
State string `xorm:"varchar(100)" json:"state"`
|
State string `xorm:"varchar(100)" json:"state"`
|
||||||
Message string `xorm:"varchar(1000)" json:"message"`
|
Message string `xorm:"varchar(1000)" json:"message"`
|
||||||
|
|
||||||
|
PersonName string `xorm:"varchar(100)" json:"personName"`
|
||||||
|
PersonIdCard string `xorm:"varchar(100)" json:"personIdCard"`
|
||||||
|
PersonEmail string `xorm:"varchar(100)" json:"personEmail"`
|
||||||
|
PersonPhone string `xorm:"varchar(100)" json:"personPhone"`
|
||||||
|
InvoiceType string `xorm:"varchar(100)" json:"invoiceType"`
|
||||||
|
InvoiceTitle string `xorm:"varchar(100)" json:"invoiceTitle"`
|
||||||
|
InvoiceTaxId string `xorm:"varchar(100)" json:"invoiceTaxId"`
|
||||||
|
InvoiceRemark string `xorm:"varchar(100)" json:"invoiceRemark"`
|
||||||
|
InvoiceUrl string `xorm:"varchar(255)" json:"invoiceUrl"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPaymentCount(owner, field, value string) int {
|
func GetPaymentCount(owner, field, value string) int {
|
||||||
@@ -197,6 +207,44 @@ func NotifyPayment(request *http.Request, body []byte, owner string, providerNam
|
|||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func invoicePayment(payment *Payment) (string, error) {
|
||||||
|
provider := getProvider(payment.Owner, payment.Provider)
|
||||||
|
if provider == nil {
|
||||||
|
return "", fmt.Errorf("the payment provider: %s does not exist", payment.Provider)
|
||||||
|
}
|
||||||
|
|
||||||
|
pProvider, _, err := provider.getPaymentProvider()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
invoiceUrl, err := pProvider.GetInvoice(payment.Name, payment.PersonName, payment.PersonIdCard, payment.PersonEmail, payment.PersonPhone, payment.InvoiceType, payment.InvoiceTitle, payment.InvoiceTaxId)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return invoiceUrl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func InvoicePayment(payment *Payment) (string, error) {
|
||||||
|
if payment.State != "Paid" {
|
||||||
|
return "", fmt.Errorf("the payment state is supposed to be: \"%s\", got: \"%s\"", "Paid", payment.State)
|
||||||
|
}
|
||||||
|
|
||||||
|
invoiceUrl, err := invoicePayment(payment)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
payment.InvoiceUrl = invoiceUrl
|
||||||
|
affected := UpdatePayment(payment.GetId(), payment)
|
||||||
|
if !affected {
|
||||||
|
return "", fmt.Errorf("failed to update the payment: %s", payment.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return invoiceUrl, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (payment *Payment) GetId() string {
|
func (payment *Payment) GetId() string {
|
||||||
return fmt.Sprintf("%s/%s", payment.Owner, payment.Name)
|
return fmt.Sprintf("%s/%s", payment.Owner, payment.Name)
|
||||||
}
|
}
|
||||||
|
@@ -30,6 +30,7 @@ type Permission struct {
|
|||||||
Users []string `xorm:"mediumtext" json:"users"`
|
Users []string `xorm:"mediumtext" json:"users"`
|
||||||
Roles []string `xorm:"mediumtext" json:"roles"`
|
Roles []string `xorm:"mediumtext" json:"roles"`
|
||||||
|
|
||||||
|
Model string `xorm:"varchar(100)" json:"model"`
|
||||||
ResourceType string `xorm:"varchar(100)" json:"resourceType"`
|
ResourceType string `xorm:"varchar(100)" json:"resourceType"`
|
||||||
Resources []string `xorm:"mediumtext" json:"resources"`
|
Resources []string `xorm:"mediumtext" json:"resources"`
|
||||||
Actions []string `xorm:"mediumtext" json:"actions"`
|
Actions []string `xorm:"mediumtext" json:"actions"`
|
||||||
|
@@ -170,6 +170,7 @@ func BuyProduct(id string, providerName string, user *User, host string) (string
|
|||||||
|
|
||||||
owner := product.Owner
|
owner := product.Owner
|
||||||
productName := product.Name
|
productName := product.Name
|
||||||
|
payerName := fmt.Sprintf("%s | %s", user.Name, user.DisplayName)
|
||||||
paymentName := util.GenerateTimeId()
|
paymentName := util.GenerateTimeId()
|
||||||
productDisplayName := product.DisplayName
|
productDisplayName := product.DisplayName
|
||||||
|
|
||||||
@@ -177,7 +178,7 @@ func BuyProduct(id string, providerName string, user *User, host string) (string
|
|||||||
returnUrl := fmt.Sprintf("%s/payments/%s/result", originFrontend, paymentName)
|
returnUrl := fmt.Sprintf("%s/payments/%s/result", originFrontend, paymentName)
|
||||||
notifyUrl := fmt.Sprintf("%s/api/notify-payment/%s/%s/%s/%s", originBackend, owner, providerName, productName, paymentName)
|
notifyUrl := fmt.Sprintf("%s/api/notify-payment/%s/%s/%s/%s", originBackend, owner, providerName, productName, paymentName)
|
||||||
|
|
||||||
payUrl, err := pProvider.Pay(providerName, productName, paymentName, productDisplayName, product.Price, returnUrl, notifyUrl)
|
payUrl, err := pProvider.Pay(providerName, productName, payerName, paymentName, productDisplayName, product.Price, returnUrl, notifyUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@@ -27,16 +27,21 @@ type Provider struct {
|
|||||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||||
|
|
||||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||||
Category string `xorm:"varchar(100)" json:"category"`
|
Category string `xorm:"varchar(100)" json:"category"`
|
||||||
Type string `xorm:"varchar(100)" json:"type"`
|
Type string `xorm:"varchar(100)" json:"type"`
|
||||||
SubType string `xorm:"varchar(100)" json:"subType"`
|
SubType string `xorm:"varchar(100)" json:"subType"`
|
||||||
Method string `xorm:"varchar(100)" json:"method"`
|
Method string `xorm:"varchar(100)" json:"method"`
|
||||||
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
||||||
ClientSecret string `xorm:"varchar(2000)" json:"clientSecret"`
|
ClientSecret string `xorm:"varchar(2000)" json:"clientSecret"`
|
||||||
ClientId2 string `xorm:"varchar(100)" json:"clientId2"`
|
ClientId2 string `xorm:"varchar(100)" json:"clientId2"`
|
||||||
ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"`
|
ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"`
|
||||||
Cert string `xorm:"varchar(100)" json:"cert"`
|
Cert string `xorm:"varchar(100)" json:"cert"`
|
||||||
|
CustomAuthUrl string `xorm:"varchar(200)" json:"customAuthUrl"`
|
||||||
|
CustomScope string `xorm:"varchar(200)" json:"customScope"`
|
||||||
|
CustomTokenUrl string `xorm:"varchar(200)" json:"customTokenUrl"`
|
||||||
|
CustomUserInfoUrl string `xorm:"varchar(200)" json:"customUserInfoUrl"`
|
||||||
|
CustomLogo string `xorm:"varchar(200)" json:"customLogo"`
|
||||||
|
|
||||||
Host string `xorm:"varchar(100)" json:"host"`
|
Host string `xorm:"varchar(100)" json:"host"`
|
||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
@@ -137,8 +142,8 @@ func GetProvider(id string) *Provider {
|
|||||||
return getProvider(owner, name)
|
return getProvider(owner, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDefaultHumanCheckProvider() *Provider {
|
func GetDefaultCaptchaProvider() *Provider {
|
||||||
provider := Provider{Owner: "admin", Category: "HumanCheck"}
|
provider := Provider{Owner: "admin", Category: "Captcha"}
|
||||||
existed, err := adapter.Engine.Get(&provider)
|
existed, err := adapter.Engine.Get(&provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -167,7 +172,14 @@ func UpdateProvider(id string, provider *Provider) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(provider)
|
session := adapter.Engine.ID(core.PK{owner, name}).AllCols()
|
||||||
|
if provider.ClientSecret == "***" {
|
||||||
|
session = session.Omit("client_secret")
|
||||||
|
}
|
||||||
|
if provider.ClientSecret2 == "***" {
|
||||||
|
session = session.Omit("client_secret2")
|
||||||
|
}
|
||||||
|
affected, err := session.Update(provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -213,3 +225,37 @@ func (p *Provider) getPaymentProvider() (pp.PaymentProvider, *Cert, error) {
|
|||||||
func (p *Provider) GetId() string {
|
func (p *Provider) GetId() string {
|
||||||
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
|
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetCaptchaProviderByOwnerName(applicationId string) (*Provider, error) {
|
||||||
|
owner, name := util.GetOwnerAndNameFromId(applicationId)
|
||||||
|
provider := Provider{Owner: owner, Name: name, Category: "Captcha"}
|
||||||
|
existed, err := adapter.Engine.Get(&provider)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !existed {
|
||||||
|
return nil, fmt.Errorf("the provider: %s does not exist", applicationId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &provider, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCaptchaProviderByApplication(applicationId, isCurrentProvider string) (*Provider, error) {
|
||||||
|
if isCurrentProvider == "true" {
|
||||||
|
return GetCaptchaProviderByOwnerName(applicationId)
|
||||||
|
}
|
||||||
|
application := GetApplication(applicationId)
|
||||||
|
if application == nil || len(application.Providers) == 0 {
|
||||||
|
return nil, fmt.Errorf("invalid application id")
|
||||||
|
}
|
||||||
|
for _, provider := range application.Providers {
|
||||||
|
if provider.Provider == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if provider.Provider.Category == "Captcha" {
|
||||||
|
return GetCaptchaProviderByOwnerName(fmt.Sprintf("%s/%s", provider.Provider.Owner, provider.Provider.Name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
@@ -23,7 +23,7 @@ import (
|
|||||||
|
|
||||||
type Resource struct {
|
type Resource struct {
|
||||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||||
Name string `xorm:"varchar(200) notnull pk" json:"name"`
|
Name string `xorm:"varchar(250) notnull pk" json:"name"`
|
||||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||||
|
|
||||||
User string `xorm:"varchar(100)" json:"user"`
|
User string `xorm:"varchar(100)" json:"user"`
|
||||||
@@ -31,7 +31,7 @@ type Resource struct {
|
|||||||
Application string `xorm:"varchar(100)" json:"application"`
|
Application string `xorm:"varchar(100)" json:"application"`
|
||||||
Tag string `xorm:"varchar(100)" json:"tag"`
|
Tag string `xorm:"varchar(100)" json:"tag"`
|
||||||
Parent string `xorm:"varchar(100)" json:"parent"`
|
Parent string `xorm:"varchar(100)" json:"parent"`
|
||||||
FileName string `xorm:"varchar(100)" json:"fileName"`
|
FileName string `xorm:"varchar(1000)" json:"fileName"`
|
||||||
FileType string `xorm:"varchar(100)" json:"fileType"`
|
FileType string `xorm:"varchar(100)" json:"fileType"`
|
||||||
FileFormat string `xorm:"varchar(100)" json:"fileFormat"`
|
FileFormat string `xorm:"varchar(100)" json:"fileFormat"`
|
||||||
FileSize int `json:"fileSize"`
|
FileSize int `json:"fileSize"`
|
||||||
|
@@ -51,7 +51,7 @@ func NewSamlResponse(user *User, host string, publicKey string, destination stri
|
|||||||
samlResponse.CreateAttr("Version", "2.0")
|
samlResponse.CreateAttr("Version", "2.0")
|
||||||
samlResponse.CreateAttr("IssueInstant", now)
|
samlResponse.CreateAttr("IssueInstant", now)
|
||||||
samlResponse.CreateAttr("Destination", destination)
|
samlResponse.CreateAttr("Destination", destination)
|
||||||
samlResponse.CreateAttr("InResponseTo", fmt.Sprintf("Casdoor_%s", arId))
|
samlResponse.CreateAttr("InResponseTo", fmt.Sprintf("_%s", arId))
|
||||||
samlResponse.CreateElement("saml:Issuer").SetText(host)
|
samlResponse.CreateElement("saml:Issuer").SetText(host)
|
||||||
|
|
||||||
samlResponse.CreateElement("samlp:Status").CreateElement("samlp:StatusCode").CreateAttr("Value", "urn:oasis:names:tc:SAML:2.0:status:Success")
|
samlResponse.CreateElement("samlp:Status").CreateElement("samlp:StatusCode").CreateAttr("Value", "urn:oasis:names:tc:SAML:2.0:status:Success")
|
||||||
@@ -261,13 +261,15 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
|||||||
}
|
}
|
||||||
ctx := dsig.NewDefaultSigningContext(randomKeyStore)
|
ctx := dsig.NewDefaultSigningContext(randomKeyStore)
|
||||||
ctx.Hash = crypto.SHA1
|
ctx.Hash = crypto.SHA1
|
||||||
signedXML, err := ctx.SignEnveloped(samlResponse)
|
//signedXML, err := ctx.SignEnvelopedLimix(samlResponse)
|
||||||
if err != nil {
|
//if err != nil {
|
||||||
return "", "", fmt.Errorf("err: %s", err.Error())
|
// return "", "", fmt.Errorf("err: %s", err.Error())
|
||||||
}
|
//}
|
||||||
|
sig, err := ctx.ConstructSignature(samlResponse, true)
|
||||||
|
samlResponse.InsertChildAt(1, sig)
|
||||||
|
|
||||||
doc := etree.NewDocument()
|
doc := etree.NewDocument()
|
||||||
doc.SetRoot(signedXML)
|
doc.SetRoot(samlResponse)
|
||||||
xmlStr, err := doc.WriteToString()
|
xmlStr, err := doc.WriteToString()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", fmt.Errorf("err: %s", err.Error())
|
return "", "", fmt.Errorf("err: %s", err.Error())
|
||||||
|
@@ -56,6 +56,9 @@ func getUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool
|
|||||||
// provider.Domain = "http://localhost:8000" or "https://door.casdoor.com"
|
// provider.Domain = "http://localhost:8000" or "https://door.casdoor.com"
|
||||||
host = util.UrlJoin(provider.Domain, "/files")
|
host = util.UrlJoin(provider.Domain, "/files")
|
||||||
}
|
}
|
||||||
|
if provider.Type == "Azure Blob" {
|
||||||
|
host = fmt.Sprintf("%s/%s", host, provider.Bucket)
|
||||||
|
}
|
||||||
|
|
||||||
fileUrl := util.UrlJoin(host, objectKey)
|
fileUrl := util.UrlJoin(host, objectKey)
|
||||||
if hasTimestamp {
|
if hasTimestamp {
|
||||||
|
@@ -133,7 +133,11 @@ func UpdateSyncer(id string, syncer *Syncer) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(syncer)
|
session := adapter.Engine.ID(core.PK{owner, name}).AllCols()
|
||||||
|
if syncer.Password == "***" {
|
||||||
|
session.Omit("password")
|
||||||
|
}
|
||||||
|
affected, err := session.Update(syncer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -206,3 +210,8 @@ func (syncer *Syncer) getTable() string {
|
|||||||
return syncer.Table
|
return syncer.Table
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RunSyncer(syncer *Syncer) {
|
||||||
|
syncer.initAdapter()
|
||||||
|
syncer.syncUsers()
|
||||||
|
}
|
||||||
|
@@ -173,7 +173,18 @@ func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]string) []*Or
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tableColumn := range syncer.TableColumns {
|
for _, tableColumn := range syncer.TableColumns {
|
||||||
syncer.setUserByKeyValue(originalUser, tableColumn.CasdoorName, result[tableColumn.Name])
|
value := ""
|
||||||
|
if strings.Contains(tableColumn.Name, "+") {
|
||||||
|
names := strings.Split(tableColumn.Name, "+")
|
||||||
|
var values []string
|
||||||
|
for _, name := range names {
|
||||||
|
values = append(values, result[strings.Trim(name, " ")])
|
||||||
|
}
|
||||||
|
value = strings.Join(values, " ")
|
||||||
|
} else {
|
||||||
|
value = result[tableColumn.Name]
|
||||||
|
}
|
||||||
|
syncer.setUserByKeyValue(originalUser, tableColumn.CasdoorName, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
if syncer.Type == "Keycloak" {
|
if syncer.Type == "Keycloak" {
|
||||||
|
@@ -27,6 +27,10 @@ import (
|
|||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
hourSeconds = 3600
|
||||||
|
)
|
||||||
|
|
||||||
type Code struct {
|
type Code struct {
|
||||||
Message string `xorm:"varchar(100)" json:"message"`
|
Message string `xorm:"varchar(100)" json:"message"`
|
||||||
Code string `xorm:"varchar(100)" json:"code"`
|
Code string `xorm:"varchar(100)" json:"code"`
|
||||||
@@ -292,7 +296,7 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
|
|||||||
Code: util.GenerateClientId(),
|
Code: util.GenerateClientId(),
|
||||||
AccessToken: accessToken,
|
AccessToken: accessToken,
|
||||||
RefreshToken: refreshToken,
|
RefreshToken: refreshToken,
|
||||||
ExpiresIn: application.ExpireInHours * 60,
|
ExpiresIn: application.ExpireInHours * hourSeconds,
|
||||||
Scope: scope,
|
Scope: scope,
|
||||||
TokenType: "Bearer",
|
TokenType: "Bearer",
|
||||||
CodeChallenge: challenge,
|
CodeChallenge: challenge,
|
||||||
@@ -463,7 +467,7 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
|||||||
Code: util.GenerateClientId(),
|
Code: util.GenerateClientId(),
|
||||||
AccessToken: newAccessToken,
|
AccessToken: newAccessToken,
|
||||||
RefreshToken: newRefreshToken,
|
RefreshToken: newRefreshToken,
|
||||||
ExpiresIn: application.ExpireInHours * 60,
|
ExpiresIn: application.ExpireInHours * hourSeconds,
|
||||||
Scope: scope,
|
Scope: scope,
|
||||||
TokenType: "Bearer",
|
TokenType: "Bearer",
|
||||||
}
|
}
|
||||||
@@ -572,7 +576,7 @@ func GetPasswordToken(application *Application, username string, password string
|
|||||||
Code: util.GenerateClientId(),
|
Code: util.GenerateClientId(),
|
||||||
AccessToken: accessToken,
|
AccessToken: accessToken,
|
||||||
RefreshToken: refreshToken,
|
RefreshToken: refreshToken,
|
||||||
ExpiresIn: application.ExpireInHours * 60,
|
ExpiresIn: application.ExpireInHours * hourSeconds,
|
||||||
Scope: scope,
|
Scope: scope,
|
||||||
TokenType: "Bearer",
|
TokenType: "Bearer",
|
||||||
CodeIsUsed: true,
|
CodeIsUsed: true,
|
||||||
@@ -604,7 +608,7 @@ func GetClientCredentialsToken(application *Application, clientSecret string, sc
|
|||||||
User: nullUser.Name,
|
User: nullUser.Name,
|
||||||
Code: util.GenerateClientId(),
|
Code: util.GenerateClientId(),
|
||||||
AccessToken: accessToken,
|
AccessToken: accessToken,
|
||||||
ExpiresIn: application.ExpireInHours * 60,
|
ExpiresIn: application.ExpireInHours * hourSeconds,
|
||||||
Scope: scope,
|
Scope: scope,
|
||||||
TokenType: "Bearer",
|
TokenType: "Bearer",
|
||||||
CodeIsUsed: true,
|
CodeIsUsed: true,
|
||||||
@@ -629,7 +633,7 @@ func GetTokenByUser(application *Application, user *User, scope string, host str
|
|||||||
Code: util.GenerateClientId(),
|
Code: util.GenerateClientId(),
|
||||||
AccessToken: accessToken,
|
AccessToken: accessToken,
|
||||||
RefreshToken: refreshToken,
|
RefreshToken: refreshToken,
|
||||||
ExpiresIn: application.ExpireInHours * 60,
|
ExpiresIn: application.ExpireInHours * hourSeconds,
|
||||||
Scope: scope,
|
Scope: scope,
|
||||||
TokenType: "Bearer",
|
TokenType: "Bearer",
|
||||||
CodeIsUsed: true,
|
CodeIsUsed: true,
|
||||||
|
@@ -94,6 +94,10 @@ type User struct {
|
|||||||
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"`
|
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"`
|
||||||
Slack string `xorm:"slack varchar(100)" json:"slack"`
|
Slack string `xorm:"slack varchar(100)" json:"slack"`
|
||||||
Steam string `xorm:"steam varchar(100)" json:"steam"`
|
Steam string `xorm:"steam varchar(100)" json:"steam"`
|
||||||
|
Bilibili string `xorm:"bilibili varchar(100)" json:"bilibili"`
|
||||||
|
Okta string `xorm:"okta varchar(100)" json:"okta"`
|
||||||
|
Douyin string `xorm:"douyin vachar(100)" json:"douyin"`
|
||||||
|
Custom string `xorm:"custom varchar(100)" json:"custom"`
|
||||||
|
|
||||||
Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
|
Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
|
||||||
Properties map[string]string `json:"properties"`
|
Properties map[string]string `json:"properties"`
|
||||||
@@ -312,6 +316,9 @@ func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) boo
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if user.Password == "***" {
|
||||||
|
user.Password = oldUser.Password
|
||||||
|
}
|
||||||
user.UpdateUserHash()
|
user.UpdateUserHash()
|
||||||
|
|
||||||
if user.Avatar != oldUser.Avatar && user.Avatar != "" && user.PermanentAvatar != "*" {
|
if user.Avatar != oldUser.Avatar && user.Avatar != "" && user.PermanentAvatar != "*" {
|
||||||
|
@@ -97,3 +97,14 @@ func TestGetMaskedUsers(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetUserByField(t *testing.T) {
|
||||||
|
InitConfig()
|
||||||
|
|
||||||
|
user := GetUserByField("built-in", "DingTalk", "test")
|
||||||
|
if user != nil {
|
||||||
|
t.Logf("%+v", user)
|
||||||
|
} else {
|
||||||
|
t.Log("no user found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -29,7 +29,7 @@ func GetUserByField(organizationName string, field string, value string) *User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
user := User{Owner: organizationName}
|
user := User{Owner: organizationName}
|
||||||
existed, err := adapter.Engine.Where(fmt.Sprintf("%s=?", field), value).Get(&user)
|
existed, err := adapter.Engine.Where(fmt.Sprintf("%s=?", strings.ToLower(field)), value).Get(&user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@@ -45,7 +45,7 @@ func NewAlipayPaymentProvider(appId string, appPublicKey string, appPrivateKey s
|
|||||||
return pp
|
return pp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *AlipayPaymentProvider) Pay(providerName string, productName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) {
|
func (pp *AlipayPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) {
|
||||||
//pp.Client.DebugSwitch = gopay.DebugOn
|
//pp.Client.DebugSwitch = gopay.DebugOn
|
||||||
|
|
||||||
bm := gopay.BodyMap{}
|
bm := gopay.BodyMap{}
|
||||||
@@ -90,3 +90,7 @@ func (pp *AlipayPaymentProvider) Notify(request *http.Request, body []byte, auth
|
|||||||
|
|
||||||
return productDisplayName, paymentName, price, productName, providerName, nil
|
return productDisplayName, paymentName, price, productName, providerName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pp *AlipayPaymentProvider) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
112
pp/gc.go
112
pp/gc.go
@@ -38,11 +38,14 @@ type GcPayReqInfo struct {
|
|||||||
OrderDate string `json:"orderdate"`
|
OrderDate string `json:"orderdate"`
|
||||||
OrderNo string `json:"orderno"`
|
OrderNo string `json:"orderno"`
|
||||||
Amount string `json:"amount"`
|
Amount string `json:"amount"`
|
||||||
PayerId string `json:"payerid"`
|
|
||||||
PayerName string `json:"payername"`
|
|
||||||
Xmpch string `json:"xmpch"`
|
Xmpch string `json:"xmpch"`
|
||||||
|
Body string `json:"body"`
|
||||||
ReturnUrl string `json:"return_url"`
|
ReturnUrl string `json:"return_url"`
|
||||||
NotifyUrl string `json:"notify_url"`
|
NotifyUrl string `json:"notify_url"`
|
||||||
|
PayerId string `json:"payerid"`
|
||||||
|
PayerName string `json:"payername"`
|
||||||
|
Remark1 string `json:"remark1"`
|
||||||
|
Remark2 string `json:"remark2"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GcPayRespInfo struct {
|
type GcPayRespInfo struct {
|
||||||
@@ -87,6 +90,27 @@ type GcResponseBody struct {
|
|||||||
Sign string `json:"sign"`
|
Sign string `json:"sign"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GcInvoiceReqInfo struct {
|
||||||
|
BusNo string `json:"busno"`
|
||||||
|
PayerName string `json:"payername"`
|
||||||
|
IdNum string `json:"idnum"`
|
||||||
|
PayerType string `json:"payertype"`
|
||||||
|
InvoiceTitle string `json:"invoicetitle"`
|
||||||
|
Tin string `json:"tin"`
|
||||||
|
Phone string `json:"phone"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GcInvoiceRespInfo struct {
|
||||||
|
BusNo string `json:"busno"`
|
||||||
|
State string `json:"state"`
|
||||||
|
EbillCode string `json:"ebillcode"`
|
||||||
|
EbillNo string `json:"ebillno"`
|
||||||
|
CheckCode string `json:"checkcode"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
func NewGcPaymentProvider(clientId string, clientSecret string, host string) *GcPaymentProvider {
|
func NewGcPaymentProvider(clientId string, clientSecret string, host string) *GcPaymentProvider {
|
||||||
pp := &GcPaymentProvider{}
|
pp := &GcPaymentProvider{}
|
||||||
|
|
||||||
@@ -130,16 +154,17 @@ func (pp *GcPaymentProvider) doPost(postBytes []byte) ([]byte, error) {
|
|||||||
return respBytes, nil
|
return respBytes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *GcPaymentProvider) Pay(providerName string, productName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) {
|
func (pp *GcPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) {
|
||||||
payReqInfo := GcPayReqInfo{
|
payReqInfo := GcPayReqInfo{
|
||||||
OrderDate: util.GenerateSimpleTimeId(),
|
OrderDate: util.GenerateSimpleTimeId(),
|
||||||
OrderNo: util.GenerateTimeId(),
|
OrderNo: paymentName,
|
||||||
Amount: getPriceString(price),
|
Amount: getPriceString(price),
|
||||||
PayerId: "",
|
|
||||||
PayerName: "",
|
|
||||||
Xmpch: pp.Xmpch,
|
Xmpch: pp.Xmpch,
|
||||||
|
Body: productDisplayName,
|
||||||
ReturnUrl: returnUrl,
|
ReturnUrl: returnUrl,
|
||||||
NotifyUrl: notifyUrl,
|
NotifyUrl: notifyUrl,
|
||||||
|
Remark1: payerName,
|
||||||
|
Remark2: productName,
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := json.Marshal(payReqInfo)
|
b, err := json.Marshal(payReqInfo)
|
||||||
@@ -230,3 +255,78 @@ func (pp *GcPaymentProvider) Notify(request *http.Request, body []byte, authorit
|
|||||||
|
|
||||||
return productDisplayName, paymentName, price, productName, providerName, nil
|
return productDisplayName, paymentName, price, productName, providerName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pp *GcPaymentProvider) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) {
|
||||||
|
payerType := "0"
|
||||||
|
if invoiceType == "Organization" {
|
||||||
|
payerType = "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
invoiceReqInfo := GcInvoiceReqInfo{
|
||||||
|
BusNo: paymentName,
|
||||||
|
PayerName: personName,
|
||||||
|
IdNum: personIdCard,
|
||||||
|
PayerType: payerType,
|
||||||
|
InvoiceTitle: invoiceTitle,
|
||||||
|
Tin: invoiceTaxId,
|
||||||
|
Phone: personPhone,
|
||||||
|
Email: personEmail,
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(invoiceReqInfo)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
body := GcRequestBody{
|
||||||
|
Op: "InvoiceEBillByOrder",
|
||||||
|
Xmpch: pp.Xmpch,
|
||||||
|
Version: "1.4",
|
||||||
|
Data: base64.StdEncoding.EncodeToString(b),
|
||||||
|
RequestTime: util.GenerateSimpleTimeId(),
|
||||||
|
}
|
||||||
|
|
||||||
|
params := fmt.Sprintf("data=%s&op=%s&requesttime=%s&version=%s&xmpch=%s%s", body.Data, body.Op, body.RequestTime, body.Version, body.Xmpch, pp.SecretKey)
|
||||||
|
body.Sign = strings.ToUpper(util.GetMd5Hash(params))
|
||||||
|
|
||||||
|
bodyBytes, err := json.Marshal(body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
respBytes, err := pp.doPost(bodyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var respBody GcResponseBody
|
||||||
|
err = json.Unmarshal(respBytes, &respBody)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if respBody.ReturnCode != "SUCCESS" {
|
||||||
|
return "", fmt.Errorf("%s: %s", respBody.ReturnCode, respBody.ReturnMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
invoiceRespInfoBytes, err := base64.StdEncoding.DecodeString(respBody.Data)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var invoiceRespInfo GcInvoiceRespInfo
|
||||||
|
err = json.Unmarshal(invoiceRespInfoBytes, &invoiceRespInfo)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if invoiceRespInfo.State == "0" {
|
||||||
|
return "", fmt.Errorf("申请成功,开票中")
|
||||||
|
}
|
||||||
|
|
||||||
|
if invoiceRespInfo.Url == "" {
|
||||||
|
return "", fmt.Errorf("invoice URL is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
return invoiceRespInfo.Url, nil
|
||||||
|
}
|
||||||
|
@@ -17,8 +17,9 @@ package pp
|
|||||||
import "net/http"
|
import "net/http"
|
||||||
|
|
||||||
type PaymentProvider interface {
|
type PaymentProvider interface {
|
||||||
Pay(providerName string, productName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error)
|
Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error)
|
||||||
Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error)
|
Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error)
|
||||||
|
GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPaymentProvider(typ string, appId string, clientSecret string, host string, appPublicKey string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) PaymentProvider {
|
func GetPaymentProvider(typ string, appId string, clientSecret string, host string, appPublicKey string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) PaymentProvider {
|
||||||
|
@@ -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")) {
|
if strings.HasPrefix(urlPath, "/cas") && (strings.HasSuffix(urlPath, "/serviceValidate") || strings.HasSuffix(urlPath, "/proxy") || strings.HasSuffix(urlPath, "/proxyValidate") || strings.HasSuffix(urlPath, "/validate") || strings.HasSuffix(urlPath, "/p3/serviceValidate") || strings.HasSuffix(urlPath, "/p3/proxyValidate") || strings.HasSuffix(urlPath, "/samlValidate")) {
|
||||||
return "/cas"
|
return "/cas"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(urlPath, "/api/login/oauth") {
|
||||||
|
return "/api/login/oauth"
|
||||||
|
}
|
||||||
|
|
||||||
return urlPath
|
return urlPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -65,5 +65,5 @@ func RecordMessage(ctx *context.Context) {
|
|||||||
record.Organization, record.User = util.GetOwnerAndNameFromId(userId)
|
record.Organization, record.User = util.GetOwnerAndNameFromId(userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
go object.AddRecord(record)
|
util.SafeGoroutine(func() { object.AddRecord(record) })
|
||||||
}
|
}
|
||||||
|
@@ -84,12 +84,19 @@ func initAPI() {
|
|||||||
beego.Router("/api/add-permission", &controllers.ApiController{}, "POST:AddPermission")
|
beego.Router("/api/add-permission", &controllers.ApiController{}, "POST:AddPermission")
|
||||||
beego.Router("/api/delete-permission", &controllers.ApiController{}, "POST:DeletePermission")
|
beego.Router("/api/delete-permission", &controllers.ApiController{}, "POST:DeletePermission")
|
||||||
|
|
||||||
|
beego.Router("/api/get-models", &controllers.ApiController{}, "GET:GetModels")
|
||||||
|
beego.Router("/api/get-model", &controllers.ApiController{}, "GET:GetModel")
|
||||||
|
beego.Router("/api/update-model", &controllers.ApiController{}, "POST:UpdateModel")
|
||||||
|
beego.Router("/api/add-model", &controllers.ApiController{}, "POST:AddModel")
|
||||||
|
beego.Router("/api/delete-model", &controllers.ApiController{}, "POST:DeleteModel")
|
||||||
|
|
||||||
beego.Router("/api/set-password", &controllers.ApiController{}, "POST:SetPassword")
|
beego.Router("/api/set-password", &controllers.ApiController{}, "POST:SetPassword")
|
||||||
beego.Router("/api/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword")
|
beego.Router("/api/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword")
|
||||||
beego.Router("/api/get-email-and-phone", &controllers.ApiController{}, "POST:GetEmailAndPhone")
|
beego.Router("/api/get-email-and-phone", &controllers.ApiController{}, "POST:GetEmailAndPhone")
|
||||||
beego.Router("/api/send-verification-code", &controllers.ApiController{}, "POST:SendVerificationCode")
|
beego.Router("/api/send-verification-code", &controllers.ApiController{}, "POST:SendVerificationCode")
|
||||||
|
beego.Router("/api/verify-captcha", &controllers.ApiController{}, "POST:VerifyCaptcha")
|
||||||
beego.Router("/api/reset-email-or-phone", &controllers.ApiController{}, "POST:ResetEmailOrPhone")
|
beego.Router("/api/reset-email-or-phone", &controllers.ApiController{}, "POST:ResetEmailOrPhone")
|
||||||
beego.Router("/api/get-human-check", &controllers.ApiController{}, "GET:GetHumanCheck")
|
beego.Router("/api/get-captcha", &controllers.ApiController{}, "GET:GetCaptcha")
|
||||||
|
|
||||||
beego.Router("/api/get-ldap-user", &controllers.ApiController{}, "POST:GetLdapUser")
|
beego.Router("/api/get-ldap-user", &controllers.ApiController{}, "POST:GetLdapUser")
|
||||||
beego.Router("/api/get-ldaps", &controllers.ApiController{}, "POST:GetLdaps")
|
beego.Router("/api/get-ldaps", &controllers.ApiController{}, "POST:GetLdaps")
|
||||||
@@ -145,6 +152,7 @@ func initAPI() {
|
|||||||
beego.Router("/api/update-syncer", &controllers.ApiController{}, "POST:UpdateSyncer")
|
beego.Router("/api/update-syncer", &controllers.ApiController{}, "POST:UpdateSyncer")
|
||||||
beego.Router("/api/add-syncer", &controllers.ApiController{}, "POST:AddSyncer")
|
beego.Router("/api/add-syncer", &controllers.ApiController{}, "POST:AddSyncer")
|
||||||
beego.Router("/api/delete-syncer", &controllers.ApiController{}, "POST:DeleteSyncer")
|
beego.Router("/api/delete-syncer", &controllers.ApiController{}, "POST:DeleteSyncer")
|
||||||
|
beego.Router("/api/run-syncer", &controllers.ApiController{}, "GET:RunSyncer")
|
||||||
|
|
||||||
beego.Router("/api/get-certs", &controllers.ApiController{}, "GET:GetCerts")
|
beego.Router("/api/get-certs", &controllers.ApiController{}, "GET:GetCerts")
|
||||||
beego.Router("/api/get-cert", &controllers.ApiController{}, "GET:GetCert")
|
beego.Router("/api/get-cert", &controllers.ApiController{}, "GET:GetCert")
|
||||||
@@ -166,6 +174,7 @@ func initAPI() {
|
|||||||
beego.Router("/api/add-payment", &controllers.ApiController{}, "POST:AddPayment")
|
beego.Router("/api/add-payment", &controllers.ApiController{}, "POST:AddPayment")
|
||||||
beego.Router("/api/delete-payment", &controllers.ApiController{}, "POST:DeletePayment")
|
beego.Router("/api/delete-payment", &controllers.ApiController{}, "POST:DeletePayment")
|
||||||
beego.Router("/api/notify-payment/?:owner/?:provider/?:product/?:payment", &controllers.ApiController{}, "POST:NotifyPayment")
|
beego.Router("/api/notify-payment/?:owner/?:provider/?:product/?:payment", &controllers.ApiController{}, "POST:NotifyPayment")
|
||||||
|
beego.Router("/api/invoice-payment", &controllers.ApiController{}, "POST:InvoicePayment")
|
||||||
|
|
||||||
beego.Router("/api/send-email", &controllers.ApiController{}, "POST:SendEmail")
|
beego.Router("/api/send-email", &controllers.ApiController{}, "POST:SendEmail")
|
||||||
beego.Router("/api/send-sms", &controllers.ApiController{}, "POST:SendSms")
|
beego.Router("/api/send-sms", &controllers.ApiController{}, "POST:SendSms")
|
||||||
|
@@ -15,8 +15,8 @@
|
|||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/qor/oss"
|
"github.com/casdoor/oss"
|
||||||
"github.com/qor/oss/aliyun"
|
"github.com/casdoor/oss/aliyun"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewAliyunOssStorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
|
func NewAliyunOssStorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
|
||||||
|
@@ -16,8 +16,8 @@ package storage
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
awss3 "github.com/aws/aws-sdk-go/service/s3"
|
awss3 "github.com/aws/aws-sdk-go/service/s3"
|
||||||
"github.com/qor/oss"
|
"github.com/casdoor/oss"
|
||||||
"github.com/qor/oss/s3"
|
"github.com/casdoor/oss/s3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewAwsS3StorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
|
func NewAwsS3StorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
|
||||||
|
@@ -12,12 +12,20 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package object
|
package storage
|
||||||
|
|
||||||
type SignupItem struct {
|
import (
|
||||||
Name string `json:"name"`
|
"github.com/casdoor/oss"
|
||||||
Visible bool `json:"visible"`
|
"github.com/casdoor/oss/azureblob"
|
||||||
Required bool `json:"required"`
|
)
|
||||||
Prompted bool `json:"prompted"`
|
|
||||||
Rule string `json:"rule"`
|
func NewAzureBlobStorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
|
||||||
|
sp := azureblob.New(&azureblob.Config{
|
||||||
|
AccessId: clientId,
|
||||||
|
AccessKey: clientSecret,
|
||||||
|
Region: region,
|
||||||
|
Bucket: bucket,
|
||||||
|
Endpoint: endpoint,
|
||||||
|
})
|
||||||
|
return sp
|
||||||
}
|
}
|
@@ -20,7 +20,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qor/oss"
|
"github.com/casdoor/oss"
|
||||||
)
|
)
|
||||||
|
|
||||||
var baseFolder = "files"
|
var baseFolder = "files"
|
||||||
|
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
package storage
|
package storage
|
||||||
|
|
||||||
import "github.com/qor/oss"
|
import "github.com/casdoor/oss"
|
||||||
|
|
||||||
func GetStorageProvider(providerType string, clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
|
func GetStorageProvider(providerType string, clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
|
||||||
switch providerType {
|
switch providerType {
|
||||||
@@ -26,6 +26,8 @@ func GetStorageProvider(providerType string, clientId string, clientSecret strin
|
|||||||
return NewAliyunOssStorageProvider(clientId, clientSecret, region, bucket, endpoint)
|
return NewAliyunOssStorageProvider(clientId, clientSecret, region, bucket, endpoint)
|
||||||
case "Tencent Cloud COS":
|
case "Tencent Cloud COS":
|
||||||
return NewTencentCloudCosStorageProvider(clientId, clientSecret, region, bucket, endpoint)
|
return NewTencentCloudCosStorageProvider(clientId, clientSecret, region, bucket, endpoint)
|
||||||
|
case "Azure Blob":
|
||||||
|
return NewAzureBlobStorageProvider(clientId, clientSecret, region, bucket, endpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@@ -15,8 +15,8 @@
|
|||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/qor/oss"
|
"github.com/casdoor/oss"
|
||||||
"github.com/qor/oss/tencent"
|
"github.com/casdoor/oss/tencent"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewTencentCloudCosStorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
|
func NewTencentCloudCosStorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -12,11 +12,22 @@ paths:
|
|||||||
tags:
|
tags:
|
||||||
- OIDC API
|
- OIDC API
|
||||||
operationId: RootController.GetJwks
|
operationId: RootController.GetJwks
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: ""
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/jose.JSONWebKey'
|
||||||
/.well-known/openid-configuration:
|
/.well-known/openid-configuration:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- OIDC API
|
- OIDC API
|
||||||
|
description: Get Oidc Discovery
|
||||||
operationId: RootController.GetOidcDiscovery
|
operationId: RootController.GetOidcDiscovery
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: ""
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/object.OidcDiscovery'
|
||||||
/api/add-application:
|
/api/add-application:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@@ -58,6 +69,24 @@ paths:
|
|||||||
tags:
|
tags:
|
||||||
- Account API
|
- Account API
|
||||||
operationId: ApiController.AddLdap
|
operationId: ApiController.AddLdap
|
||||||
|
/api/add-model:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Model API
|
||||||
|
description: add model
|
||||||
|
operationId: ApiController.AddModel
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
description: The details of the model
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/object.Model'
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controllers.Response'
|
||||||
/api/add-organization:
|
/api/add-organization:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@@ -243,11 +272,11 @@ paths:
|
|||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/controllers.Response'
|
$ref: '#/definitions/controllers.Response'
|
||||||
/api/api/get-human-check:
|
/api/api/get-captcha:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Login API
|
- Login API
|
||||||
operationId: ApiController.GetHumancheck
|
operationId: ApiController.GetCaptcha
|
||||||
/api/api/reset-email-or-phone:
|
/api/api/reset-email-or-phone:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@@ -271,11 +300,11 @@ paths:
|
|||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
- in: body
|
- in: body
|
||||||
name: body
|
name: from
|
||||||
description: Details of the email request
|
description: Details of the email request
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/emailForm'
|
$ref: '#/definitions/controllers.EmailForm'
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: object
|
description: object
|
||||||
@@ -299,11 +328,11 @@ paths:
|
|||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
- in: body
|
- in: body
|
||||||
name: body
|
name: from
|
||||||
description: Details of the sms request
|
description: Details of the sms request
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/smsForm'
|
$ref: '#/definitions/controllers.SmsForm'
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: object
|
description: object
|
||||||
@@ -382,6 +411,24 @@ paths:
|
|||||||
tags:
|
tags:
|
||||||
- Account API
|
- Account API
|
||||||
operationId: ApiController.DeleteLdap
|
operationId: ApiController.DeleteLdap
|
||||||
|
/api/delete-model:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Model API
|
||||||
|
description: delete model
|
||||||
|
operationId: ApiController.DeleteModel
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
description: The details of the model
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/object.Model'
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controllers.Response'
|
||||||
/api/delete-organization:
|
/api/delete-organization:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@@ -578,6 +625,43 @@ paths:
|
|||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/controllers.Response'
|
$ref: '#/definitions/controllers.Response'
|
||||||
|
/api/get-app-login:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Login API
|
||||||
|
description: get application login
|
||||||
|
operationId: ApiController.GetApplicationLogin
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: clientId
|
||||||
|
description: client id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: responseType
|
||||||
|
description: response type
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: redirectUri
|
||||||
|
description: redirect uri
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: scope
|
||||||
|
description: scope
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: state
|
||||||
|
description: state
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Response'
|
||||||
/api/get-application:
|
/api/get-application:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@@ -700,6 +784,42 @@ paths:
|
|||||||
tags:
|
tags:
|
||||||
- Account API
|
- Account API
|
||||||
operationId: ApiController.GetLdaps
|
operationId: ApiController.GetLdaps
|
||||||
|
/api/get-model:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Model API
|
||||||
|
description: get model
|
||||||
|
operationId: ApiController.GetModel
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: id
|
||||||
|
description: The id of the model
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/object.Model'
|
||||||
|
/api/get-models:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Model API
|
||||||
|
description: get models
|
||||||
|
operationId: ApiController.GetModels
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: owner
|
||||||
|
description: The owner of models
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/object.Model'
|
||||||
/api/get-organization:
|
/api/get-organization:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@@ -901,9 +1021,7 @@ paths:
|
|||||||
"200":
|
"200":
|
||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
type: array
|
$ref: '#/definitions/object.Record'
|
||||||
items:
|
|
||||||
$ref: '#/definitions/object.Records'
|
|
||||||
/api/get-records-filter:
|
/api/get-records-filter:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@@ -912,18 +1030,17 @@ paths:
|
|||||||
operationId: ApiController.GetRecordsByFilter
|
operationId: ApiController.GetRecordsByFilter
|
||||||
parameters:
|
parameters:
|
||||||
- in: body
|
- in: body
|
||||||
name: body
|
name: filter
|
||||||
description: filter Record message
|
description: filter Record message
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/object.Records'
|
type: string
|
||||||
|
type: string
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
type: array
|
$ref: '#/definitions/object.Record'
|
||||||
items:
|
|
||||||
$ref: '#/definitions/object.Records'
|
|
||||||
/api/get-resource:
|
/api/get-resource:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@@ -1216,6 +1333,23 @@ paths:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/object.Webhook'
|
$ref: '#/definitions/object.Webhook'
|
||||||
|
/api/invoice-payment:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Payment API
|
||||||
|
description: invoice payment
|
||||||
|
operationId: ApiController.InvoicePayment
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: id
|
||||||
|
description: The id of the payment
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controllers.Response'
|
||||||
/api/login:
|
/api/login:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@@ -1224,21 +1358,51 @@ paths:
|
|||||||
operationId: ApiController.Login
|
operationId: ApiController.Login
|
||||||
parameters:
|
parameters:
|
||||||
- in: query
|
- in: query
|
||||||
name: oAuthParams
|
name: clientId
|
||||||
description: oAuth parameters
|
description: clientId
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: responseType
|
||||||
|
description: responseType
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: redirectUri
|
||||||
|
description: redirectUri
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: scope
|
||||||
|
description: scope
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: state
|
||||||
|
description: state
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: nonce
|
||||||
|
description: nonce
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: code_challenge_method
|
||||||
|
description: code_challenge_method
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: code_challenge
|
||||||
|
description: code_challenge
|
||||||
|
type: string
|
||||||
- in: body
|
- in: body
|
||||||
name: body
|
name: form
|
||||||
description: Login information
|
description: Login information
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/RequestForm'
|
$ref: '#/definitions/controllers.RequestForm'
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/controllers.api_controller.Response'
|
$ref: '#/definitions/Response'
|
||||||
/api/login/oauth/access_token:
|
/api/login/oauth/access_token:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@@ -1424,6 +1588,24 @@ paths:
|
|||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/controllers.Response'
|
$ref: '#/definitions/controllers.Response'
|
||||||
|
/api/run-syncer:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Syncer API
|
||||||
|
description: run syncer
|
||||||
|
operationId: ApiController.RunSyncer
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
description: The details of the syncer
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/object.Syncer'
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controllers.Response'
|
||||||
/api/send-verification-code:
|
/api/send-verification-code:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@@ -1493,42 +1675,6 @@ paths:
|
|||||||
tags:
|
tags:
|
||||||
- Login API
|
- Login API
|
||||||
/api/update-application:
|
/api/update-application:
|
||||||
get:
|
|
||||||
tags:
|
|
||||||
- Login API
|
|
||||||
description: get application login
|
|
||||||
operationId: ApiController.GetApplicationLogin
|
|
||||||
parameters:
|
|
||||||
- in: query
|
|
||||||
name: clientId
|
|
||||||
description: client id
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
- in: query
|
|
||||||
name: responseType
|
|
||||||
description: response type
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
- in: query
|
|
||||||
name: redirectUri
|
|
||||||
description: redirect uri
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
- in: query
|
|
||||||
name: scope
|
|
||||||
description: scope
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
- in: query
|
|
||||||
name: state
|
|
||||||
description: state
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: The Response object
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/controllers.api_controller.Response'
|
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- Application API
|
- Application API
|
||||||
@@ -1579,6 +1725,29 @@ paths:
|
|||||||
tags:
|
tags:
|
||||||
- Account API
|
- Account API
|
||||||
operationId: ApiController.UpdateLdap
|
operationId: ApiController.UpdateLdap
|
||||||
|
/api/update-model:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Model API
|
||||||
|
description: update model
|
||||||
|
operationId: ApiController.UpdateModel
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: id
|
||||||
|
description: The id of the model
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
description: The details of the model
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/object.Model'
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controllers.Response'
|
||||||
/api/update-organization:
|
/api/update-organization:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@@ -1830,27 +1999,97 @@ paths:
|
|||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/object.Userinfo'
|
$ref: '#/definitions/object.Userinfo'
|
||||||
|
/api/verify-captcha:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Verification API
|
||||||
|
operationId: ApiController.VerifyCaptcha
|
||||||
definitions:
|
definitions:
|
||||||
2127.0xc00036c600.false:
|
2200.0xc0003c4b70.false:
|
||||||
title: "false"
|
title: "false"
|
||||||
type: object
|
type: object
|
||||||
2161.0xc00036c630.false:
|
2235.0xc0003c4ba0.false:
|
||||||
title: "false"
|
title: "false"
|
||||||
type: object
|
type: object
|
||||||
RequestForm:
|
|
||||||
title: RequestForm
|
|
||||||
type: object
|
|
||||||
Response:
|
Response:
|
||||||
title: Response
|
title: Response
|
||||||
type: object
|
type: object
|
||||||
|
controllers.EmailForm:
|
||||||
|
title: EmailForm
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
content:
|
||||||
|
type: string
|
||||||
|
receivers:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
sender:
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
controllers.RequestForm:
|
||||||
|
title: RequestForm
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
affiliation:
|
||||||
|
type: string
|
||||||
|
application:
|
||||||
|
type: string
|
||||||
|
autoSignin:
|
||||||
|
type: boolean
|
||||||
|
code:
|
||||||
|
type: string
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
emailCode:
|
||||||
|
type: string
|
||||||
|
firstName:
|
||||||
|
type: string
|
||||||
|
idCard:
|
||||||
|
type: string
|
||||||
|
lastName:
|
||||||
|
type: string
|
||||||
|
method:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
organization:
|
||||||
|
type: string
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
phone:
|
||||||
|
type: string
|
||||||
|
phoneCode:
|
||||||
|
type: string
|
||||||
|
phonePrefix:
|
||||||
|
type: string
|
||||||
|
provider:
|
||||||
|
type: string
|
||||||
|
redirectUri:
|
||||||
|
type: string
|
||||||
|
region:
|
||||||
|
type: string
|
||||||
|
relayState:
|
||||||
|
type: string
|
||||||
|
samlRequest:
|
||||||
|
type: string
|
||||||
|
samlResponse:
|
||||||
|
type: string
|
||||||
|
state:
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
controllers.Response:
|
controllers.Response:
|
||||||
title: Response
|
title: Response
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
data:
|
data:
|
||||||
$ref: '#/definitions/2127.0xc00036c600.false'
|
$ref: '#/definitions/2200.0xc0003c4b70.false'
|
||||||
data2:
|
data2:
|
||||||
$ref: '#/definitions/2161.0xc00036c630.false'
|
$ref: '#/definitions/2235.0xc0003c4ba0.false'
|
||||||
msg:
|
msg:
|
||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
@@ -1859,25 +2098,33 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
sub:
|
sub:
|
||||||
type: string
|
type: string
|
||||||
controllers.api_controller.Response:
|
controllers.SmsForm:
|
||||||
title: Response
|
title: SmsForm
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
data:
|
content:
|
||||||
$ref: '#/definitions/2127.0xc00036c600.false'
|
type: string
|
||||||
data2:
|
organizationId:
|
||||||
$ref: '#/definitions/2161.0xc00036c630.false'
|
type: string
|
||||||
msg:
|
receivers:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
jose.JSONWebKey:
|
||||||
|
title: JSONWebKey
|
||||||
|
type: object
|
||||||
|
object.AccountItem:
|
||||||
|
title: AccountItem
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
modifyRule:
|
||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
status:
|
viewRule:
|
||||||
type: string
|
type: string
|
||||||
sub:
|
visible:
|
||||||
type: string
|
type: boolean
|
||||||
emailForm:
|
|
||||||
title: emailForm
|
|
||||||
type: object
|
|
||||||
object.Adapter:
|
object.Adapter:
|
||||||
title: Adapter
|
title: Adapter
|
||||||
type: object
|
type: object
|
||||||
@@ -2037,10 +2284,80 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
username:
|
username:
|
||||||
type: string
|
type: string
|
||||||
|
object.Model:
|
||||||
|
title: Model
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
createdTime:
|
||||||
|
type: string
|
||||||
|
displayName:
|
||||||
|
type: string
|
||||||
|
isEnabled:
|
||||||
|
type: boolean
|
||||||
|
modelText:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
owner:
|
||||||
|
type: string
|
||||||
|
object.OidcDiscovery:
|
||||||
|
title: OidcDiscovery
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
authorization_endpoint:
|
||||||
|
type: string
|
||||||
|
claims_supported:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
grant_types_supported:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
id_token_signing_alg_values_supported:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
introspection_endpoint:
|
||||||
|
type: string
|
||||||
|
issuer:
|
||||||
|
type: string
|
||||||
|
jwks_uri:
|
||||||
|
type: string
|
||||||
|
request_object_signing_alg_values_supported:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
request_parameter_supported:
|
||||||
|
type: boolean
|
||||||
|
response_modes_supported:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
response_types_supported:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
scopes_supported:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
subject_types_supported:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
token_endpoint:
|
||||||
|
type: string
|
||||||
|
userinfo_endpoint:
|
||||||
|
type: string
|
||||||
object.Organization:
|
object.Organization:
|
||||||
title: Organization
|
title: Organization
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
accountItems:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/object.AccountItem'
|
||||||
createdTime:
|
createdTime:
|
||||||
type: string
|
type: string
|
||||||
defaultAvatar:
|
defaultAvatar:
|
||||||
@@ -2051,6 +2368,8 @@ definitions:
|
|||||||
type: boolean
|
type: boolean
|
||||||
favicon:
|
favicon:
|
||||||
type: string
|
type: string
|
||||||
|
isProfilePublic:
|
||||||
|
type: boolean
|
||||||
masterPassword:
|
masterPassword:
|
||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
@@ -2081,6 +2400,16 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
displayName:
|
displayName:
|
||||||
type: string
|
type: string
|
||||||
|
invoiceRemark:
|
||||||
|
type: string
|
||||||
|
invoiceTaxId:
|
||||||
|
type: string
|
||||||
|
invoiceTitle:
|
||||||
|
type: string
|
||||||
|
invoiceType:
|
||||||
|
type: string
|
||||||
|
invoiceUrl:
|
||||||
|
type: string
|
||||||
message:
|
message:
|
||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
@@ -2091,6 +2420,14 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
payUrl:
|
payUrl:
|
||||||
type: string
|
type: string
|
||||||
|
personEmail:
|
||||||
|
type: string
|
||||||
|
personIdCard:
|
||||||
|
type: string
|
||||||
|
personName:
|
||||||
|
type: string
|
||||||
|
personPhone:
|
||||||
|
type: string
|
||||||
price:
|
price:
|
||||||
type: number
|
type: number
|
||||||
format: double
|
format: double
|
||||||
@@ -2126,6 +2463,8 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
isEnabled:
|
isEnabled:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
model:
|
||||||
|
type: string
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
owner:
|
owner:
|
||||||
@@ -2205,6 +2544,16 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
createdTime:
|
createdTime:
|
||||||
type: string
|
type: string
|
||||||
|
customAuthUrl:
|
||||||
|
type: string
|
||||||
|
customLogo:
|
||||||
|
type: string
|
||||||
|
customScope:
|
||||||
|
type: string
|
||||||
|
customTokenUrl:
|
||||||
|
type: string
|
||||||
|
customUserInfoUrl:
|
||||||
|
type: string
|
||||||
displayName:
|
displayName:
|
||||||
type: string
|
type: string
|
||||||
domain:
|
domain:
|
||||||
@@ -2264,9 +2613,35 @@ definitions:
|
|||||||
type: boolean
|
type: boolean
|
||||||
provider:
|
provider:
|
||||||
$ref: '#/definitions/object.Provider'
|
$ref: '#/definitions/object.Provider'
|
||||||
object.Records:
|
object.Record:
|
||||||
title: Records
|
title: Record
|
||||||
type: object
|
type: object
|
||||||
|
properties:
|
||||||
|
action:
|
||||||
|
type: string
|
||||||
|
clientIp:
|
||||||
|
type: string
|
||||||
|
createdTime:
|
||||||
|
type: string
|
||||||
|
extendedUser:
|
||||||
|
$ref: '#/definitions/object.User'
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
isTriggered:
|
||||||
|
type: boolean
|
||||||
|
method:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
organization:
|
||||||
|
type: string
|
||||||
|
owner:
|
||||||
|
type: string
|
||||||
|
requestUri:
|
||||||
|
type: string
|
||||||
|
user:
|
||||||
|
type: string
|
||||||
object.Role:
|
object.Role:
|
||||||
title: Role
|
title: Role
|
||||||
type: object
|
type: object
|
||||||
@@ -2442,6 +2817,8 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
baidu:
|
baidu:
|
||||||
type: string
|
type: string
|
||||||
|
bilibili:
|
||||||
|
type: string
|
||||||
bio:
|
bio:
|
||||||
type: string
|
type: string
|
||||||
birthday:
|
birthday:
|
||||||
@@ -2452,10 +2829,14 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
createdTime:
|
createdTime:
|
||||||
type: string
|
type: string
|
||||||
|
custom:
|
||||||
|
type: string
|
||||||
dingtalk:
|
dingtalk:
|
||||||
type: string
|
type: string
|
||||||
displayName:
|
displayName:
|
||||||
type: string
|
type: string
|
||||||
|
douyin:
|
||||||
|
type: string
|
||||||
education:
|
education:
|
||||||
type: string
|
type: string
|
||||||
email:
|
email:
|
||||||
@@ -2521,6 +2902,8 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
|
okta:
|
||||||
|
type: string
|
||||||
owner:
|
owner:
|
||||||
type: string
|
type: string
|
||||||
password:
|
password:
|
||||||
@@ -2558,6 +2941,8 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
type:
|
type:
|
||||||
type: string
|
type: string
|
||||||
|
unionId:
|
||||||
|
type: string
|
||||||
updatedTime:
|
updatedTime:
|
||||||
type: string
|
type: string
|
||||||
wechat:
|
wechat:
|
||||||
@@ -2618,9 +3003,6 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
url:
|
url:
|
||||||
type: string
|
type: string
|
||||||
smsForm:
|
|
||||||
title: smsForm
|
|
||||||
type: object
|
|
||||||
xorm.Engine:
|
xorm.Engine:
|
||||||
title: Engine
|
title: Engine
|
||||||
type: object
|
type: object
|
||||||
|
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()
|
||||||
|
}()
|
||||||
|
}
|
@@ -1,13 +1,23 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
|
<script>
|
||||||
|
var _hmt = _hmt || [];
|
||||||
|
(function() {
|
||||||
|
var hm = document.createElement("script");
|
||||||
|
hm.src = "https://hm.baidu.com/hm.js?5998fcd123c220efc0936edf4f250504";
|
||||||
|
var s = document.getElementsByTagName("script")[0];
|
||||||
|
s.parentNode.insertBefore(hm, s);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<!-- <link rel="icon" href="%PUBLIC_URL%/favicon.png" />-->
|
<!-- <link rel="icon" href="%PUBLIC_URL%/favicon.png" />-->
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
content="Web site created using create-react-app"
|
content="Casdoor - An Identity and Access Management (IAM) / Single-Sign-On (SSO) platform with web UI supporting OAuth 2.0, OIDC, SAML and CAS"
|
||||||
/>
|
/>
|
||||||
<link rel="apple-touch-icon" href="https://cdn.casdoor.com/static/favicon.png" />
|
<link rel="apple-touch-icon" href="https://cdn.casdoor.com/static/favicon.png" />
|
||||||
<!--
|
<!--
|
||||||
|
242
web/src/AccountTable.js
Normal file
242
web/src/AccountTable.js
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import {DownOutlined, DeleteOutlined, UpOutlined} from '@ant-design/icons';
|
||||||
|
import {Button, Col, Row, Select, Switch, Table, Tooltip} from 'antd';
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import i18next from "i18next";
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
|
class AccountTable extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
classes: props,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTable(table) {
|
||||||
|
this.props.onUpdateTable(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateField(table, index, key, value) {
|
||||||
|
table[index][key] = value;
|
||||||
|
this.updateTable(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
addRow(table) {
|
||||||
|
let row = {name: Setting.getNewRowNameForTable(table, "Please select an account item"), visible: true};
|
||||||
|
if (table === undefined) {
|
||||||
|
table = [];
|
||||||
|
}
|
||||||
|
table = Setting.addRow(table, row);
|
||||||
|
this.updateTable(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteRow(table, i) {
|
||||||
|
table = Setting.deleteRow(table, i);
|
||||||
|
this.updateTable(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
upRow(table, i) {
|
||||||
|
table = Setting.swapRow(table, i - 1, i);
|
||||||
|
this.updateTable(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
downRow(table, i) {
|
||||||
|
table = Setting.swapRow(table, i, i + 1);
|
||||||
|
this.updateTable(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTable(table) {
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: i18next.t("provider:Name"),
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
render: (text, record, index) => {
|
||||||
|
const items = [
|
||||||
|
{name: "Organization", displayName: i18next.t("general:Organization")},
|
||||||
|
{name: "ID", displayName: i18next.t("general:ID")},
|
||||||
|
{name: "Name", displayName: i18next.t("general:Name")},
|
||||||
|
{name: "Display name", displayName: i18next.t("general:Display name")},
|
||||||
|
{name: "Avatar", displayName: i18next.t("general:Avatar")},
|
||||||
|
{name: "User type", displayName: i18next.t("general:User type")},
|
||||||
|
{name: "Password", displayName: i18next.t("general:Password")},
|
||||||
|
{name: "Email", displayName: i18next.t("general:Email")},
|
||||||
|
{name: "Phone", displayName: i18next.t("general:Phone")},
|
||||||
|
{name: "Country/Region", displayName: i18next.t("user:Country/Region")},
|
||||||
|
{name: "Location", displayName: i18next.t("user:Location")},
|
||||||
|
{name: "Affiliation", displayName: i18next.t("user:Affiliation")},
|
||||||
|
{name: "Title", displayName: i18next.t("user:Title")},
|
||||||
|
{name: "Homepage", displayName: i18next.t("user:Homepage")},
|
||||||
|
{name: "Bio", displayName: i18next.t("user:Bio")},
|
||||||
|
{name: "Tag", displayName: i18next.t("user:Tag")},
|
||||||
|
{name: "Signup application", displayName: i18next.t("general:Signup application")},
|
||||||
|
{name: "3rd-party logins", displayName: i18next.t("user:3rd-party logins")},
|
||||||
|
{name: "Properties", displayName: i18next.t("user:Properties")},
|
||||||
|
{name: "Is admin", displayName: i18next.t("user:Is admin")},
|
||||||
|
{name: "Is global admin", displayName: i18next.t("user:Is global admin")},
|
||||||
|
{name: "Is forbidden", displayName: i18next.t("user:Is forbidden")},
|
||||||
|
{name: "Is deleted", displayName: i18next.t("user:Is deleted")},
|
||||||
|
];
|
||||||
|
|
||||||
|
const getItemDisplayName = (text) => {
|
||||||
|
const item = items.filter(item => item.name === text);
|
||||||
|
if (item.length === 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return item[0].displayName;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select virtual={false} style={{width: '100%'}}
|
||||||
|
value={getItemDisplayName(text)}
|
||||||
|
onChange={value => {
|
||||||
|
this.updateField(table, index, 'name', value);
|
||||||
|
}} >
|
||||||
|
{
|
||||||
|
Setting.getDeduplicatedArray(items, table, "name").map((item, index) => <Option key={index} value={item.name}>{item.displayName}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("provider:visible"),
|
||||||
|
dataIndex: 'visible',
|
||||||
|
key: 'visible',
|
||||||
|
width: '120px',
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Switch checked={text} onChange={checked => {
|
||||||
|
this.updateField(table, index, 'visible', checked);
|
||||||
|
}} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("organization:viewRule"),
|
||||||
|
dataIndex: 'viewRule',
|
||||||
|
key: 'viewRule',
|
||||||
|
width: '155px',
|
||||||
|
render: (text, record, index) => {
|
||||||
|
if (!record.visible) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let options = [
|
||||||
|
{id: 'Public', name: 'Public'},
|
||||||
|
{id: 'Self', name: 'Self'},
|
||||||
|
{id: 'Admin', name: 'Admin'},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select virtual={false} style={{width: '100%'}} value={text} onChange={(value => {
|
||||||
|
this.updateField(table, index, 'viewRule', value);
|
||||||
|
})}>
|
||||||
|
{
|
||||||
|
options.map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("organization:modifyRule"),
|
||||||
|
dataIndex: 'modifyRule',
|
||||||
|
key: 'modifyRule',
|
||||||
|
width: '155px',
|
||||||
|
render: (text, record, index) => {
|
||||||
|
if (!record.visible) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let options;
|
||||||
|
if (record.viewRule === "Admin") {
|
||||||
|
options = [
|
||||||
|
{id: 'Admin', name: 'Admin'},
|
||||||
|
{id: 'Immutable', name: 'Immutable'},
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
options = [
|
||||||
|
{id: 'Self', name: 'Self'},
|
||||||
|
{id: 'Admin', name: 'Admin'},
|
||||||
|
{id: 'Immutable', name: 'Immutable'},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select virtual={false} style={{width: '100%'}} value={text} onChange={(value => {
|
||||||
|
this.updateField(table, index, 'modifyRule', value);
|
||||||
|
})}>
|
||||||
|
{
|
||||||
|
options.map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Action"),
|
||||||
|
key: 'action',
|
||||||
|
width: '100px',
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Tooltip placement="bottomLeft" title={i18next.t("general:Up")}>
|
||||||
|
<Button style={{marginRight: "5px"}} disabled={index === 0} icon={<UpOutlined />} size="small" onClick={() => this.upRow(table, index)} />
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip placement="topLeft" title={i18next.t("general:Down")}>
|
||||||
|
<Button style={{marginRight: "5px"}} disabled={index === table.length - 1} icon={<DownOutlined />} size="small" onClick={() => this.downRow(table, index)} />
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip placement="topLeft" title={i18next.t("general:Delete")}>
|
||||||
|
<Button icon={<DeleteOutlined />} size="small" onClick={() => this.deleteRow(table, index)} />
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table scroll={{x: 'max-content'}} rowKey="name" columns={columns} dataSource={table} size="middle" bordered pagination={false}
|
||||||
|
title={() => (
|
||||||
|
<div>
|
||||||
|
{this.props.title}
|
||||||
|
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col span={24}>
|
||||||
|
{
|
||||||
|
this.renderTable(this.props.table)
|
||||||
|
}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AccountTable;
|
@@ -69,6 +69,8 @@ import PromptPage from "./auth/PromptPage";
|
|||||||
import OdicDiscoveryPage from "./auth/OidcDiscoveryPage";
|
import OdicDiscoveryPage from "./auth/OidcDiscoveryPage";
|
||||||
import SamlCallback from './auth/SamlCallback';
|
import SamlCallback from './auth/SamlCallback';
|
||||||
import CasLogout from "./auth/CasLogout";
|
import CasLogout from "./auth/CasLogout";
|
||||||
|
import ModelListPage from "./ModelListPage";
|
||||||
|
import ModelEditPage from "./ModelEditPage";
|
||||||
|
|
||||||
const { Header, Footer } = Layout;
|
const { Header, Footer } = Layout;
|
||||||
|
|
||||||
@@ -118,6 +120,8 @@ class App extends Component {
|
|||||||
this.setState({ selectedMenuKey: '/roles' });
|
this.setState({ selectedMenuKey: '/roles' });
|
||||||
} else if (uri.includes('/permissions')) {
|
} else if (uri.includes('/permissions')) {
|
||||||
this.setState({ selectedMenuKey: '/permissions' });
|
this.setState({ selectedMenuKey: '/permissions' });
|
||||||
|
} else if (uri.includes('/models')) {
|
||||||
|
this.setState({ selectedMenuKey: '/models' });
|
||||||
} else if (uri.includes('/providers')) {
|
} else if (uri.includes('/providers')) {
|
||||||
this.setState({ selectedMenuKey: '/providers' });
|
this.setState({ selectedMenuKey: '/providers' });
|
||||||
} else if (uri.includes('/applications')) {
|
} else if (uri.includes('/applications')) {
|
||||||
@@ -382,6 +386,13 @@ class App extends Component {
|
|||||||
</Link>
|
</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
);
|
);
|
||||||
|
res.push(
|
||||||
|
<Menu.Item key="/models">
|
||||||
|
<Link to="/models">
|
||||||
|
{i18next.t("general:Models")}
|
||||||
|
</Link>
|
||||||
|
</Menu.Item>
|
||||||
|
);
|
||||||
res.push(
|
res.push(
|
||||||
<Menu.Item key="/providers">
|
<Menu.Item key="/providers">
|
||||||
<Link to="/providers">
|
<Link to="/providers">
|
||||||
@@ -514,6 +525,8 @@ class App extends Component {
|
|||||||
<Route exact path="/roles/:organizationName/:roleName" render={(props) => this.renderLoginIfNotLoggedIn(<RoleEditPage account={this.state.account} {...props} />)}/>
|
<Route exact path="/roles/:organizationName/:roleName" render={(props) => this.renderLoginIfNotLoggedIn(<RoleEditPage account={this.state.account} {...props} />)}/>
|
||||||
<Route exact path="/permissions" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionListPage account={this.state.account} {...props} />)}/>
|
<Route exact path="/permissions" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionListPage account={this.state.account} {...props} />)}/>
|
||||||
<Route exact path="/permissions/:organizationName/:permissionName" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionEditPage account={this.state.account} {...props} />)}/>
|
<Route exact path="/permissions/:organizationName/:permissionName" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionEditPage account={this.state.account} {...props} />)}/>
|
||||||
|
<Route exact path="/models" render={(props) => this.renderLoginIfNotLoggedIn(<ModelListPage account={this.state.account} {...props} />)}/>
|
||||||
|
<Route exact path="/models/:organizationName/:modelName" render={(props) => this.renderLoginIfNotLoggedIn(<ModelEditPage account={this.state.account} {...props} />)}/>
|
||||||
<Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)}/>
|
<Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)}/>
|
||||||
<Route exact path="/providers/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)}/>
|
<Route exact path="/providers/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)}/>
|
||||||
<Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)}/>
|
<Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)}/>
|
||||||
|
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Button, Card, Col, Input, Popover, Row, Select, Switch, Upload} from 'antd';
|
import {Button, Card, Col, Input, Popover, Row, Select, Switch, Upload} from 'antd';
|
||||||
import {LinkOutlined, UploadOutlined} from "@ant-design/icons";
|
import {CopyOutlined, LinkOutlined, UploadOutlined} from "@ant-design/icons";
|
||||||
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
||||||
import * as CertBackend from "./backend/CertBackend";
|
import * as CertBackend from "./backend/CertBackend";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
@@ -28,11 +28,13 @@ import UrlTable from "./UrlTable";
|
|||||||
import ProviderTable from "./ProviderTable";
|
import ProviderTable from "./ProviderTable";
|
||||||
import SignupTable from "./SignupTable";
|
import SignupTable from "./SignupTable";
|
||||||
import PromptPage from "./auth/PromptPage";
|
import PromptPage from "./auth/PromptPage";
|
||||||
|
import copy from "copy-to-clipboard";
|
||||||
|
|
||||||
import {Controlled as CodeMirror} from 'react-codemirror2';
|
import {Controlled as CodeMirror} from 'react-codemirror2';
|
||||||
import "codemirror/lib/codemirror.css";
|
import "codemirror/lib/codemirror.css";
|
||||||
require('codemirror/theme/material-darker.css');
|
require('codemirror/theme/material-darker.css');
|
||||||
require("codemirror/mode/htmlmixed/htmlmixed");
|
require("codemirror/mode/htmlmixed/htmlmixed");
|
||||||
|
require("codemirror/mode/xml/xml");
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
@@ -48,6 +50,7 @@ class ApplicationEditPage extends React.Component {
|
|||||||
providers: [],
|
providers: [],
|
||||||
uploading: false,
|
uploading: false,
|
||||||
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||||
|
samlMetadata: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,6 +59,7 @@ class ApplicationEditPage extends React.Component {
|
|||||||
this.getOrganizations();
|
this.getOrganizations();
|
||||||
this.getCerts();
|
this.getCerts();
|
||||||
this.getProviders();
|
this.getProviders();
|
||||||
|
this.getSamlMetadata();
|
||||||
}
|
}
|
||||||
|
|
||||||
getApplication() {
|
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) {
|
parseApplicationField(key, value) {
|
||||||
if (["expireInHours", "refreshExpireInHours"].includes(key)) {
|
if (["expireInHours", "refreshExpireInHours"].includes(key)) {
|
||||||
value = Setting.myParseInt(value);
|
value = Setting.myParseInt(value);
|
||||||
@@ -168,7 +181,7 @@ class ApplicationEditPage extends React.Component {
|
|||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:Logo"), i18next.t("general:Logo - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Logo"), i18next.t("general:Logo - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} style={(Setting.isMobile()) ? {maxWidth:'100%'} :{}}>
|
<Col span={22} style={(Setting.isMobile()) ? {maxWidth: '100%'} :{}}>
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
|
||||||
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
|
||||||
@@ -390,7 +403,7 @@ class ApplicationEditPage extends React.Component {
|
|||||||
}}/>
|
}}/>
|
||||||
<Upload maxCount={1} accept=".html" showUploadList={false}
|
<Upload maxCount={1} accept=".html" showUploadList={false}
|
||||||
beforeUpload={file => {return false}} onChange={info => {this.handleUpload(info)}}>
|
beforeUpload={file => {return false}} onChange={info => {this.handleUpload(info)}}>
|
||||||
<Button icon={<UploadOutlined />} loading={this.state.uploading}>Click to Upload</Button>
|
<Button icon={<UploadOutlined />} loading={this.state.uploading}>{i18next.t("general:Click to Upload")}</Button>
|
||||||
</Upload>
|
</Upload>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@@ -454,12 +467,25 @@ class ApplicationEditPage extends React.Component {
|
|||||||
{id: "password", name: "Password"},
|
{id: "password", name: "Password"},
|
||||||
{id: "client_credentials", name: "Client Credentials"},
|
{id: "client_credentials", name: "Client Credentials"},
|
||||||
{id: "token", name: "Token"},
|
{id: "token", name: "Token"},
|
||||||
{id: "id_token",name:"ID Token"},
|
{id: "id_token", name: "ID Token"},
|
||||||
].map((item, index)=><Option key={index} value={item.id}>{item.name}</Option>)
|
{id: "refresh_token", name: "Refresh Token"},
|
||||||
|
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||||
}
|
}
|
||||||
</Select>
|
</Select>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 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) => {}}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:Providers"), i18next.t("general:Providers - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Providers"), i18next.t("general:Providers - Tooltip"))} :
|
||||||
@@ -479,7 +505,7 @@ class ApplicationEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
{
|
{
|
||||||
this.renderPreview()
|
this.renderSignupSigninPreview()
|
||||||
}
|
}
|
||||||
</Row>
|
</Row>
|
||||||
{
|
{
|
||||||
@@ -503,29 +529,33 @@ class ApplicationEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
{
|
{
|
||||||
this.renderPreview2()
|
this.renderPromptPreview()
|
||||||
}
|
}
|
||||||
</Row>
|
</Row>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPreview() {
|
renderSignupSigninPreview() {
|
||||||
let signUpUrl = `/signup/${this.state.application.name}`;
|
let signUpUrl = `/signup/${this.state.application.name}`;
|
||||||
let signInUrl = `/login/oauth/authorize?client_id=${this.state.application.clientId}&response_type=code&redirect_uri=${this.state.application.redirectUris[0]}&scope=read&state=casdoor`;
|
let signInUrl = `/login/oauth/authorize?client_id=${this.state.application.clientId}&response_type=code&redirect_uri=${this.state.application.redirectUris[0]}&scope=read&state=casdoor`;
|
||||||
|
let maskStyle = {position: 'absolute', top: '0px', left: '0px', zIndex: 10, height: '100%', width: '100%', background: 'rgba(0,0,0,0.4)'};
|
||||||
if (!this.state.application.enablePassword) {
|
if (!this.state.application.enablePassword) {
|
||||||
signUpUrl = signInUrl.replace("/login/oauth/authorize", "/signup/oauth/authorize");
|
signUpUrl = signInUrl.replace("/login/oauth/authorize", "/signup/oauth/authorize");
|
||||||
}
|
}
|
||||||
if (!Setting.isMobile()) {
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Col span={11} style={{display:"flex", flexDirection: "column"}}>
|
<Col span={11}>
|
||||||
<a style={{marginBottom: "10px", display: "flex"}} target="_blank" rel="noreferrer" href={signUpUrl}>
|
<Button style={{marginBottom: "10px"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
|
||||||
<Button type="primary">{i18next.t("application:Test signup page..")}</Button>
|
copy(`${window.location.origin}${signUpUrl}`);
|
||||||
</a>
|
Setting.showMessage("success", i18next.t("application:Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser"));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18next.t("application:Copy signup page URL")}
|
||||||
|
</Button>
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<div style={{position: "relative", width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems:"center", overflow:"auto", flexDirection:"column", flex: "auto"}}>
|
||||||
<div style={{width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems:"center", overflow:"auto", flexDirection:"column", flex: "auto"}}>
|
|
||||||
{
|
{
|
||||||
this.state.application.enablePassword ? (
|
this.state.application.enablePassword ? (
|
||||||
<SignupPage application={this.state.application} />
|
<SignupPage application={this.state.application} />
|
||||||
@@ -533,64 +563,45 @@ class ApplicationEditPage extends React.Component {
|
|||||||
<LoginPage type={"login"} mode={"signup"} application={this.state.application} />
|
<LoginPage type={"login"} mode={"signup"} application={this.state.application} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
<div style={maskStyle}></div>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={11} style={{display:"flex", flexDirection: "column"}}>
|
<Col span={11}>
|
||||||
<a style={{marginBottom: "10px", display: "flex"}} target="_blank" rel="noreferrer" href={signInUrl}>
|
<Button style={{marginBottom: "10px"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
|
||||||
<Button type="primary">{i18next.t("application:Test signin page..")}</Button>
|
copy(`${window.location.origin}${signInUrl}`);
|
||||||
</a>
|
Setting.showMessage("success", i18next.t("application:Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser"));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18next.t("application:Copy signin page URL")}
|
||||||
|
</Button>
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<div style={{position: "relative", width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems:"center", overflow:"auto", flexDirection:"column", flex: "auto"}}>
|
||||||
<div style={{width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems:"center", overflow:"auto", flexDirection:"column", flex: "auto"}}>
|
|
||||||
<LoginPage type={"login"} mode={"signin"} application={this.state.application} />
|
|
||||||
</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} />
|
<LoginPage type={"login"} mode={"signin"} application={this.state.application} />
|
||||||
|
<div style={maskStyle}></div>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
renderPreview2() {
|
renderPromptPreview() {
|
||||||
let promptUrl = `/prompt/${this.state.application.name}`;
|
let promptUrl = `/prompt/${this.state.application.name}`;
|
||||||
|
let maskStyle = {position: 'absolute', top: '0px', left: '0px', zIndex: 10, height: '100%', width: '100%', background: 'rgba(0,0,0,0.4)'};
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<Col span={11}>
|
||||||
<Col span={(Setting.isMobile()) ? 24 : 11} style={{display:"flex", flexDirection: "column", flex: "auto"}} >
|
<Button style={{marginBottom: "10px"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
|
||||||
<a style={{marginBottom: "10px"}} target="_blank" rel="noreferrer" href={promptUrl}>
|
copy(`${window.location.origin}${promptUrl}`);
|
||||||
<Button type="primary">{i18next.t("application:Test prompt page..")}</Button>
|
Setting.showMessage("success", i18next.t("application:Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser"));
|
||||||
</a>
|
}}
|
||||||
<br style={(Setting.isMobile()) ? {display: "none"} : {}} />
|
>
|
||||||
<br style={(Setting.isMobile()) ? {display: "none"} : {}} />
|
{i18next.t("application:Copy prompt page URL")}
|
||||||
<div style={{width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", flexDirection: "column", flex: "auto"}}>
|
</Button>
|
||||||
<PromptPage application={this.state.application} account={this.props.account} />
|
<br/>
|
||||||
</div>
|
<div style={{position: "relative", width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", flexDirection: "column", flex: "auto"}}>
|
||||||
</Col>
|
<PromptPage application={this.state.application} account={this.props.account} />
|
||||||
</React.Fragment>
|
<div style={maskStyle}></div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -43,7 +43,7 @@ class ApplicationListPage extends BaseListPage {
|
|||||||
{name: "Display name", visible: true, required: true, rule: "None"},
|
{name: "Display name", visible: true, required: true, rule: "None"},
|
||||||
{name: "Password", visible: true, required: true, rule: "None"},
|
{name: "Password", visible: true, required: true, rule: "None"},
|
||||||
{name: "Confirm 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: "Phone", visible: true, required: true, rule: "None"},
|
||||||
{name: "Agreement", visible: true, required: true, rule: "None"},
|
{name: "Agreement", visible: true, required: true, rule: "None"},
|
||||||
],
|
],
|
||||||
|
@@ -136,7 +136,7 @@ class CertEditPage extends React.Component {
|
|||||||
})}>
|
})}>
|
||||||
{
|
{
|
||||||
[
|
[
|
||||||
{id: 'RSA', name: 'RSA'},
|
{id: 'RS256', name: 'RS256'},
|
||||||
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||||
}
|
}
|
||||||
</Select>
|
</Select>
|
||||||
|
@@ -32,7 +32,7 @@ class CertListPage extends BaseListPage {
|
|||||||
displayName: `New Cert - ${randomName}`,
|
displayName: `New Cert - ${randomName}`,
|
||||||
scope: "JWT",
|
scope: "JWT",
|
||||||
type: "x509",
|
type: "x509",
|
||||||
cryptoAlgorithm: "RSA",
|
cryptoAlgorithm: "RS256",
|
||||||
bitSize: 4096,
|
bitSize: 4096,
|
||||||
expireInYears: 20,
|
expireInYears: 20,
|
||||||
publicKey: "",
|
publicKey: "",
|
||||||
@@ -131,7 +131,7 @@ class CertListPage extends BaseListPage {
|
|||||||
key: 'cryptoAlgorithm',
|
key: 'cryptoAlgorithm',
|
||||||
filterMultiple: false,
|
filterMultiple: false,
|
||||||
filters: [
|
filters: [
|
||||||
{text: 'RSA', value: 'RSA'},
|
{text: 'RS256', value: 'RS256'},
|
||||||
],
|
],
|
||||||
width: '190px',
|
width: '190px',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
|
@@ -27,7 +27,6 @@ export const CropperDiv = (props) => {
|
|||||||
const [confirmLoading, setConfirmLoading] = React.useState(false);
|
const [confirmLoading, setConfirmLoading] = React.useState(false);
|
||||||
const {title} = props;
|
const {title} = props;
|
||||||
const {user} = props;
|
const {user} = props;
|
||||||
const {account} = props;
|
|
||||||
const {buttonText} = props;
|
const {buttonText} = props;
|
||||||
let uploadButton;
|
let uploadButton;
|
||||||
|
|
||||||
|
208
web/src/ModelEditPage.js
Normal file
208
web/src/ModelEditPage.js
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import {Button, Card, Col, Input, Row, Select, Switch} from 'antd';
|
||||||
|
import * as ModelBackend from "./backend/ModelBackend";
|
||||||
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import TextArea from "antd/es/input/TextArea";
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
|
class ModelEditPage extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
classes: props,
|
||||||
|
organizationName: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
|
||||||
|
modelName: props.match.params.modelName,
|
||||||
|
model: null,
|
||||||
|
organizations: [],
|
||||||
|
users: [],
|
||||||
|
models: [],
|
||||||
|
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
UNSAFE_componentWillMount() {
|
||||||
|
this.getModel();
|
||||||
|
this.getOrganizations();
|
||||||
|
}
|
||||||
|
|
||||||
|
getModel() {
|
||||||
|
ModelBackend.getModel(this.state.organizationName, this.state.modelName)
|
||||||
|
.then((model) => {
|
||||||
|
this.setState({
|
||||||
|
model: model,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.getModels(model.owner);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrganizations() {
|
||||||
|
OrganizationBackend.getOrganizations("admin")
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
organizations: (res.msg === undefined) ? res : [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getModels(organizationName) {
|
||||||
|
ModelBackend.getModels(organizationName)
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
models: res,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
parseModelField(key, value) {
|
||||||
|
if ([""].includes(key)) {
|
||||||
|
value = Setting.myParseInt(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateModelField(key, value) {
|
||||||
|
value = this.parseModelField(key, value);
|
||||||
|
|
||||||
|
let model = this.state.model;
|
||||||
|
model[key] = value;
|
||||||
|
this.setState({
|
||||||
|
model: model,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderModel() {
|
||||||
|
return (
|
||||||
|
<Card size="small" title={
|
||||||
|
<div>
|
||||||
|
{this.state.mode === "add" ? i18next.t("model:New Model") : i18next.t("model:Edit Model")}
|
||||||
|
<Button onClick={() => this.submitModelEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||||
|
<Button style={{marginLeft: '20px'}} type="primary" onClick={() => this.submitModelEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||||
|
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} onClick={() => this.deleteModel()}>{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}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: '100%'}} value={this.state.model.owner} onChange={(value => {this.updateModelField('owner', 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}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.model.name} onChange={e => {
|
||||||
|
this.updateModelField('name', e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<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.model.displayName} onChange={e => {
|
||||||
|
this.updateModelField('displayName', e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("model:Model text"), i18next.t("model:Model text - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22}>
|
||||||
|
<TextArea rows={10} value={this.state.model.modelText} onChange={e => {
|
||||||
|
this.updateModelField('modelText', e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={1} >
|
||||||
|
<Switch checked={this.state.model.isEnabled} onChange={checked => {
|
||||||
|
this.updateModelField('isEnabled', checked);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
submitModelEdit(willExist) {
|
||||||
|
let model = Setting.deepCopy(this.state.model);
|
||||||
|
ModelBackend.updateModel(this.state.organizationName, this.state.modelName, model)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.msg === "") {
|
||||||
|
Setting.showMessage("success", `Successfully saved`);
|
||||||
|
this.setState({
|
||||||
|
modelName: this.state.model.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (willExist) {
|
||||||
|
this.props.history.push(`/models`);
|
||||||
|
} else {
|
||||||
|
this.props.history.push(`/models/${this.state.model.owner}/${this.state.model.name}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", res.msg);
|
||||||
|
this.updateModelField('name', this.state.modelName);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `Failed to connect to server: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteModel() {
|
||||||
|
ModelBackend.deleteModel(this.state.model)
|
||||||
|
.then(() => {
|
||||||
|
this.props.history.push(`/models`);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `Model failed to delete: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
this.state.model !== null ? this.renderModel() : null
|
||||||
|
}
|
||||||
|
<div style={{marginTop: '20px', marginLeft: '40px'}}>
|
||||||
|
<Button size="large" onClick={() => this.submitModelEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||||
|
<Button style={{marginLeft: '20px'}} type="primary" size="large" onClick={() => this.submitModelEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||||
|
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} size="large" onClick={() => this.deleteModel()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ModelEditPage;
|
201
web/src/ModelListPage.js
Normal file
201
web/src/ModelListPage.js
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import {Link} from "react-router-dom";
|
||||||
|
import {Button, Popconfirm, Switch, Table} from 'antd';
|
||||||
|
import moment from "moment";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import * as ModelBackend from "./backend/ModelBackend";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import BaseListPage from "./BaseListPage";
|
||||||
|
|
||||||
|
class ModelListPage extends BaseListPage {
|
||||||
|
newModel() {
|
||||||
|
const randomName = Setting.getRandomName();
|
||||||
|
return {
|
||||||
|
owner: "built-in",
|
||||||
|
name: `model_${randomName}`,
|
||||||
|
createdTime: moment().format(),
|
||||||
|
displayName: `New Model - ${randomName}`,
|
||||||
|
modelText: "",
|
||||||
|
isEnabled: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addModel() {
|
||||||
|
const newModel = this.newModel();
|
||||||
|
ModelBackend.addModel(newModel)
|
||||||
|
.then((res) => {
|
||||||
|
this.props.history.push({pathname: `/models/${newModel.owner}/${newModel.name}`, mode: "add"});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `Model failed to add: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteModel(i) {
|
||||||
|
ModelBackend.deleteModel(this.state.data[i])
|
||||||
|
.then((res) => {
|
||||||
|
Setting.showMessage("success", `Model deleted successfully`);
|
||||||
|
this.setState({
|
||||||
|
data: Setting.deleteRow(this.state.data, i),
|
||||||
|
pagination: {total: this.state.pagination.total - 1},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `Model failed to delete: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTable(models) {
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Organization"),
|
||||||
|
dataIndex: 'owner',
|
||||||
|
key: 'owner',
|
||||||
|
width: '120px',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps('owner'),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Link to={`/organizations/${text}`}>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Name"),
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
width: '150px',
|
||||||
|
fixed: 'left',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps('name'),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Link to={`/models/${text}`}>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Created time"),
|
||||||
|
dataIndex: 'createdTime',
|
||||||
|
key: 'createdTime',
|
||||||
|
width: '160px',
|
||||||
|
sorter: true,
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return Setting.getFormattedDate(text);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Display name"),
|
||||||
|
dataIndex: 'displayName',
|
||||||
|
key: 'displayName',
|
||||||
|
width: '200px',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps('displayName'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Is enabled"),
|
||||||
|
dataIndex: 'isEnabled',
|
||||||
|
key: 'isEnabled',
|
||||||
|
width: '120px',
|
||||||
|
sorter: true,
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text}/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Action"),
|
||||||
|
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(`/models/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
|
<Popconfirm
|
||||||
|
title={`Sure to delete model: ${record.name} ?`}
|
||||||
|
onConfirm={() => this.deleteModel(index)}
|
||||||
|
>
|
||||||
|
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const paginationProps = {
|
||||||
|
total: this.state.pagination.total,
|
||||||
|
showQuickJumper: true,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Table scroll={{x: 'max-content'}} columns={columns} dataSource={models} rowKey="name" size="middle" bordered
|
||||||
|
pagination={paginationProps}
|
||||||
|
title={() => (
|
||||||
|
<div>
|
||||||
|
{i18next.t("general:Models")}
|
||||||
|
<Button type="primary" size="small"
|
||||||
|
onClick={this.addModel.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
loading={this.state.loading}
|
||||||
|
onChange={this.handleTableChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch = (params = {}) => {
|
||||||
|
let field = params.searchedColumn, value = params.searchText;
|
||||||
|
let sortField = params.sortField, sortOrder = params.sortOrder;
|
||||||
|
if (params.type !== undefined && params.type !== null) {
|
||||||
|
field = "type";
|
||||||
|
value = params.type;
|
||||||
|
}
|
||||||
|
this.setState({loading: true});
|
||||||
|
ModelBackend.getModels("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
data: res.data,
|
||||||
|
pagination: {
|
||||||
|
...params.pagination,
|
||||||
|
total: res.data2,
|
||||||
|
},
|
||||||
|
searchText: params.searchText,
|
||||||
|
searchedColumn: params.searchedColumn,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ModelListPage;
|
@@ -20,6 +20,7 @@ import * as Setting from "./Setting";
|
|||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import {LinkOutlined} from "@ant-design/icons";
|
import {LinkOutlined} from "@ant-design/icons";
|
||||||
import LdapTable from "./LdapTable";
|
import LdapTable from "./LdapTable";
|
||||||
|
import AccountTable from "./AccountTable";
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
@@ -117,7 +118,7 @@ class OrganizationEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
|
||||||
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={23} >
|
<Col span={23} >
|
||||||
@@ -127,7 +128,7 @@ class OrganizationEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
|
||||||
{i18next.t("general:Preview")}:
|
{i18next.t("general:Preview")}:
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={23} >
|
<Col span={23} >
|
||||||
@@ -155,7 +156,7 @@ class OrganizationEditPage extends React.Component {
|
|||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Select virtual={false} style={{width: '100%'}} value={this.state.organization.passwordType} onChange={(value => {this.updateOrganizationField('passwordType', value);})}>
|
<Select virtual={false} style={{width: '100%'}} value={this.state.organization.passwordType} onChange={(value => {this.updateOrganizationField('passwordType', value);})}>
|
||||||
{
|
{
|
||||||
['plain', 'salt', 'md5-salt', 'bcrypt', 'pbkdf2-salt']
|
['plain', 'salt', 'md5-salt', 'bcrypt', 'pbkdf2-salt', 'argon2id']
|
||||||
.map((item, index) => <Option key={index} value={item}>{item}</Option>)
|
.map((item, index) => <Option key={index} value={item}>{item}</Option>)
|
||||||
}
|
}
|
||||||
</Select>
|
</Select>
|
||||||
@@ -187,7 +188,7 @@ class OrganizationEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
|
||||||
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={23} >
|
<Col span={23} >
|
||||||
@@ -197,7 +198,7 @@ class OrganizationEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
|
||||||
{i18next.t("general:Preview")}:
|
{i18next.t("general:Preview")}:
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={23} >
|
<Col span={23} >
|
||||||
@@ -240,6 +241,28 @@ class OrganizationEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("organization:Is profile public"), i18next.t("organization:Is profile public - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={1} >
|
||||||
|
<Switch checked={this.state.organization.isProfilePublic} onChange={checked => {
|
||||||
|
this.updateOrganizationField('isProfilePublic', checked);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("organization:Account items"), i18next.t("organization:Account items - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<AccountTable
|
||||||
|
title={i18next.t("organization:Account items")}
|
||||||
|
table={this.state.organization.accountItems}
|
||||||
|
onUpdateTable={(value) => { this.updateOrganizationField('accountItems', value)}}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: '20px'}}>
|
<Row style={{marginTop: '20px'}}>
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:LDAPs"), i18next.t("general:LDAPs - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:LDAPs"), i18next.t("general:LDAPs - Tooltip"))} :
|
||||||
|
@@ -39,6 +39,32 @@ class OrganizationListPage extends BaseListPage {
|
|||||||
tags: [],
|
tags: [],
|
||||||
masterPassword: "",
|
masterPassword: "",
|
||||||
enableSoftDeletion: false,
|
enableSoftDeletion: false,
|
||||||
|
isProfilePublic: true,
|
||||||
|
accountItems: [
|
||||||
|
{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"},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -13,11 +13,14 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Button, Card, Col, Input, Row} from 'antd';
|
import {Button, Card, Col, Descriptions, Input, Modal, Row, Select} from 'antd';
|
||||||
|
import {InfoCircleTwoTone} from "@ant-design/icons";
|
||||||
import * as PaymentBackend from "./backend/PaymentBackend";
|
import * as PaymentBackend from "./backend/PaymentBackend";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
class PaymentEditPage extends React.Component {
|
class PaymentEditPage extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -26,6 +29,8 @@ class PaymentEditPage extends React.Component {
|
|||||||
organizationName: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
|
organizationName: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
|
||||||
paymentName: props.match.params.paymentName,
|
paymentName: props.match.params.paymentName,
|
||||||
payment: null,
|
payment: null,
|
||||||
|
isModalVisible: false,
|
||||||
|
isInvoiceLoading: false,
|
||||||
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -40,6 +45,8 @@ class PaymentEditPage extends React.Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
payment: payment,
|
payment: payment,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Setting.scrollToDiv("invoice-area");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,6 +67,82 @@ class PaymentEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
issueInvoice() {
|
||||||
|
this.setState({
|
||||||
|
isModalVisible: false,
|
||||||
|
isInvoiceLoading: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
PaymentBackend.invoicePayment(this.state.payment.owner, this.state.paymentName)
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
isInvoiceLoading: false,
|
||||||
|
});
|
||||||
|
if (res.msg === "") {
|
||||||
|
Setting.showMessage("success", `Successfully invoiced`);
|
||||||
|
Setting.openLinkSafe(res.data);
|
||||||
|
this.getPayment();
|
||||||
|
} else {
|
||||||
|
Setting.showMessage(res.msg.includes("成功") ? "info" : "error", res.msg);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.setState({
|
||||||
|
isInvoiceLoading: false,
|
||||||
|
});
|
||||||
|
Setting.showMessage("error", `Failed to connect to server: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadInvoice() {
|
||||||
|
Setting.openLinkSafe(this.state.payment.invoiceUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderModal() {
|
||||||
|
const ths = this;
|
||||||
|
const handleIssueInvoice = () => {
|
||||||
|
ths.issueInvoice();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
this.setState({
|
||||||
|
isModalVisible: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal title={
|
||||||
|
<div>
|
||||||
|
<InfoCircleTwoTone twoToneColor="rgb(45,120,213)" />
|
||||||
|
{" " + i18next.t("payment:Confirm your invoice information")}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
visible={this.state.isModalVisible}
|
||||||
|
onOk={handleIssueInvoice}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
okText={i18next.t("payment:Issue Invoice")}
|
||||||
|
cancelText={i18next.t("general:Cancel")}>
|
||||||
|
<p>
|
||||||
|
{
|
||||||
|
i18next.t("payment:Please carefully check your invoice information. Once the invoice is issued, it cannot be withdrawn or modified.")
|
||||||
|
}
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
<Descriptions size={"small"} bordered>
|
||||||
|
<Descriptions.Item label={i18next.t("payment:Person name")} span={3}>{this.state.payment?.personName}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label={i18next.t("payment:Person ID card")} span={3}>{this.state.payment?.personIdCard}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label={i18next.t("payment:Person Email")} span={3}>{this.state.payment?.personEmail}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label={i18next.t("payment:Person phone")} span={3}>{this.state.payment?.personPhone}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label={i18next.t("payment:Invoice type")} span={3}>{this.state.payment?.invoiceType === "Individual" ? i18next.t("payment:Individual") : i18next.t("payment:Organization")}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label={i18next.t("payment:Invoice title")} span={3}>{this.state.payment?.invoiceTitle}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label={i18next.t("payment:Invoice tax ID")} span={3}>{this.state.payment?.invoiceTaxId}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label={i18next.t("payment:Invoice remark")} span={3}>{this.state.payment?.invoiceRemark}</Descriptions.Item>
|
||||||
|
</Descriptions>
|
||||||
|
</p>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
renderPayment() {
|
renderPayment() {
|
||||||
return (
|
return (
|
||||||
<Card size="small" title={
|
<Card size="small" title={
|
||||||
@@ -75,7 +158,7 @@ class PaymentEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.payment.organization} onChange={e => {
|
<Input disabled={true} value={this.state.payment.organization} onChange={e => {
|
||||||
// this.updatePaymentField('organization', e.target.value);
|
// this.updatePaymentField('organization', e.target.value);
|
||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
@@ -85,7 +168,7 @@ class PaymentEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.payment.name} onChange={e => {
|
<Input disabled={true} value={this.state.payment.name} onChange={e => {
|
||||||
// this.updatePaymentField('name', e.target.value);
|
// this.updatePaymentField('name', e.target.value);
|
||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
@@ -95,7 +178,7 @@ class PaymentEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.payment.displayName} onChange={e => {
|
<Input disabled={true} value={this.state.payment.displayName} onChange={e => {
|
||||||
this.updatePaymentField('displayName', e.target.value);
|
this.updatePaymentField('displayName', e.target.value);
|
||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
@@ -105,7 +188,7 @@ class PaymentEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("general:Provider"), i18next.t("general:Provider - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Provider"), i18next.t("general:Provider - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.payment.provider} onChange={e => {
|
<Input disabled={true} value={this.state.payment.provider} onChange={e => {
|
||||||
// this.updatePaymentField('provider', e.target.value);
|
// this.updatePaymentField('provider', e.target.value);
|
||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
@@ -115,7 +198,7 @@ class PaymentEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("payment:Type"), i18next.t("payment:Type - Tooltip"))} :
|
{Setting.getLabel(i18next.t("payment:Type"), i18next.t("payment:Type - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.payment.type} onChange={e => {
|
<Input disabled={true} value={this.state.payment.type} onChange={e => {
|
||||||
// this.updatePaymentField('type', e.target.value);
|
// this.updatePaymentField('type', e.target.value);
|
||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
@@ -125,7 +208,7 @@ class PaymentEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("payment:Product"), i18next.t("payment:Product - Tooltip"))} :
|
{Setting.getLabel(i18next.t("payment:Product"), i18next.t("payment:Product - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.payment.productName} onChange={e => {
|
<Input disabled={true} value={this.state.payment.productName} onChange={e => {
|
||||||
// this.updatePaymentField('productName', e.target.value);
|
// this.updatePaymentField('productName', e.target.value);
|
||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
@@ -135,7 +218,7 @@ class PaymentEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("payment:Price"), i18next.t("payment:Price - Tooltip"))} :
|
{Setting.getLabel(i18next.t("payment:Price"), i18next.t("payment:Price - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.payment.price} onChange={e => {
|
<Input disabled={true} value={this.state.payment.price} onChange={e => {
|
||||||
// this.updatePaymentField('amount', e.target.value);
|
// this.updatePaymentField('amount', e.target.value);
|
||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
@@ -145,7 +228,7 @@ class PaymentEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("payment:Currency"), i18next.t("payment:Currency - Tooltip"))} :
|
{Setting.getLabel(i18next.t("payment:Currency"), i18next.t("payment:Currency - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.payment.currency} onChange={e => {
|
<Input disabled={true} value={this.state.payment.currency} onChange={e => {
|
||||||
// this.updatePaymentField('currency', e.target.value);
|
// this.updatePaymentField('currency', e.target.value);
|
||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
@@ -155,7 +238,7 @@ class PaymentEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("payment:State"), i18next.t("payment:State - Tooltip"))} :
|
{Setting.getLabel(i18next.t("payment:State"), i18next.t("payment:State - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.payment.state} onChange={e => {
|
<Input disabled={true} value={this.state.payment.state} onChange={e => {
|
||||||
// this.updatePaymentField('state', e.target.value);
|
// this.updatePaymentField('state', e.target.value);
|
||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
@@ -165,18 +248,200 @@ class PaymentEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("payment:Message"), i18next.t("payment:Message - Tooltip"))} :
|
{Setting.getLabel(i18next.t("payment:Message"), i18next.t("payment:Message - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.payment.message} onChange={e => {
|
<Input disabled={true} value={this.state.payment.message} onChange={e => {
|
||||||
// this.updatePaymentField('message', e.target.value);
|
// this.updatePaymentField('message', e.target.value);
|
||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("payment:Person name"), i18next.t("payment:Person name - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input disabled={this.state.payment.invoiceUrl !== ""} value={this.state.payment.personName} onChange={e => {
|
||||||
|
this.updatePaymentField('personName', e.target.value);
|
||||||
|
if (this.state.payment.invoiceType === "Individual") {
|
||||||
|
this.updatePaymentField('invoiceTitle', e.target.value);
|
||||||
|
this.updatePaymentField('invoiceTaxId', "");
|
||||||
|
}
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("payment:Person ID card"), i18next.t("payment:Person ID card - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input disabled={this.state.payment.invoiceUrl !== ""} value={this.state.payment.personIdCard} onChange={e => {
|
||||||
|
this.updatePaymentField('personIdCard', e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("payment:Person Email"), i18next.t("payment:Person Email - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input disabled={this.state.payment.invoiceUrl !== ""} value={this.state.payment.personEmail} onChange={e => {
|
||||||
|
this.updatePaymentField('personEmail', e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("payment:Person phone"), i18next.t("payment:Person phone - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input disabled={this.state.payment.invoiceUrl !== ""} value={this.state.payment.personPhone} onChange={e => {
|
||||||
|
this.updatePaymentField('personPhone', e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("payment:Invoice type"), i18next.t("payment:Invoice type - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select disabled={this.state.payment.invoiceUrl !== ""} virtual={false} style={{width: '100%'}} value={this.state.payment.invoiceType} onChange={(value => {
|
||||||
|
this.updatePaymentField('invoiceType', value);
|
||||||
|
if (value === "Individual") {
|
||||||
|
this.updatePaymentField('invoiceTitle', this.state.payment.personName);
|
||||||
|
this.updatePaymentField('invoiceTaxId', "");
|
||||||
|
}
|
||||||
|
})}>
|
||||||
|
{
|
||||||
|
[
|
||||||
|
{id: 'Individual', name: i18next.t("payment:Individual")},
|
||||||
|
{id: 'Organization', name: i18next.t("payment:Organization")},
|
||||||
|
].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}>
|
||||||
|
{Setting.getLabel(i18next.t("payment:Invoice title"), i18next.t("payment:Invoice title - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input disabled={this.state.payment.invoiceUrl !== "" || this.state.payment.invoiceType === "Individual"} value={this.state.payment.invoiceTitle} onChange={e => {
|
||||||
|
this.updatePaymentField('invoiceTitle', e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("payment:Invoice tax ID"), i18next.t("payment:Invoice tax ID - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input disabled={this.state.payment.invoiceUrl !== "" || this.state.payment.invoiceType === "Individual"} value={this.state.payment.invoiceTaxId} onChange={e => {
|
||||||
|
this.updatePaymentField('invoiceTaxId', e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("payment:Invoice remark"), i18next.t("payment:Invoice remark - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input disabled={this.state.payment.invoiceUrl !== ""} value={this.state.payment.invoiceRemark} onChange={e => {
|
||||||
|
this.updatePaymentField('invoiceRemark', e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("payment:Invoice URL"), i18next.t("payment:Invoice URL - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input disabled={true} value={this.state.payment.invoiceUrl} onChange={e => {
|
||||||
|
this.updatePaymentField('invoiceUrl', e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row id={"invoice-area"} style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("payment:Invoice actions"), i18next.t("payment:Invoice actions - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
{
|
||||||
|
this.state.payment.invoiceUrl === "" ? (
|
||||||
|
<Button type={"primary"} loading={this.state.isInvoiceLoading} onClick={() => {
|
||||||
|
const errorText = this.checkError();
|
||||||
|
if (errorText !== "") {
|
||||||
|
Setting.showMessage("error", errorText);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
isModalVisible: true,
|
||||||
|
});
|
||||||
|
}}>{i18next.t("payment:Issue Invoice")}</Button>
|
||||||
|
) : (
|
||||||
|
<Button type={"primary"} onClick={() => this.downloadInvoice(false)}>{i18next.t("payment:Download Invoice")}</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<Button style={{marginLeft: "20px"}} onClick={() => Setting.goToLink(this.state.payment.returnUrl)}>{i18next.t("payment:Return to Website")}</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkError() {
|
||||||
|
if (this.state.payment.state !== "Paid") {
|
||||||
|
return i18next.t("payment:Please pay the order first!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Setting.isValidPersonName(this.state.payment.personName)) {
|
||||||
|
return i18next.t("signup:Please input your real name!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Setting.isValidIdCard(this.state.payment.personIdCard)) {
|
||||||
|
return i18next.t("signup:Please input the correct ID card number!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Setting.isValidEmail(this.state.payment.personEmail)) {
|
||||||
|
return i18next.t("signup:The input is not valid Email!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Setting.isValidPhone(this.state.payment.personPhone)) {
|
||||||
|
return i18next.t("signup:The input is not valid Phone!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Setting.isValidPhone(this.state.payment.personPhone)) {
|
||||||
|
return i18next.t("signup:The input is not valid Phone!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.payment.invoiceType === "Individual") {
|
||||||
|
if (this.state.payment.invoiceTitle !== this.state.payment.personName) {
|
||||||
|
return i18next.t("signup:The input is not invoice title!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.payment.invoiceTaxId !== "") {
|
||||||
|
return i18next.t("signup:The input is not invoice Tax ID!");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!Setting.isValidInvoiceTitle(this.state.payment.invoiceTitle)) {
|
||||||
|
return i18next.t("signup:The input is not invoice title!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Setting.isValidTaxId(this.state.payment.invoiceTaxId)) {
|
||||||
|
return i18next.t("signup:The input is not invoice Tax ID!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
submitPaymentEdit(willExist) {
|
submitPaymentEdit(willExist) {
|
||||||
|
const errorText = this.checkError();
|
||||||
|
if (errorText !== "") {
|
||||||
|
Setting.showMessage("error", errorText);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let payment = Setting.deepCopy(this.state.payment);
|
let payment = Setting.deepCopy(this.state.payment);
|
||||||
PaymentBackend.updatePayment(this.state.organizationName, this.state.paymentName, payment)
|
PaymentBackend.updatePayment(this.state.payment.owner, this.state.paymentName, payment)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.msg === "") {
|
if (res.msg === "") {
|
||||||
Setting.showMessage("success", `Successfully saved`);
|
Setting.showMessage("success", `Successfully saved`);
|
||||||
@@ -215,6 +480,9 @@ class PaymentEditPage extends React.Component {
|
|||||||
{
|
{
|
||||||
this.state.payment !== null ? this.renderPayment() : null
|
this.state.payment !== null ? this.renderPayment() : null
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
this.renderModal()
|
||||||
|
}
|
||||||
<div style={{marginTop: '20px', marginLeft: '40px'}}>
|
<div style={{marginTop: '20px', marginLeft: '40px'}}>
|
||||||
<Button size="large" onClick={() => this.submitPaymentEdit(false)}>{i18next.t("general:Save")}</Button>
|
<Button size="large" onClick={() => this.submitPaymentEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||||
<Button style={{marginLeft: '20px'}} type="primary" size="large" onClick={() => this.submitPaymentEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
<Button style={{marginLeft: '20px'}} type="primary" size="large" onClick={() => this.submitPaymentEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||||
|
@@ -20,6 +20,7 @@ import * as UserBackend from "./backend/UserBackend";
|
|||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import * as RoleBackend from "./backend/RoleBackend";
|
import * as RoleBackend from "./backend/RoleBackend";
|
||||||
|
import * as ModelBackend from "./backend/ModelBackend";
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
@@ -34,6 +35,7 @@ class PermissionEditPage extends React.Component {
|
|||||||
organizations: [],
|
organizations: [],
|
||||||
users: [],
|
users: [],
|
||||||
roles: [],
|
roles: [],
|
||||||
|
models: [],
|
||||||
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -52,6 +54,7 @@ class PermissionEditPage extends React.Component {
|
|||||||
|
|
||||||
this.getUsers(permission.owner);
|
this.getUsers(permission.owner);
|
||||||
this.getRoles(permission.owner);
|
this.getRoles(permission.owner);
|
||||||
|
this.getModels(permission.owner);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,6 +85,15 @@ class PermissionEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getModels(organizationName) {
|
||||||
|
ModelBackend.getModels(organizationName)
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
models: res,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
parsePermissionField(key, value) {
|
parsePermissionField(key, value) {
|
||||||
if ([""].includes(key)) {
|
if ([""].includes(key)) {
|
||||||
value = Setting.myParseInt(value);
|
value = Setting.myParseInt(value);
|
||||||
@@ -146,6 +158,20 @@ class PermissionEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Model"), i18next.t("general:Model - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: '100%'}} value={this.state.permission.model} onChange={(model => {
|
||||||
|
this.updatePermissionField('model', model);
|
||||||
|
})}>
|
||||||
|
{
|
||||||
|
this.state.models.map((model, index) => <Option key={index} value={model.name}>{model.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("role:Sub users"), i18next.t("role:Sub users - Tooltip"))} :
|
{Setting.getLabel(i18next.t("role:Sub users"), i18next.t("role:Sub users - Tooltip"))} :
|
||||||
|
@@ -17,7 +17,6 @@ import {Button, Descriptions, Spin} from "antd";
|
|||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import * as ProductBackend from "./backend/ProductBackend";
|
import * as ProductBackend from "./backend/ProductBackend";
|
||||||
import * as ProviderBackend from "./backend/ProviderBackend";
|
import * as ProviderBackend from "./backend/ProviderBackend";
|
||||||
import * as Provider from "./auth/Provider";
|
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
|
|
||||||
class ProductBuyPage extends React.Component {
|
class ProductBuyPage extends React.Component {
|
||||||
@@ -148,7 +147,7 @@ class ProductBuyPage extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Button style={{height: "50px", borderWidth: "2px"}} shape="round" icon={
|
<Button style={{height: "50px", borderWidth: "2px"}} shape="round" icon={
|
||||||
<img style={{marginRight: "10px"}} width={36} height={36} src={Provider.getProviderLogo(provider)} alt={provider.displayName} />
|
<img style={{marginRight: "10px"}} width={36} height={36} src={Setting.getProviderLogoURL(provider)} alt={provider.displayName} />
|
||||||
} size={"large"} >
|
} size={"large"} >
|
||||||
{
|
{
|
||||||
text
|
text
|
||||||
@@ -207,7 +206,7 @@ class ProductBuyPage extends React.Component {
|
|||||||
<Descriptions.Item label={i18next.t("product:Tag")}><span style={{fontSize: 16}}>{product?.tag}</span></Descriptions.Item>
|
<Descriptions.Item label={i18next.t("product:Tag")}><span style={{fontSize: 16}}>{product?.tag}</span></Descriptions.Item>
|
||||||
<Descriptions.Item label={i18next.t("product:SKU")}><span style={{fontSize: 16}}>{product?.name}</span></Descriptions.Item>
|
<Descriptions.Item label={i18next.t("product:SKU")}><span style={{fontSize: 16}}>{product?.name}</span></Descriptions.Item>
|
||||||
<Descriptions.Item label={i18next.t("product:Image")} span={3}>
|
<Descriptions.Item label={i18next.t("product:Image")} span={3}>
|
||||||
<img src={product?.image} alt={product?.image} height={90} style={{marginBottom: '20px'}}/>
|
<img src={product?.image} alt={product?.name} height={90} style={{marginBottom: '20px'}}/>
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label={i18next.t("product:Price")}>
|
<Descriptions.Item label={i18next.t("product:Price")}>
|
||||||
<span style={{fontSize: 28, color: "red", fontWeight: "bold"}}>
|
<span style={{fontSize: 28, color: "red", fontWeight: "bold"}}>
|
||||||
|
@@ -110,7 +110,7 @@ class ProductEditPage extends React.Component {
|
|||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("product:Image"), i18next.t("product:Image - Tooltip"))} :
|
{Setting.getLabel(i18next.t("product:Image"), i18next.t("product:Image - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} style={(Setting.isMobile()) ? {maxWidth:'100%'} :{}}>
|
<Col span={22} style={(Setting.isMobile()) ? {maxWidth: '100%'} :{}}>
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
|
||||||
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
|
||||||
|
@@ -20,6 +20,7 @@ import * as Setting from "./Setting";
|
|||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { authConfig } from "./auth/Auth";
|
import { authConfig } from "./auth/Auth";
|
||||||
import copy from 'copy-to-clipboard';
|
import copy from 'copy-to-clipboard';
|
||||||
|
import { CaptchaPreview } from "./common/CaptchaPreview";
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
const { TextArea } = Input;
|
const { TextArea } = Input;
|
||||||
@@ -70,10 +71,15 @@ class ProviderEditPage extends React.Component {
|
|||||||
case "Email":
|
case "Email":
|
||||||
return Setting.getLabel(i18next.t("signup:Username"), i18next.t("signup:Username - Tooltip"));
|
return Setting.getLabel(i18next.t("signup:Username"), i18next.t("signup:Username - Tooltip"));
|
||||||
case "SMS":
|
case "SMS":
|
||||||
if (this.state.provider.type === "Volc Engine SMS")
|
if (this.state.provider.type === "Volc Engine SMS") {
|
||||||
return Setting.getLabel(i18next.t("provider:Access key"), i18next.t("provider:Access key - Tooltip"));
|
return Setting.getLabel(i18next.t("provider:Access key"), i18next.t("provider:Access key - Tooltip"));
|
||||||
if (this.state.provider.type === "Huawei Cloud SMS")
|
} else if (this.state.provider.type === "Huawei Cloud SMS") {
|
||||||
return Setting.getLabel(i18next.t("provider:App key"), i18next.t("provider:App key - Tooltip"));
|
return Setting.getLabel(i18next.t("provider:App key"), i18next.t("provider:App key - Tooltip"));
|
||||||
|
} else {
|
||||||
|
return Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"));
|
||||||
|
}
|
||||||
|
case "Captcha":
|
||||||
|
return Setting.getLabel(i18next.t("provider:Site key"), i18next.t("provider:Site key - Tooltip"));
|
||||||
default:
|
default:
|
||||||
return Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"));
|
return Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"));
|
||||||
}
|
}
|
||||||
@@ -84,10 +90,15 @@ class ProviderEditPage extends React.Component {
|
|||||||
case "Email":
|
case "Email":
|
||||||
return Setting.getLabel(i18next.t("login:Password"), i18next.t("login:Password - Tooltip"));
|
return Setting.getLabel(i18next.t("login:Password"), i18next.t("login:Password - Tooltip"));
|
||||||
case "SMS":
|
case "SMS":
|
||||||
if (this.state.provider.type === "Volc Engine SMS")
|
if (this.state.provider.type === "Volc Engine SMS") {
|
||||||
return Setting.getLabel(i18next.t("provider:Secret access key"), i18next.t("provider:SecretAccessKey - Tooltip"));
|
return Setting.getLabel(i18next.t("provider:Secret access key"), i18next.t("provider:SecretAccessKey - Tooltip"));
|
||||||
if (this.state.provider.type === "Huawei Cloud SMS")
|
} else if (this.state.provider.type === "Huawei Cloud SMS") {
|
||||||
return Setting.getLabel(i18next.t("provider:App secret"), i18next.t("provider:AppSecret - Tooltip"));
|
return Setting.getLabel(i18next.t("provider:App secret"), i18next.t("provider:AppSecret - Tooltip"));
|
||||||
|
} else {
|
||||||
|
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
|
||||||
|
}
|
||||||
|
case "Captcha":
|
||||||
|
return Setting.getLabel(i18next.t("provider:Secret key"), i18next.t("provider:Secret key - Tooltip"));
|
||||||
default:
|
default:
|
||||||
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
|
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
|
||||||
}
|
}
|
||||||
@@ -187,6 +198,8 @@ class ProviderEditPage extends React.Component {
|
|||||||
this.updateProviderField('domain', Setting.getFullServerUrl());
|
this.updateProviderField('domain', Setting.getFullServerUrl());
|
||||||
} else if (value === "SAML") {
|
} else if (value === "SAML") {
|
||||||
this.updateProviderField('type', 'Aliyun IDaaS');
|
this.updateProviderField('type', 'Aliyun IDaaS');
|
||||||
|
} else if (value === "Captcha") {
|
||||||
|
this.updateProviderField('type', 'Default');
|
||||||
}
|
}
|
||||||
})}>
|
})}>
|
||||||
{
|
{
|
||||||
@@ -197,6 +210,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
{id: 'Storage', name: 'Storage'},
|
{id: 'Storage', name: 'Storage'},
|
||||||
{id: 'SAML', name: 'SAML'},
|
{id: 'SAML', name: 'SAML'},
|
||||||
{id: 'Payment', name: 'Payment'},
|
{id: 'Payment', name: 'Payment'},
|
||||||
|
{id: 'Captcha', name: 'Captcha'},
|
||||||
].map((providerCategory, index) => <Option key={index} value={providerCategory.id}>{providerCategory.name}</Option>)
|
].map((providerCategory, index) => <Option key={index} value={providerCategory.id}>{providerCategory.name}</Option>)
|
||||||
}
|
}
|
||||||
</Select>
|
</Select>
|
||||||
@@ -212,6 +226,12 @@ class ProviderEditPage extends React.Component {
|
|||||||
if (value === "Local File System") {
|
if (value === "Local File System") {
|
||||||
this.updateProviderField('domain', Setting.getFullServerUrl());
|
this.updateProviderField('domain', Setting.getFullServerUrl());
|
||||||
}
|
}
|
||||||
|
if (value === "Custom") {
|
||||||
|
this.updateProviderField('customAuthUrl', 'https://door.casdoor.com/login/oauth/authorize');
|
||||||
|
this.updateProviderField('customScope', 'openid profile email');
|
||||||
|
this.updateProviderField('customTokenUrl', 'https://door.casdoor.com/api/login/oauth/access_token');
|
||||||
|
this.updateProviderField('customUserInfoUrl', 'https://door.casdoor.com/api/userinfo');
|
||||||
|
}
|
||||||
})}>
|
})}>
|
||||||
{
|
{
|
||||||
Setting.getProviderTypeOptions(this.state.provider.category).map((providerType, index) => <Option key={index} value={providerType.id}>{providerType.name}</Option>)
|
Setting.getProviderTypeOptions(this.state.provider.category).map((providerType, index) => <Option key={index} value={providerType.id}>{providerType.name}</Option>)
|
||||||
@@ -256,26 +276,104 @@ class ProviderEditPage extends React.Component {
|
|||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
<Row style={{marginTop: '20px'}} >
|
{
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
this.state.provider.type !== "Custom" ? null : (
|
||||||
{this.getClientIdLabel()}
|
<React.Fragment>
|
||||||
</Col>
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col span={22} >
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
<Input value={this.state.provider.clientId} onChange={e => {
|
{Setting.getLabel(i18next.t("provider:Auth URL"), i18next.t("provider:Auth URL - Tooltip"))}
|
||||||
this.updateProviderField('clientId', e.target.value);
|
</Col>
|
||||||
}} />
|
<Col span={22} >
|
||||||
</Col>
|
<Input value={this.state.provider.customAuthUrl} onChange={e => {
|
||||||
</Row>
|
this.updateProviderField('customAuthUrl', e.target.value);
|
||||||
<Row style={{marginTop: '20px'}} >
|
}} />
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
</Col>
|
||||||
{this.getClientSecretLabel()}
|
</Row>
|
||||||
</Col>
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col span={22} >
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
<Input value={this.state.provider.clientSecret} onChange={e => {
|
{Setting.getLabel(i18next.t("provider:Scope"), i18next.t("provider:Scope - Tooltip"))}
|
||||||
this.updateProviderField('clientSecret', e.target.value);
|
</Col>
|
||||||
}} />
|
<Col span={22} >
|
||||||
</Col>
|
<Input value={this.state.provider.customScope} onChange={e => {
|
||||||
</Row>
|
this.updateProviderField('customScope', e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("provider:Token URL"), i18next.t("provider:Token URL - Tooltip"))}
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.provider.customTokenUrl} onChange={e => {
|
||||||
|
this.updateProviderField('customTokenUrl', e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("provider:UserInfo URL"), i18next.t("provider:UserInfo URL - Tooltip"))}
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.provider.customUserInfoUrl} onChange={e => {
|
||||||
|
this.updateProviderField('customUserInfoUrl', e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel( i18next.t("general:Favicon"), i18next.t("general:Favicon - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<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.provider.customLogo} onChange={e => {
|
||||||
|
this.updateProviderField('customLogo', e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<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.provider.customLogo}>
|
||||||
|
<img src={this.state.provider.customLogo} alt={this.state.provider.customLogo} height={90} style={{marginBottom: '20px'}}/>
|
||||||
|
</a>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
this.state.provider.type !== "Default" &&
|
||||||
|
<>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{this.getClientIdLabel()}
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.provider.clientId} onChange={e => {
|
||||||
|
this.updateProviderField('clientId', e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{this.getClientSecretLabel()}
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.provider.clientSecret} onChange={e => {
|
||||||
|
this.updateProviderField('clientSecret', e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</>
|
||||||
|
}
|
||||||
{
|
{
|
||||||
this.state.provider.type !== "WeChat" ? null : (
|
this.state.provider.type !== "WeChat" ? null : (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
@@ -303,7 +401,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
this.state.provider.type !== "Adfs" && this.state.provider.type !== "Casdoor" ? null : (
|
this.state.provider.type !== "Adfs" && this.state.provider.type !== "Casdoor" && this.state.provider.type !== "Okta" ? null : (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={2}>
|
<Col style={{marginTop: '5px'}} span={2}>
|
||||||
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
|
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
|
||||||
@@ -542,16 +640,39 @@ class ProviderEditPage extends React.Component {
|
|||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
{this.getAppIdRow()}
|
{this.getAppIdRow()}
|
||||||
<Row style={{marginTop: '20px'}} >
|
{
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
this.state.provider.type !== "Default" &&
|
||||||
{Setting.getLabel(i18next.t("provider:Provider URL"), i18next.t("provider:Provider URL - Tooltip"))} :
|
<Row style={{marginTop: '20px'}} >
|
||||||
</Col>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
<Col span={22} >
|
{Setting.getLabel(i18next.t("provider:Provider URL"), i18next.t("provider:Provider URL - Tooltip"))} :
|
||||||
<Input prefix={<LinkOutlined/>} value={this.state.provider.providerUrl} onChange={e => {
|
</Col>
|
||||||
this.updateProviderField('providerUrl', e.target.value);
|
<Col span={22} >
|
||||||
}} />
|
<Input prefix={<LinkOutlined/>} value={this.state.provider.providerUrl} onChange={e => {
|
||||||
</Col>
|
this.updateProviderField('providerUrl', e.target.value);
|
||||||
</Row>
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
this.state.provider.category === "Captcha" &&
|
||||||
|
<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>
|
||||||
|
<Col span={22} >
|
||||||
|
<CaptchaPreview
|
||||||
|
provider={this.state.provider}
|
||||||
|
providerName={this.state.providerName}
|
||||||
|
clientSecret={this.state.provider.clientSecret}
|
||||||
|
captchaType={this.state.provider.type}
|
||||||
|
owner={this.state.provider.owner}
|
||||||
|
clientId={this.state.provider.clientId}
|
||||||
|
name={this.state.provider.name}
|
||||||
|
providerUrl={this.state.provider.providerUrl}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
}
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -134,6 +134,7 @@ class ProviderListPage extends BaseListPage {
|
|||||||
{text: 'SMS', value: 'SMS', children: Setting.getProviderTypeOptions('SMS').map((o) => {return {text:o.id, value:o.name}})},
|
{text: 'SMS', value: 'SMS', children: Setting.getProviderTypeOptions('SMS').map((o) => {return {text:o.id, value:o.name}})},
|
||||||
{text: 'Storage', value: 'Storage', children: Setting.getProviderTypeOptions('Storage').map((o) => {return {text:o.id, value:o.name}})},
|
{text: 'Storage', value: 'Storage', children: Setting.getProviderTypeOptions('Storage').map((o) => {return {text:o.id, value:o.name}})},
|
||||||
{text: 'SAML', value: 'SAML', children: Setting.getProviderTypeOptions('SAML').map((o) => {return {text:o.id, value:o.name}})},
|
{text: 'SAML', value: 'SAML', children: Setting.getProviderTypeOptions('SAML').map((o) => {return {text:o.id, value:o.name}})},
|
||||||
|
{text: 'Captcha', value: 'Captcha', children: Setting.getProviderTypeOptions('Captcha').map((o) => {return {text:o.id, value:o.name}})},
|
||||||
],
|
],
|
||||||
sorter: true,
|
sorter: true,
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
|
@@ -74,7 +74,7 @@ class ResourceListPage extends BaseListPage {
|
|||||||
|
|
||||||
renderUpload() {
|
renderUpload() {
|
||||||
return (
|
return (
|
||||||
<Upload maxCount={1} accept="image/*,video/*,audio/*,.pdf,.doc,.docx" showUploadList={false}
|
<Upload maxCount={1} accept="image/*,video/*,audio/*,.pdf,.doc,.docx,.csv,.xls,.xlsx" showUploadList={false}
|
||||||
beforeUpload={file => {return false}} onChange={info => {this.handleUpload(info)}}>
|
beforeUpload={file => {return false}} onChange={info => {this.handleUpload(info)}}>
|
||||||
<Button icon={<UploadOutlined />} loading={this.state.uploading} type="primary" size="small">
|
<Button icon={<UploadOutlined />} loading={this.state.uploading} type="primary" size="small">
|
||||||
{i18next.t("resource:Upload a file...")}
|
{i18next.t("resource:Upload a file...")}
|
||||||
@@ -206,7 +206,7 @@ class ResourceListPage extends BaseListPage {
|
|||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
if (record.fileType === "image") {
|
if (record.fileType === "image") {
|
||||||
return (
|
return (
|
||||||
<a target="_blank" href={record.url}>
|
<a target="_blank" rel="noreferrer" href={record.url}>
|
||||||
<img src={record.url} alt={record.name} width={100} />
|
<img src={record.url} alt={record.name} width={100} />
|
||||||
</a>
|
</a>
|
||||||
)
|
)
|
||||||
|
@@ -21,7 +21,6 @@ import i18next from "i18next";
|
|||||||
import copy from "copy-to-clipboard";
|
import copy from "copy-to-clipboard";
|
||||||
import {authConfig} from "./auth/Auth";
|
import {authConfig} from "./auth/Auth";
|
||||||
import {Helmet} from "react-helmet";
|
import {Helmet} from "react-helmet";
|
||||||
import moment from "moment";
|
|
||||||
import * as Conf from "./Conf";
|
import * as Conf from "./Conf";
|
||||||
|
|
||||||
export let ServerUrl = "";
|
export let ServerUrl = "";
|
||||||
@@ -32,6 +31,97 @@ export const StaticBaseUrl = "https://cdn.casbin.org";
|
|||||||
// https://catamphetamine.gitlab.io/country-flag-icons/3x2/index.html
|
// https://catamphetamine.gitlab.io/country-flag-icons/3x2/index.html
|
||||||
export const CountryRegionData = getCountryRegionData();
|
export const CountryRegionData = getCountryRegionData();
|
||||||
|
|
||||||
|
export const OtherProviderInfo = {
|
||||||
|
SMS: {
|
||||||
|
"Aliyun SMS": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_aliyun.png`,
|
||||||
|
url: "https://aliyun.com/product/sms",
|
||||||
|
},
|
||||||
|
"Tencent Cloud SMS": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_tencent_cloud.jpg`,
|
||||||
|
url: "https://cloud.tencent.com/product/sms",
|
||||||
|
},
|
||||||
|
"Volc Engine SMS": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_volc_engine.jpg`,
|
||||||
|
url: "https://www.volcengine.com/products/cloud-sms",
|
||||||
|
},
|
||||||
|
"Huawei Cloud SMS": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_huawei.png`,
|
||||||
|
url: "https://www.huaweicloud.com/product/msgsms.html",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Email: {
|
||||||
|
"Default": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_default.png`,
|
||||||
|
url: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Storage: {
|
||||||
|
"Local File System": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_file.png`,
|
||||||
|
url: "",
|
||||||
|
},
|
||||||
|
"AWS S3": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_aws.png`,
|
||||||
|
url: "https://aws.amazon.com/s3",
|
||||||
|
},
|
||||||
|
"Aliyun OSS": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_aliyun.png`,
|
||||||
|
url: "https://aliyun.com/product/oss",
|
||||||
|
},
|
||||||
|
"Tencent Cloud COS": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_tencent_cloud.jpg`,
|
||||||
|
url: "https://cloud.tencent.com/product/cos",
|
||||||
|
},
|
||||||
|
"Azure Blob": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_azure.jpg`,
|
||||||
|
url: "https://azure.microsoft.com/en-us/services/storage/blobs/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
SAML: {
|
||||||
|
"Aliyun IDaaS": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_aliyun.png`,
|
||||||
|
url: "https://aliyun.com/product/idaas"
|
||||||
|
},
|
||||||
|
"Keycloak": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_keycloak.png`,
|
||||||
|
url: "https://www.keycloak.org/"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Payment: {
|
||||||
|
"Alipay": {
|
||||||
|
logo: `${StaticBaseUrl}/img/payment_alipay.png`,
|
||||||
|
url: "https://www.alipay.com/"
|
||||||
|
},
|
||||||
|
"WeChat Pay": {
|
||||||
|
logo: `${StaticBaseUrl}/img/payment_wechat_pay.png`,
|
||||||
|
url: "https://pay.weixin.qq.com/"
|
||||||
|
},
|
||||||
|
"PayPal": {
|
||||||
|
logo: `${StaticBaseUrl}/img/payment_paypal.png`,
|
||||||
|
url: "https://www.paypal.com/"
|
||||||
|
},
|
||||||
|
"GC": {
|
||||||
|
logo: `${StaticBaseUrl}/img/payment_gc.png`,
|
||||||
|
url: "https://gc.org"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Captcha: {
|
||||||
|
"Default": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_default.png`,
|
||||||
|
url: "https://pkg.go.dev/github.com/dchest/captcha",
|
||||||
|
},
|
||||||
|
"reCAPTCHA": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_recaptcha.png`,
|
||||||
|
url: "https://www.google.com/recaptcha",
|
||||||
|
},
|
||||||
|
"hCaptcha": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_hcaptcha.png`,
|
||||||
|
url: "https://www.hcaptcha.com",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export function getCountryRegionData() {
|
export function getCountryRegionData() {
|
||||||
let language = i18next.language;
|
let language = i18next.language;
|
||||||
if (language === null || language === "null") {
|
if (language === null || language === "null") {
|
||||||
@@ -115,6 +205,17 @@ export function getSignupItem(application, itemName) {
|
|||||||
return signupItems[0];
|
return signupItems[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isValidPersonName(personName) {
|
||||||
|
// https://blog.css8.cn/post/14210975.html
|
||||||
|
const personNameRegex = /^[\u4e00-\u9fa5]{2,6}$/;
|
||||||
|
return personNameRegex.test(personName);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isValidIdCard(idCard) {
|
||||||
|
const idCardRegex = /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9X]$/;
|
||||||
|
return idCardRegex.test(idCard);
|
||||||
|
}
|
||||||
|
|
||||||
export function isValidEmail(email) {
|
export function isValidEmail(email) {
|
||||||
// https://github.com/yiminghe/async-validator/blob/057b0b047f88fac65457bae691d6cb7c6fe48ce1/src/rule/type.ts#L9
|
// https://github.com/yiminghe/async-validator/blob/057b0b047f88fac65457bae691d6cb7c6fe48ce1/src/rule/type.ts#L9
|
||||||
const emailRegex = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
const emailRegex = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||||
@@ -122,11 +223,36 @@ export function isValidEmail(email) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isValidPhone(phone) {
|
export function isValidPhone(phone) {
|
||||||
|
if (phone === "") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// https://learnku.com/articles/31543, `^s*$` filter empty email individually.
|
// https://learnku.com/articles/31543, `^s*$` filter empty email individually.
|
||||||
const phoneRegex = /^\s*$|^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$/;
|
const phoneRegex = /^\s*$|^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$/;
|
||||||
return phoneRegex.test(phone);
|
return phoneRegex.test(phone);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isValidInvoiceTitle(invoiceTitle) {
|
||||||
|
if (invoiceTitle === "") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://blog.css8.cn/post/14210975.html
|
||||||
|
const invoiceTitleRegex = /^[()()\u4e00-\u9fa5]{0,50}$/;
|
||||||
|
return invoiceTitleRegex.test(invoiceTitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isValidTaxId(taxId) {
|
||||||
|
// https://www.codetd.com/article/8592083
|
||||||
|
const regArr = [/^[\da-z]{10,15}$/i, /^\d{6}[\da-z]{10,12}$/i, /^[a-z]\d{6}[\da-z]{9,11}$/i, /^[a-z]{2}\d{6}[\da-z]{8,10}$/i, /^\d{14}[\dx][\da-z]{4,5}$/i, /^\d{17}[\dx][\da-z]{1,2}$/i, /^[a-z]\d{14}[\dx][\da-z]{3,4}$/i, /^[a-z]\d{17}[\dx][\da-z]{0,1}$/i, /^[\d]{6}[\da-z]{13,14}$/i];
|
||||||
|
for (let i = 0; i < regArr.length; i++) {
|
||||||
|
if (regArr[i].test(taxId)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
export function isAffiliationPrompted(application) {
|
export function isAffiliationPrompted(application) {
|
||||||
const signupItem = getSignupItem(application, "Affiliation");
|
const signupItem = getSignupItem(application, "Affiliation");
|
||||||
if (signupItem === null) {
|
if (signupItem === null) {
|
||||||
@@ -199,11 +325,25 @@ export function openLink(link) {
|
|||||||
w.location.href = link;
|
w.location.href = link;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function openLinkSafe(link) {
|
||||||
|
// Javascript window.open issue in safari
|
||||||
|
// https://stackoverflow.com/questions/45569893/javascript-window-open-issue-in-safari
|
||||||
|
let a = document.createElement('a');
|
||||||
|
a.href = link;
|
||||||
|
a.setAttribute('target', '_blank');
|
||||||
|
a.click();
|
||||||
|
}
|
||||||
|
|
||||||
export function goToLink(link) {
|
export function goToLink(link) {
|
||||||
window.location.href = link;
|
window.location.href = link;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function goToLinkSoft(ths, link) {
|
export function goToLinkSoft(ths, link) {
|
||||||
|
if (link.startsWith("http")) {
|
||||||
|
openLink(link);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ths.props.history.push(link);
|
ths.props.history.push(link);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,6 +354,8 @@ export function showMessage(type, text) {
|
|||||||
message.success(text);
|
message.success(text);
|
||||||
} else if (type === "error") {
|
} else if (type === "error") {
|
||||||
message.error(text);
|
message.error(text);
|
||||||
|
} else if (type === "info") {
|
||||||
|
message.info(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,27 +487,26 @@ export function changeLanguage(language) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function changeMomentLanguage(language) {
|
export function changeMomentLanguage(language) {
|
||||||
return;
|
// if (language === "zh") {
|
||||||
if (language === "zh") {
|
// moment.locale("zh", {
|
||||||
moment.locale("zh", {
|
// relativeTime: {
|
||||||
relativeTime: {
|
// future: "%s内",
|
||||||
future: "%s内",
|
// past: "%s前",
|
||||||
past: "%s前",
|
// s: "几秒",
|
||||||
s: "几秒",
|
// ss: "%d秒",
|
||||||
ss: "%d秒",
|
// m: "1分钟",
|
||||||
m: "1分钟",
|
// mm: "%d分钟",
|
||||||
mm: "%d分钟",
|
// h: "1小时",
|
||||||
h: "1小时",
|
// hh: "%d小时",
|
||||||
hh: "%d小时",
|
// d: "1天",
|
||||||
d: "1天",
|
// dd: "%d天",
|
||||||
dd: "%d天",
|
// M: "1个月",
|
||||||
M: "1个月",
|
// MM: "%d个月",
|
||||||
MM: "%d个月",
|
// y: "1年",
|
||||||
y: "1年",
|
// yy: "%d年",
|
||||||
yy: "%d年",
|
// },
|
||||||
},
|
// });
|
||||||
});
|
// }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getClickable(text) {
|
export function getClickable(text) {
|
||||||
@@ -380,9 +521,20 @@ export function getClickable(text) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getProviderLogoURL(provider) {
|
||||||
|
if (provider.category === "OAuth") {
|
||||||
|
if (provider.type === "Custom") {
|
||||||
|
return provider.customLogo;
|
||||||
|
}
|
||||||
|
return `${StaticBaseUrl}/img/social_${provider.type.toLowerCase()}.png`;
|
||||||
|
} else {
|
||||||
|
return OtherProviderInfo[provider.category][provider.type].logo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function getProviderLogo(provider) {
|
export function getProviderLogo(provider) {
|
||||||
const idp = provider.type.toLowerCase().trim().split(' ')[0];
|
const idp = provider.type.toLowerCase().trim().split(' ')[0];
|
||||||
const url = `${StaticBaseUrl}/img/social_${idp}.png`;
|
const url = getProviderLogoURL(provider);
|
||||||
return (
|
return (
|
||||||
<img width={30} height={30} src={url} alt={idp} />
|
<img width={30} height={30} src={url} alt={idp} />
|
||||||
)
|
)
|
||||||
@@ -414,6 +566,10 @@ export function getProviderTypeOptions(category) {
|
|||||||
{id: 'AzureAD', name: 'AzureAD'},
|
{id: 'AzureAD', name: 'AzureAD'},
|
||||||
{id: 'Slack', name: 'Slack'},
|
{id: 'Slack', name: 'Slack'},
|
||||||
{id: 'Steam', name: 'Steam'},
|
{id: 'Steam', name: 'Steam'},
|
||||||
|
{id: 'Bilibili', name: 'Bilibili'},
|
||||||
|
{id: 'Okta', name: 'Okta'},
|
||||||
|
{id: 'Douyin', name: 'Douyin'},
|
||||||
|
{id: 'Custom', name: 'Custom'},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
} else if (category === "Email") {
|
} else if (category === "Email") {
|
||||||
@@ -438,6 +594,7 @@ export function getProviderTypeOptions(category) {
|
|||||||
{id: 'AWS S3', name: 'AWS S3'},
|
{id: 'AWS S3', name: 'AWS S3'},
|
||||||
{id: 'Aliyun OSS', name: 'Aliyun OSS'},
|
{id: 'Aliyun OSS', name: 'Aliyun OSS'},
|
||||||
{id: 'Tencent Cloud COS', name: 'Tencent Cloud COS'},
|
{id: 'Tencent Cloud COS', name: 'Tencent Cloud COS'},
|
||||||
|
{id: 'Azure Blob', name: 'Azure Blob'}
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
} else if (category === "SAML") {
|
} else if (category === "SAML") {
|
||||||
@@ -452,6 +609,12 @@ export function getProviderTypeOptions(category) {
|
|||||||
{id: 'PayPal', name: 'PayPal'},
|
{id: 'PayPal', name: 'PayPal'},
|
||||||
{id: 'GC', name: 'GC'},
|
{id: 'GC', name: 'GC'},
|
||||||
]);
|
]);
|
||||||
|
} else if (category === "Captcha") {
|
||||||
|
return ([
|
||||||
|
{id: 'Default', name: 'Default'},
|
||||||
|
{id: 'reCAPTCHA', name: 'reCAPTCHA'},
|
||||||
|
{id: 'hCaptcha', name: 'hCaptcha'},
|
||||||
|
]);
|
||||||
} else {
|
} else {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -660,6 +823,15 @@ export function getFromLink() {
|
|||||||
return from;
|
return from;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function scrollToDiv(divId) {
|
||||||
|
if (divId) {
|
||||||
|
let ele = document.getElementById(divId);
|
||||||
|
if (ele) {
|
||||||
|
ele.scrollIntoView({behavior: "smooth"});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function getSyncerTableColumns(syncer) {
|
export function getSyncerTableColumns(syncer) {
|
||||||
switch (syncer.type) {
|
switch (syncer.type) {
|
||||||
case "Keycloak":
|
case "Keycloak":
|
||||||
@@ -683,7 +855,7 @@ export function getSyncerTableColumns(syncer) {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name":"USERNAME",
|
"name":"LAST_NAME+FIRST_NAME",
|
||||||
"type":"string",
|
"type":"string",
|
||||||
"casdoorName":"DisplayName",
|
"casdoorName":"DisplayName",
|
||||||
"isHashed":true,
|
"isHashed":true,
|
||||||
|
@@ -69,27 +69,35 @@ class SignupTable extends React.Component {
|
|||||||
key: 'name',
|
key: 'name',
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
const items = [
|
const items = [
|
||||||
{id: 'Username', name: 'Username'},
|
{name: "Username", displayName: i18next.t("signup:Username")},
|
||||||
{id: 'ID', name: 'ID'},
|
{name: "ID", displayName: i18next.t("general:ID")},
|
||||||
{id: 'Display name', name: 'Display name'},
|
{name: "Display name", displayName: i18next.t("general:Display name")},
|
||||||
{id: 'Affiliation', name: 'Affiliation'},
|
{name: "Affiliation", displayName: i18next.t("user:Affiliation")},
|
||||||
{id: 'Country/Region', name: 'Country/Region'},
|
{name: "Country/Region", displayName: i18next.t("user:Country/Region")},
|
||||||
{id: 'ID card', name: 'ID card'},
|
{name: "ID card", displayName: i18next.t("user:ID card")},
|
||||||
{id: 'Email', name: 'Email'},
|
{name: "Email", displayName: i18next.t("general:Email")},
|
||||||
{id: 'Password', name: 'Password'},
|
{name: "Password", displayName: i18next.t("forget:Password")},
|
||||||
{id: 'Confirm password', name: 'Confirm password'},
|
{name: "Confirm password", displayName: i18next.t("forget:Confirm")},
|
||||||
{id: 'Phone', name: 'Phone'},
|
{name: "Phone", displayName: i18next.t("general:Phone")},
|
||||||
{id: 'Agreement', name: 'Agreement'},
|
{name: "Agreement", displayName: i18next.t("signup:Agreement")},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const getItemDisplayName = (text) => {
|
||||||
|
const item = items.filter(item => item.name === text);
|
||||||
|
if (item.length === 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return item[0].displayName;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select virtual={false} style={{width: '100%'}}
|
<Select virtual={false} style={{width: '100%'}}
|
||||||
value={text}
|
value={getItemDisplayName(text)}
|
||||||
onChange={value => {
|
onChange={value => {
|
||||||
this.updateField(table, index, 'name', value);
|
this.updateField(table, index, 'name', value);
|
||||||
}} >
|
}} >
|
||||||
{
|
{
|
||||||
Setting.getDeduplicatedArray(items, table, "name").map((item, index) => <Option key={index} value={item.name}>{item.name}</Option>)
|
Setting.getDeduplicatedArray(items, table, "name").map((item, index) => <Option key={index} value={item.name}>{item.displayName}</Option>)
|
||||||
}
|
}
|
||||||
</Select>
|
</Select>
|
||||||
)
|
)
|
||||||
@@ -156,10 +164,10 @@ class SignupTable extends React.Component {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("provider:rule"),
|
title: i18next.t("application:rule"),
|
||||||
dataIndex: 'rule',
|
dataIndex: 'rule',
|
||||||
key: 'rule',
|
key: 'rule',
|
||||||
width: '120px',
|
width: '155px',
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
let options = [];
|
let options = [];
|
||||||
if (record.name === "ID") {
|
if (record.name === "ID") {
|
||||||
@@ -167,12 +175,17 @@ class SignupTable extends React.Component {
|
|||||||
{id: 'Random', name: 'Random'},
|
{id: 'Random', name: 'Random'},
|
||||||
{id: 'Incremental', name: 'Incremental'},
|
{id: 'Incremental', name: 'Incremental'},
|
||||||
];
|
];
|
||||||
} if (record.name === "Display name") {
|
} else if (record.name === "Display name") {
|
||||||
options = [
|
options = [
|
||||||
{id: 'None', name: 'None'},
|
{id: 'None', name: 'None'},
|
||||||
{id: 'Real name', name: 'Real name'},
|
{id: 'Real name', name: 'Real name'},
|
||||||
{id: 'First, last', name: 'First, last'},
|
{id: 'First, last', name: 'First, last'},
|
||||||
];
|
];
|
||||||
|
} else if (record.name === "Email") {
|
||||||
|
options = [
|
||||||
|
{id: 'Normal', name: 'Normal'},
|
||||||
|
{id: 'No verification', name: 'No verification'},
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.length === 0) {
|
if (options.length === 0) {
|
||||||
|
@@ -119,8 +119,12 @@ class SyncerEditPage extends React.Component {
|
|||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Select virtual={false} style={{width: '100%'}} value={this.state.syncer.type} onChange={(value => {
|
<Select virtual={false} style={{width: '100%'}} value={this.state.syncer.type} onChange={(value => {
|
||||||
this.updateSyncerField('type', value);
|
this.updateSyncerField('type', value);
|
||||||
this.state.syncer["tableColumns"] = Setting.getSyncerTableColumns(this.state.syncer);
|
let syncer = this.state.syncer;
|
||||||
this.state.syncer.table = value === "Keycloak" ? "user_entity" : this.state.syncer.table;
|
syncer["tableColumns"] = Setting.getSyncerTableColumns(this.state.syncer);
|
||||||
|
syncer.table = (value === "Keycloak") ? "user_entity" : this.state.syncer.table;
|
||||||
|
this.setState({
|
||||||
|
syncer: syncer,
|
||||||
|
});
|
||||||
})}>
|
})}>
|
||||||
{
|
{
|
||||||
['Database', 'LDAP', 'Keycloak']
|
['Database', 'LDAP', 'Keycloak']
|
||||||
|
@@ -73,6 +73,20 @@ class SyncerListPage extends BaseListPage {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
runSyncer(i) {
|
||||||
|
this.setState({loading: true});
|
||||||
|
SyncerBackend.runSyncer("admin", this.state.data[i].name)
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({loading: false});
|
||||||
|
Setting.showMessage("success", `Syncer sync users successfully`);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch(error => {
|
||||||
|
this.setState({loading: false});
|
||||||
|
Setting.showMessage("error", `Syncer failed to sync users: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
renderTable(syncers) {
|
renderTable(syncers) {
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
@@ -205,12 +219,13 @@ class SyncerListPage extends BaseListPage {
|
|||||||
title: i18next.t("general:Action"),
|
title: i18next.t("general:Action"),
|
||||||
dataIndex: '',
|
dataIndex: '',
|
||||||
key: 'op',
|
key: 'op',
|
||||||
width: '170px',
|
width: '240px',
|
||||||
fixed: (Setting.isMobile()) ? "false" : "right",
|
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/syncers/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.runSyncer(index)}>{i18next.t("general:Sync")}</Button>
|
||||||
|
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} onClick={() => this.props.history.push(`/syncers/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title={`Sure to delete syncer: ${record.name} ?`}
|
title={`Sure to delete syncer: ${record.name} ?`}
|
||||||
onConfirm={() => this.deleteSyncer(index)}
|
onConfirm={() => this.deleteSyncer(index)}
|
||||||
|
@@ -19,7 +19,6 @@ import moment from "moment";
|
|||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import * as TokenBackend from "./backend/TokenBackend";
|
import * as TokenBackend from "./backend/TokenBackend";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import * as ResourceBackend from "./backend/ResourceBackend";
|
|
||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
|
|
||||||
class TokenListPage extends BaseListPage {
|
class TokenListPage extends BaseListPage {
|
||||||
|
@@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Button, Card, Col, Input, Row, Select, Switch} from 'antd';
|
import {Button, Card, Col, Input, Result, Row, Select, Spin, Switch} from 'antd';
|
||||||
import * as UserBackend from "./backend/UserBackend";
|
import * as UserBackend from "./backend/UserBackend";
|
||||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
@@ -47,6 +47,7 @@ class UserEditPage extends React.Component {
|
|||||||
organizations: [],
|
organizations: [],
|
||||||
applications: [],
|
applications: [],
|
||||||
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||||
|
loading: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,9 +60,14 @@ class UserEditPage extends React.Component {
|
|||||||
|
|
||||||
getUser() {
|
getUser() {
|
||||||
UserBackend.getUser(this.state.organizationName, this.state.userName)
|
UserBackend.getUser(this.state.organizationName, this.state.userName)
|
||||||
.then((user) => {
|
.then((data) => {
|
||||||
|
if (data.status === null || data.status !== "error") {
|
||||||
|
this.setState({
|
||||||
|
user: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
user: user,
|
loading: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -118,46 +124,86 @@ class UserEditPage extends React.Component {
|
|||||||
return (this.state.user.id === this.props.account?.id) || Setting.isAdminUser(this.props.account);
|
return (this.state.user.id === this.props.account?.id) || Setting.isAdminUser(this.props.account);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderUser() {
|
renderAccountItem(accountItem) {
|
||||||
return (
|
if (!accountItem.visible) {
|
||||||
<Card size="small" title={
|
return null;
|
||||||
<div>
|
}
|
||||||
{this.state.mode === "add" ? i18next.t("user:New User") : i18next.t("user:Edit User")}
|
|
||||||
<Button onClick={() => this.submitUserEdit(false)}>{i18next.t("general:Save")}</Button>
|
const isSelf = this.state.user.id === this.props.account?.id;
|
||||||
<Button style={{marginLeft: '20px'}} type="primary" onClick={() => this.submitUserEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
const isAdmin = Setting.isAdminUser(this.props.account);
|
||||||
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} onClick={() => this.deleteUser()}>{i18next.t("general:Cancel")}</Button> : null}
|
|
||||||
</div>
|
// return (
|
||||||
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner">
|
// <div>
|
||||||
|
// {
|
||||||
|
// JSON.stringify({accountItem: accountItem, isSelf: isSelf, isAdmin: isAdmin})
|
||||||
|
// }
|
||||||
|
// </div>
|
||||||
|
// )
|
||||||
|
|
||||||
|
if (accountItem.viewRule === "Self") {
|
||||||
|
if (!isSelf && !isAdmin) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else if (accountItem.viewRule === "Admin") {
|
||||||
|
if (!isAdmin) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let disabled = false;
|
||||||
|
if (accountItem.modifyRule === "Self") {
|
||||||
|
if (!isSelf && !isAdmin) {
|
||||||
|
disabled = true;
|
||||||
|
}
|
||||||
|
} else if (accountItem.modifyRule === "Admin") {
|
||||||
|
if (!isAdmin) {
|
||||||
|
disabled = true;
|
||||||
|
}
|
||||||
|
} else if (accountItem.modifyRule === "Immutable") {
|
||||||
|
disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accountItem.name === "Organization") {
|
||||||
|
return (
|
||||||
<Row style={{marginTop: '10px'}} >
|
<Row style={{marginTop: '10px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Select virtual={false} style={{width: '100%'}} disabled={!Setting.isAdminUser(this.props.account)} value={this.state.user.owner} onChange={(value => {this.updateUserField('owner', value);})}>
|
<Select virtual={false} style={{width: '100%'}} disabled={disabled} value={this.state.user.owner} onChange={(value => {this.updateUserField('owner', value);})}>
|
||||||
{
|
{
|
||||||
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
|
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
|
||||||
}
|
}
|
||||||
</Select>
|
</Select>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "ID") {
|
||||||
|
return (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel("ID", i18next.t("general:ID - Tooltip"))} :
|
{Setting.getLabel("ID", i18next.t("general:ID - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.user.id} disabled={true} />
|
<Input value={this.state.user.id} disabled={disabled} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Name") {
|
||||||
|
return (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.user.name} disabled={!Setting.isAdminUser(this.props.account)} onChange={e => {
|
<Input value={this.state.user.name} disabled={disabled} onChange={e => {
|
||||||
this.updateUserField('name', e.target.value);
|
this.updateUserField('name', e.target.value);
|
||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Display name") {
|
||||||
|
return (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
|
||||||
@@ -168,6 +214,9 @@ class UserEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Avatar") {
|
||||||
|
return (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:Avatar"), i18next.t("general:Avatar - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Avatar"), i18next.t("general:Avatar - Tooltip"))} :
|
||||||
@@ -198,6 +247,9 @@ class UserEditPage extends React.Component {
|
|||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "User type") {
|
||||||
|
return (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:User type"), i18next.t("general:User type - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:User type"), i18next.t("general:User type - Tooltip"))} :
|
||||||
@@ -211,44 +263,56 @@ class UserEditPage extends React.Component {
|
|||||||
</Select>
|
</Select>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Password") {
|
||||||
|
return (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<PasswordModal user={this.state.user} account={this.props.account} disabled={this.state.userName !== this.state.user.name} />
|
<PasswordModal user={this.state.user} account={this.props.account} disabled={disabled} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Email") {
|
||||||
|
return (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:Email"), i18next.t("general:Email - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Email"), i18next.t("general:Email - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col style={{paddingRight: '20px'}} span={11} >
|
<Col style={{paddingRight: '20px'}} span={11} >
|
||||||
<Input value={this.state.user.email}
|
<Input value={this.state.user.email}
|
||||||
disabled={this.state.user.id === this.props.account?.id ? true : !Setting.isAdminUser(this.props.account)}
|
disabled={disabled}
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
this.updateUserField('email', e.target.value);
|
this.updateUserField('email', e.target.value);
|
||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={11} >
|
<Col span={11} >
|
||||||
{ this.state.user.id === this.props.account?.id ? (<ResetModal org={this.state.application?.organizationObj} buttonText={i18next.t("user:Reset Email...")} destType={"email"} />) : null}
|
{ this.state.user.id === this.props.account?.id ? (<ResetModal org={this.state.application?.organizationObj} buttonText={i18next.t("user:Reset Email...")} destType={"email"} />) : null}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Phone") {
|
||||||
|
return (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:Phone"), i18next.t("general:Phone - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Phone"), i18next.t("general:Phone - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col style={{paddingRight: '20px'}} span={11} >
|
<Col style={{paddingRight: '20px'}} span={11} >
|
||||||
<Input value={this.state.user.phone} addonBefore={`+${this.state.application?.organizationObj.phonePrefix}`}
|
<Input value={this.state.user.phone} addonBefore={`+${this.state.application?.organizationObj.phonePrefix}`}
|
||||||
disabled={this.state.user.id === this.props.account?.id ? true : !Setting.isAdminUser(this.props.account)}
|
disabled={disabled}
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
this.updateUserField('phone', e.target.value);
|
this.updateUserField('phone', e.target.value);
|
||||||
}}/>
|
}}/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={11} >
|
<Col span={11} >
|
||||||
{ this.state.user.id === this.props.account?.id ? (<ResetModal org={this.state.application?.organizationObj} buttonText={i18next.t("user:Reset Phone...")} destType={"phone"} />) : null}
|
{ this.state.user.id === this.props.account?.id ? (<ResetModal org={this.state.application?.organizationObj} buttonText={i18next.t("user:Reset Phone...")} destType={"phone"} />) : null}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Country/Region") {
|
||||||
|
return (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("user:Country/Region"), i18next.t("user:Country/Region - Tooltip"))} :
|
{Setting.getLabel(i18next.t("user:Country/Region"), i18next.t("user:Country/Region - Tooltip"))} :
|
||||||
@@ -259,6 +323,9 @@ class UserEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Location") {
|
||||||
|
return (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("user:Location"), i18next.t("user:Location - Tooltip"))} :
|
{Setting.getLabel(i18next.t("user:Location"), i18next.t("user:Location - Tooltip"))} :
|
||||||
@@ -269,11 +336,15 @@ class UserEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{
|
)
|
||||||
(this.state.application === null || this.state.user === null) ? null : (
|
} else if (accountItem.name === "Affiliation") {
|
||||||
<AffiliationSelect labelSpan={(Setting.isMobile()) ? 22 : 2} application={this.state.application} user={this.state.user} onUpdateUserField={(key, value) => { return this.updateUserField(key, value)}} />
|
return (
|
||||||
)
|
(this.state.application === null || this.state.user === null) ? null : (
|
||||||
}
|
<AffiliationSelect labelSpan={(Setting.isMobile()) ? 22 : 2} application={this.state.application} user={this.state.user} onUpdateUserField={(key, value) => { return this.updateUserField(key, value)}} />
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Title") {
|
||||||
|
return (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("user:Title"), i18next.t("user:Title - Tooltip"))} :
|
{Setting.getLabel(i18next.t("user:Title"), i18next.t("user:Title - Tooltip"))} :
|
||||||
@@ -284,6 +355,9 @@ class UserEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Homepage") {
|
||||||
|
return (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("user:Homepage"), i18next.t("user:Homepage - Tooltip"))} :
|
{Setting.getLabel(i18next.t("user:Homepage"), i18next.t("user:Homepage - Tooltip"))} :
|
||||||
@@ -294,6 +368,9 @@ class UserEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Bio") {
|
||||||
|
return (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("user:Bio"), i18next.t("user:Bio - Tooltip"))} :
|
{Setting.getLabel(i18next.t("user:Bio"), i18next.t("user:Bio - Tooltip"))} :
|
||||||
@@ -304,6 +381,9 @@ class UserEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Tag") {
|
||||||
|
return (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("user:Tag"), i18next.t("user:Tag - Tooltip"))} :
|
{Setting.getLabel(i18next.t("user:Tag"), i18next.t("user:Tag - Tooltip"))} :
|
||||||
@@ -329,98 +409,136 @@ class UserEditPage extends React.Component {
|
|||||||
}
|
}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Signup application") {
|
||||||
|
return (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:Signup application"), i18next.t("general:Signup application - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Signup application"), i18next.t("general:Signup application - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Select virtual={false} style={{width: '100%'}} disabled={!Setting.isAdminUser(this.props.account)} value={this.state.user.signupApplication} onChange={(value => {this.updateUserField('signupApplication', value);})}>
|
<Select virtual={false} style={{width: '100%'}} disabled={disabled} value={this.state.user.signupApplication} onChange={(value => {this.updateUserField('signupApplication', value);})}>
|
||||||
{
|
{
|
||||||
this.state.applications.map((application, index) => <Option key={index} value={application.name}>{application.name}</Option>)
|
this.state.applications.map((application, index) => <Option key={index} value={application.name}>{application.name}</Option>)
|
||||||
}
|
}
|
||||||
</Select>
|
</Select>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{
|
)
|
||||||
!this.isSelfOrAdmin() ? null : (
|
} else if (accountItem.name === "3rd-party logins") {
|
||||||
<Row style={{marginTop: '20px'}} >
|
return (
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
!this.isSelfOrAdmin() ? null : (
|
||||||
{Setting.getLabel(i18next.t("user:3rd-party logins"), i18next.t("user:3rd-party logins - Tooltip"))} :
|
<Row style={{marginTop: '20px'}} >
|
||||||
</Col>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
<Col span={22} >
|
{Setting.getLabel(i18next.t("user:3rd-party logins"), i18next.t("user:3rd-party logins - Tooltip"))} :
|
||||||
<div style={{marginBottom: 20}}>
|
</Col>
|
||||||
{
|
<Col span={22} >
|
||||||
(this.state.application === null || this.state.user === null) ? null : (
|
<div style={{marginBottom: 20}}>
|
||||||
this.state.application?.providers.filter(providerItem => Setting.isProviderVisible(providerItem)).map((providerItem, index) =>
|
{
|
||||||
(providerItem.provider.category === "OAuth") ? (
|
(this.state.application === null || this.state.user === null) ? null : (
|
||||||
<OAuthWidget key={providerItem.name} labelSpan={(Setting.isMobile()) ? 10 : 3} user={this.state.user} application={this.state.application} providerItem={providerItem} onUnlinked={() => { return this.unlinked()}} />
|
this.state.application?.providers.filter(providerItem => Setting.isProviderVisible(providerItem)).map((providerItem, index) =>
|
||||||
) : (
|
(providerItem.provider.category === "OAuth") ? (
|
||||||
<SamlWidget key={providerItem.name} labelSpan={(Setting.isMobile()) ? 10 : 3} user={this.state.user} application={this.state.application} providerItem={providerItem} onUnlinked={() => { return this.unlinked()}} />
|
<OAuthWidget key={providerItem.name} labelSpan={(Setting.isMobile()) ? 10 : 3} user={this.state.user} application={this.state.application} providerItem={providerItem} onUnlinked={() => { return this.unlinked()}} />
|
||||||
)
|
) : (
|
||||||
|
<SamlWidget key={providerItem.name} labelSpan={(Setting.isMobile()) ? 10 : 3} user={this.state.user} application={this.state.application} providerItem={providerItem} onUnlinked={() => { return this.unlinked()}} />
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
)
|
||||||
</div>
|
}
|
||||||
</Col>
|
</div>
|
||||||
</Row>
|
</Col>
|
||||||
)
|
</Row>
|
||||||
}
|
)
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Properties") {
|
||||||
|
return (
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{i18next.t("user:Properties")}:
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<CodeMirror
|
||||||
|
value={JSON.stringify(this.state.user.properties, null, 4)}
|
||||||
|
options={{mode: 'javascript', theme: "material-darker"}}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Is admin") {
|
||||||
|
return (
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("user:Is admin"), i18next.t("user:Is admin - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={(Setting.isMobile()) ? 22 : 2} >
|
||||||
|
<Switch checked={this.state.user.isAdmin} onChange={checked => {
|
||||||
|
this.updateUserField('isAdmin', checked);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Is global admin") {
|
||||||
|
return (
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("user:Is global admin"), i18next.t("user:Is global admin - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={(Setting.isMobile()) ? 22 : 2} >
|
||||||
|
<Switch checked={this.state.user.isGlobalAdmin} onChange={checked => {
|
||||||
|
this.updateUserField('isGlobalAdmin', checked);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Is forbidden") {
|
||||||
|
return (
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("user:Is forbidden"), i18next.t("user:Is forbidden - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={(Setting.isMobile()) ? 22 : 2} >
|
||||||
|
<Switch checked={this.state.user.isForbidden} onChange={checked => {
|
||||||
|
this.updateUserField('isForbidden', checked);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
} else if (accountItem.name === "Is deleted") {
|
||||||
|
return (
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("user:Is deleted"), i18next.t("user:Is deleted - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={(Setting.isMobile()) ? 22 : 2} >
|
||||||
|
<Switch checked={this.state.user.isDeleted} onChange={checked => {
|
||||||
|
this.updateUserField('isDeleted', checked);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderUser() {
|
||||||
|
return (
|
||||||
|
<Card size="small" title={
|
||||||
|
<div>
|
||||||
|
{this.state.mode === "add" ? i18next.t("user:New User") : i18next.t("user:Edit User")}
|
||||||
|
<Button onClick={() => this.submitUserEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||||
|
<Button style={{marginLeft: '20px'}} type="primary" onClick={() => this.submitUserEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||||
|
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} onClick={() => this.deleteUser()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||||
|
</div>
|
||||||
|
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner">
|
||||||
{
|
{
|
||||||
!Setting.isAdminUser(this.props.account) ? null : (
|
this.state.application?.organizationObj.accountItems?.map(accountItem => {
|
||||||
<React.Fragment>
|
return (
|
||||||
{/*<Row style={{marginTop: '20px'}} >*/}
|
<React.Fragment key={accountItem.name}>
|
||||||
{/* <Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>*/}
|
{
|
||||||
{/* {i18next.t("user:Properties")}:*/}
|
this.renderAccountItem(accountItem)
|
||||||
{/* </Col>*/}
|
}
|
||||||
{/* <Col span={22} >*/}
|
</React.Fragment>
|
||||||
{/* <CodeMirror*/}
|
)
|
||||||
{/* value={JSON.stringify(this.state.user.properties, null, 4)}*/}
|
})
|
||||||
{/* options={{mode: 'javascript', theme: "material-darker"}}*/}
|
|
||||||
{/* />*/}
|
|
||||||
{/* </Col>*/}
|
|
||||||
{/*</Row>*/}
|
|
||||||
<Row style={{marginTop: '20px'}} >
|
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
|
||||||
{Setting.getLabel(i18next.t("user:Is admin"), i18next.t("user:Is admin - Tooltip"))} :
|
|
||||||
</Col>
|
|
||||||
<Col span={(Setting.isMobile()) ? 22 : 2} >
|
|
||||||
<Switch checked={this.state.user.isAdmin} onChange={checked => {
|
|
||||||
this.updateUserField('isAdmin', checked);
|
|
||||||
}} />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row style={{marginTop: '20px'}} >
|
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
|
||||||
{Setting.getLabel(i18next.t("user:Is global admin"), i18next.t("user:Is global admin - Tooltip"))} :
|
|
||||||
</Col>
|
|
||||||
<Col span={(Setting.isMobile()) ? 22 : 2} >
|
|
||||||
<Switch checked={this.state.user.isGlobalAdmin} onChange={checked => {
|
|
||||||
this.updateUserField('isGlobalAdmin', checked);
|
|
||||||
}} />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row style={{marginTop: '20px'}} >
|
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
|
||||||
{Setting.getLabel(i18next.t("user:Is forbidden"), i18next.t("user:Is forbidden - Tooltip"))} :
|
|
||||||
</Col>
|
|
||||||
<Col span={(Setting.isMobile()) ? 22 : 2} >
|
|
||||||
<Switch checked={this.state.user.isForbidden} onChange={checked => {
|
|
||||||
this.updateUserField('isForbidden', checked);
|
|
||||||
}} />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row style={{marginTop: '20px'}} >
|
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
|
||||||
{Setting.getLabel(i18next.t("user:Is deleted"), i18next.t("user:Is deleted - Tooltip"))} :
|
|
||||||
</Col>
|
|
||||||
<Col span={(Setting.isMobile()) ? 22 : 2} >
|
|
||||||
<Switch checked={this.state.user.isDeleted} onChange={checked => {
|
|
||||||
this.updateUserField('isDeleted', checked);
|
|
||||||
}} />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</React.Fragment>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
@@ -469,13 +587,24 @@ class UserEditPage extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{
|
{
|
||||||
this.state.user !== null ? this.renderUser() : null
|
this.state.loading ? <Spin size="large" /> : (
|
||||||
|
this.state.user !== null ? this.renderUser() :
|
||||||
|
<Result
|
||||||
|
status="404"
|
||||||
|
title="404 NOT FOUND"
|
||||||
|
subTitle={i18next.t("general:Sorry, the user you visited does not exist or you are not authorized to access this user.")}
|
||||||
|
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
this.state.user === null ? null :
|
||||||
|
<div style={{marginTop: '20px', marginLeft: '40px'}}>
|
||||||
|
<Button size="large" onClick={() => this.submitUserEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||||
|
<Button style={{marginLeft: '20px'}} type="primary" size="large" onClick={() => this.submitUserEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||||
|
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} size="large" onClick={() => this.deleteUser()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
<div style={{marginTop: '20px', marginLeft: '40px'}}>
|
|
||||||
<Button size="large" onClick={() => this.submitUserEdit(false)}>{i18next.t("general:Save")}</Button>
|
|
||||||
<Button style={{marginLeft: '20px'}} type="primary" size="large" onClick={() => this.submitUserEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
|
||||||
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} size="large" onClick={() => this.deleteUser()}>{i18next.t("general:Cancel")}</Button> : null}
|
|
||||||
</div>
|
|
||||||
</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