Compare commits

..

40 Commits

Author SHA1 Message Date
Yixiang Zhao
63c09a879f fix: disable jsx-a11y/anchor-is-valid (#800)
* fix: disable jsx-a11y/anchor-is-valid

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

* Update LoginPage.js

* Update SignupPage.js

Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2022-06-17 19:57:11 +08:00
limix
61c80e790f Fix Authentication failure! invalid_ticket: OneLogin::RubySaml::ValidationError #798 (#799) 2022-06-17 18:35:44 +08:00
Gucheng Wang
be91ff47aa Fix logo columns. 2022-06-17 00:07:16 +08:00
Gucheng Wang
b4c18eb7a4 Use codemirror for samlMetadata. 2022-06-16 23:59:18 +08:00
Gucheng Wang
0f483fb65b Improve preview buttons to copy link. 2022-06-16 22:01:09 +08:00
Gucheng Wang
ebe9889d58 Improve i18n 2022-06-16 21:35:52 +08:00
Gucheng Wang
ee42fcac8e Remove signup_item.go 2022-06-16 20:52:54 +08:00
ziliangyu
6187b48f61 fix: show alert when user clicks on application edit page's preview window (#794)
* fix:Show alert when user clicks on application edit page's preview window

* fix: Show alert when user clicks on application edit page's preview window in preview

* fix:Show alert when user clicks on application edit page's preview window

* fix: Show alert when user clicks on application edit page's preview window in preview

* Update ApplicationEditPage.js

* fix: show alert when user clicks on application edit page's preview window

Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2022-06-15 22:11:37 +08:00
Gucheng Wang
2020955270 Fix cannot support old Docker version bug, revert PR: https://github.com/casdoor/casdoor/pull/606 2022-06-15 01:20:00 +08:00
Gucheng Wang
1b5a8f8e57 Fix missing i18n text. 2022-06-15 00:55:06 +08:00
Товарищ программист
ff94e5164a feat: fix incorrect CAS url concatenation (#795)
* fix: fix incorrect cas url concatenation

* Update LoginPage.js

Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2022-06-14 21:51:40 +08:00
ziliangyu
15a6fd2b52 feat: show alert when user clicks on application edit page's preview wi… (#791)
* fix:Show alert when user clicks on application edit page's preview window

* fix: Show alert when user clicks on application edit page's preview window in preview

* fix:Show alert when user clicks on application edit page's preview window

* fix: Show alert when user clicks on application edit page's preview window in preview

* Update ApplicationEditPage.js

Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2022-06-13 12:18:18 +08:00
Ryao
37b6b50751 fix: remove redundant query for OAuth user (#788) 2022-06-10 15:58:22 +08:00
Ryao
efe5431f54 fix: OAuth user id confusion caused by username (#785) 2022-06-10 00:08:26 +08:00
Yixiang Zhao
e9159902eb fix: fix the web compiled warnings (#778)
* fix: fix the web compiled warnings

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

* fix: disable changeMomentLanguage

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

* Update SyncerEditPage.js

* Update UserEditPage.js

* Update ResourceListPage.js

* Update ProviderEditPage.js

* Update ProductBuyPage.js

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-06-05 20:56:31 +08:00
caoshengdong
604e2757c8 fix: fix the problem that user owner is not updated when updating organization name (#775)
* fix: use openid or unionid as username rather than nickname when logging with WeChat
FIX #762

* fix: fix the problem that user owner is not updated when updating organization name

* Update wechat.go

Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2022-06-03 00:37:22 +08:00
Gucheng Wang
88c5aae9e9 Fix meta desc info. 2022-06-01 22:22:00 +08:00
Товарищ программист
3d0cf8788b fix: trigger missing webhook (#770)
* fix: trigger missing webhook

* Update auth.go

Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2022-06-01 09:34:56 +08:00
Yixiang Zhao
e78ea2546f fix: bilibili name and avatar (#772)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-05-31 21:54:00 +08:00
Roobtyan
f7705931f7 fix: handle WeChat username conflicts (#771)
* handle username conflicts

* Update auth.go

Co-authored-by: roobtyan <roobtyan@qq.com>
Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-05-31 21:51:41 +08:00
caoshengdong
5d8b710bf7 fix: use openid or unionid as username rather than nickname when logging with WeChat (#763)
FIX #762
2022-05-31 21:22:10 +08:00
Yixiang Zhao
b85ad896bf fix: saml endpoint crash (#773)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-05-31 21:10:35 +08:00
Yixiang Zhao
42c2210178 fix: set phone prefix when disable verification code (#769)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-05-30 18:26:42 +08:00
Yixiang Zhao
d52caed3a9 feat: add model page (#757)
* feat: add model page

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

* feat: support config model for permission

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

* translation and indentation

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-05-24 18:27:47 +08:00
Gucheng Wang
27d8cd758d Simplify README 2022-05-23 21:45:31 +08:00
greenhandatsjtu
98f77960de feat: add Douyin OAuth provider (#753) 2022-05-15 20:59:21 +08:00
Yixiang Zhao
e5b71a08ae feat: support "+" in syncer column name (#752)
* feat: support + in syncer column name

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

* feat: trim

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-05-13 20:24:46 +08:00
Jiakuan Li
3ad4b7a43c feat: add Bilibili OAuth (#720)
* implemented bilibili oauth

* add bilibili oauth

* add document address

* add frontend page

* uncheck
2022-05-12 10:07:52 +08:00
Yixiang Zhao
c5c3a08aa9 feat: add saml metadata in application edit page (#750)
* feat: add saml metadata in application edit page

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

* Update ApplicationEditPage.js

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-05-11 20:23:36 +08:00
greenhandatsjtu
8efd964835 fix: unchanged masked client_secret/password updated to *** (#749) 2022-05-10 17:37:12 +08:00
Товарищ программист
5dac87a4c3 feat: hide proxy-test output (#746)
* feat: hide proxy-test output

* Update build.sh

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-05-07 20:56:12 +08:00
Gucheng Wang
49c3266400 Fix missing OIDC response_types_supported. 2022-05-07 09:36:20 +08:00
Gucheng Wang
39548d5d72 Change cert default algorithm to RS256. 2022-05-06 09:34:42 +08:00
Gucheng Wang
1c949e415e Add refresh_token to app grantTypes. 2022-05-06 09:31:22 +08:00
Jan Piechowicz
1b840a2e9f feat: support argon2id pass manager (#744)
* support for argon2id

* Update argon2id.go

Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2022-05-06 09:25:42 +08:00
Gucheng Wang
c9849d8b55 Accept more file formats in upload. 2022-05-04 23:16:24 +08:00
xiexianbin
b747f5e27c fix: mistake GetApplicationLogin swagger router (casdoor#739) (#740)
Signed-off-by: xiexianbin <me@xiexianbin.cn>
2022-05-04 19:15:13 +08:00
一路向北
8b340105c1 fix: fix missing OpenLDAP uid in ldap.go 2022-05-04 10:23:17 +08:00
Resulte Lee
43b1006f11 fix: sign up without email verification do not work (#736) 2022-05-03 18:05:58 +08:00
leoshine
78efc9c2d0 feat: add azure storage support (#735)
* feat: add zure storage support

* Update local_file_system.go

Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2022-05-03 17:59:07 +08:00
69 changed files with 1854 additions and 371 deletions

0
$env Normal file
View File

152
README.md
View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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 {
@@ -133,7 +134,7 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
// @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} controllers.api_controller.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")
@@ -222,7 +223,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 +253,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
util.SafeGoroutine(func() {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))
@@ -321,12 +326,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 +340,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
util.SafeGoroutine(func() {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 +353,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 +402,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
util.SafeGoroutine(func() {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 +421,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 +449,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

120
controllers/model.go Normal file
View 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()
}

View File

@@ -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

38
cred/argon2id.go Normal file
View 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
}

View File

@@ -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
} }

View File

@@ -12,8 +12,6 @@ services:
- 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:

11
go.mod
View File

@@ -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,9 +33,8 @@ 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

71
go.sum
View File

@@ -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,6 +279,7 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ= github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/lestrrat-go/jwx v0.9.0 h1:Fnd0EWzTm0kFrBPzE/PEPp9nzllES5buMkksPMjEKpM= github.com/lestrrat-go/jwx v0.9.0 h1:Fnd0EWzTm0kFrBPzE/PEPp9nzllES5buMkksPMjEKpM=
github.com/lestrrat-go/jwx v0.9.0/go.mod h1:iEoxlYfZjvoGpuWwxUz+eR5e6KTJGsaRcy/YNA/UnBk= github.com/lestrrat-go/jwx v0.9.0/go.mod h1:iEoxlYfZjvoGpuWwxUz+eR5e6KTJGsaRcy/YNA/UnBk=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@@ -262,6 +290,8 @@ github.com/markbates/going v1.0.0 h1:DQw0ZP7NbNlFGcKbcE/IVSOAFzScxRtLpd0rLMzLhq0
github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA= github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA=
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911 h1:erppMjjp69Rertg1zlgRbLJH1u+eCmRPxKjMZ5I8/Ro= github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911 h1:erppMjjp69Rertg1zlgRbLJH1u+eCmRPxKjMZ5I8/Ro=
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To=
github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI=
github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
@@ -315,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=
@@ -385,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=
@@ -433,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=
@@ -449,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=
@@ -468,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=
@@ -482,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=
@@ -501,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=

221
idp/bilibili.go Normal file
View 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
}

198
idp/douyin.go Normal file
View 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
}

View File

@@ -86,8 +86,12 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
return NewCasdoorIdProvider(clientId, clientSecret, redirectUrl, hostUrl) return NewCasdoorIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
} else if typ == "Okta" { } else if typ == "Okta" {
return NewOktaIdProvider(clientId, clientSecret, redirectUrl, hostUrl) 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

View File

@@ -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)

View File

@@ -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)
} }

View File

@@ -147,7 +147,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,

View File

@@ -241,6 +241,7 @@ func (l *ldapConn) GetLdapUsers(baseDn string) ([]ldapUser, error) {
case "uidNumber": case "uidNumber":
ldapUserItem.UidNumber = attribute.Values[0] ldapUserItem.UidNumber = attribute.Values[0]
case "uid": case "uid":
ldapUserItem.Uid = attribute.Values[0]
case "sAMAccountName": case "sAMAccountName":
ldapUserItem.Uid = attribute.Values[0] ldapUserItem.Uid = attribute.Values[0]
case "cn": case "cn":
@@ -248,6 +249,7 @@ func (l *ldapConn) GetLdapUsers(baseDn string) ([]ldapUser, error) {
case "gidNumber": case "gidNumber":
ldapUserItem.GidNumber = attribute.Values[0] ldapUserItem.GidNumber = attribute.Values[0]
case "entryUUID": case "entryUUID":
ldapUserItem.Uuid = attribute.Values[0]
case "objectGUID": case "objectGUID":
ldapUserItem.Uuid = attribute.Values[0] ldapUserItem.Uuid = attribute.Values[0]
case "mail": case "mail":

122
object/model.go Normal file
View 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)
}

View File

@@ -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"},

View File

@@ -121,14 +121,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)
@@ -136,7 +140,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)
} }

View File

@@ -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"`

View File

@@ -172,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)
} }

View File

@@ -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())

View File

@@ -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)
} }

View File

@@ -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" {

View File

@@ -94,7 +94,9 @@ 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"` Okta string `xorm:"okta varchar(100)" json:"okta"`
Douyin string `xorm:"douyin vachar(100)" json:"douyin"`
Custom string `xorm:"custom varchar(100)" json:"custom"` Custom string `xorm:"custom varchar(100)" json:"custom"`
Ldap string `xorm:"ldap varchar(100)" json:"ldap"` Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
@@ -314,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 != "*" {

View File

@@ -65,5 +65,5 @@ func RecordMessage(ctx *context.Context) {
record.Organization, record.User = util.GetOwnerAndNameFromId(userId) record.Organization, record.User = util.GetOwnerAndNameFromId(userId)
} }
util.SafeGoroutine(func() {object.AddRecord(record)}) util.SafeGoroutine(func() { object.AddRecord(record) })
} }

View File

@@ -84,6 +84,12 @@ 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")

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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
} }

View File

@@ -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"

View File

@@ -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

View File

@@ -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 {

View File

@@ -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" />
<!-- <!--

View File

@@ -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} />)}/>

View File

@@ -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>
) )
} }

View File

@@ -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>

View File

@@ -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,

208
web/src/ModelEditPage.js Normal file
View 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")}&nbsp;&nbsp;&nbsp;&nbsp;
<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
View 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")}&nbsp;&nbsp;&nbsp;&nbsp;
<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;

View File

@@ -117,7 +117,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 +127,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 +155,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 +187,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 +197,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} >

View File

@@ -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"))} :

View File

@@ -206,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"}}>

View File

@@ -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"))} :

View File

@@ -70,10 +70,13 @@ 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"));
}
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 +87,13 @@ 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"));
}
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"));
} }
@@ -311,7 +317,7 @@ class ProviderEditPage 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} >
@@ -321,7 +327,7 @@ class ProviderEditPage 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} >

View File

@@ -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>
) )

View File

@@ -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 = "";
@@ -74,6 +73,10 @@ export const OtherProviderInfo = {
logo: `${StaticBaseUrl}/img/social_tencent_cloud.jpg`, logo: `${StaticBaseUrl}/img/social_tencent_cloud.jpg`,
url: "https://cloud.tencent.com/product/cos", 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: { SAML: {
"Aliyun IDaaS": { "Aliyun IDaaS": {
@@ -221,7 +224,7 @@ export function isValidInvoiceTitle(invoiceTitle) {
} }
// https://blog.css8.cn/post/14210975.html // https://blog.css8.cn/post/14210975.html
const invoiceTitleRegex = /^[\(\)\\\u4e00-\u9fa5]{0,50}$/; const invoiceTitleRegex = /^[()\u4e00-\u9fa5]{0,50}$/;
return invoiceTitleRegex.test(invoiceTitle); return invoiceTitleRegex.test(invoiceTitle);
} }
@@ -470,27 +473,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) {
@@ -550,7 +552,9 @@ 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: 'Okta', name: 'Okta'},
{id: 'Douyin', name: 'Douyin'},
{id: 'Custom', name: 'Custom'}, {id: 'Custom', name: 'Custom'},
] ]
); );
@@ -576,6 +580,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") {
@@ -830,7 +835,7 @@ export function getSyncerTableColumns(syncer) {
] ]
}, },
{ {
"name":"USERNAME", "name":"LAST_NAME+FIRST_NAME",
"type":"string", "type":"string",
"casdoorName":"DisplayName", "casdoorName":"DisplayName",
"isHashed":true, "isHashed":true,

View File

@@ -176,7 +176,7 @@ class SignupTable extends React.Component {
} else if (record.name === "Email") { } else if (record.name === "Email") {
options = [ options = [
{id: 'Normal', name: 'Normal'}, {id: 'Normal', name: 'Normal'},
{id: 'No Verification', name: 'No verification'}, {id: 'No verification', name: 'No verification'},
]; ];
} }

View File

@@ -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']

View File

@@ -28,10 +28,10 @@ import OAuthWidget from "./common/OAuthWidget";
import SamlWidget from "./common/SamlWidget"; import SamlWidget from "./common/SamlWidget";
import SelectRegionBox from "./SelectRegionBox"; import SelectRegionBox from "./SelectRegionBox";
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/javascript/javascript"); // require("codemirror/mode/javascript/javascript");
const { Option } = Select; const { Option } = Select;

View File

@@ -0,0 +1,32 @@
// 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 {createButton} from "react-social-login-buttons";
import {StaticBaseUrl} from "../Setting";
function Icon({ width = 24, height = 24, color }) {
return <img src={`${StaticBaseUrl}/buttons/bilibili.svg`} alt="Sign in with Bilibili"/>;
}
const config = {
text: "Sign in with Bilibili",
icon: Icon,
iconFormat: name => `fa fa-${name}`,
style: {background: "#0191e0"},
activeStyle: {background: "rgb(76,143,208)"},
};
const BilibiliLoginButton = createButton(config);
export default BilibiliLoginButton;

View File

@@ -0,0 +1,32 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {createButton} from "react-social-login-buttons";
import {StaticBaseUrl} from "../Setting";
function Icon({width = 24, height = 24, color}) {
return <img src={`${StaticBaseUrl}/buttons/douyin.svg`} alt="Sign in with Douyin" style={{width: 24, height: 24}}/>;
}
const config = {
text: "Sign in with Douyin",
icon: Icon,
iconFormat: name => `fa fa-${name}`,
style: {background: "#ffffff", color: "#000000"},
activeStyle: {background: "#ededee"},
};
const DouyinLoginButton = createButton(config);
export default DouyinLoginButton;

View File

@@ -44,8 +44,12 @@ import AzureADLoginButton from "./AzureADLoginButton";
import SlackLoginButton from "./SlackLoginButton"; import SlackLoginButton from "./SlackLoginButton";
import SteamLoginButton from "./SteamLoginButton"; import SteamLoginButton from "./SteamLoginButton";
import OktaLoginButton from "./OktaLoginButton"; import OktaLoginButton from "./OktaLoginButton";
import DouyinLoginButton from "./DouyinLoginButton";
import CustomGithubCorner from "../CustomGithubCorner"; import CustomGithubCorner from "../CustomGithubCorner";
import {CountDownInput} from "../common/CountDownInput"; import {CountDownInput} from "../common/CountDownInput";
import BilibiliLoginButton from "./BilibiliLoginButton";
/* eslint-disable jsx-a11y/anchor-is-valid */
class LoginPage extends React.Component { class LoginPage extends React.Component {
constructor(props) { constructor(props) {
@@ -142,47 +146,48 @@ class LoginPage extends React.Component {
const application = this.getApplicationObj(); const application = this.getApplicationObj();
const ths = this; const ths = this;
//here we are supposed to judge whether casdoor is working as a oauth server or CAS server // here we are supposed to determine whether Casdoor is working as an OAuth server or CAS server
if (this.state.type === "cas") { if (this.state.type === "cas") {
//cas // CAS
const casParams = Util.getCasParameters() const casParams = Util.getCasParameters();
values["type"] = this.state.type; values["type"] = this.state.type;
AuthBackend.loginCas(values, casParams).then((res) => { AuthBackend.loginCas(values, casParams).then((res) => {
if (res.status === 'ok') { if (res.status === 'ok') {
let msg = "Logged in successfully. " let msg = "Logged in successfully. ";
if (casParams.service === "") { if (casParams.service === "") {
//If service was not specified, CAS MUST display a message notifying the client that it has successfully initiated a single sign-on session. // If service was not specified, Casdoor must display a message notifying the client that it has successfully initiated a single sign-on session.
msg += "Now you can visit apps protected by casdoor." msg += "Now you can visit apps protected by Casdoor.";
} }
Util.showMessage("success", msg); Util.showMessage("success", msg);
if (casParams.service !== "") {
let st = res.data
window.location.href = casParams.service + "?ticket=" + st
}
if (casParams.service !== "") {
let st = res.data;
let newUrl = new URL(casParams.service);
newUrl.searchParams.append("ticket", st);
window.location.href = newUrl.toString();
}
} else { } else {
Util.showMessage("error", `Failed to log in: ${res.msg}`); Util.showMessage("error", `Failed to log in: ${res.msg}`);
} }
}) })
} else { } else {
//oauth // OAuth
const oAuthParams = Util.getOAuthGetParameters(); const oAuthParams = Util.getOAuthGetParameters();
if (oAuthParams !== null && oAuthParams.responseType != null && oAuthParams.responseType !== "") { if (oAuthParams !== null && oAuthParams.responseType != null && oAuthParams.responseType !== "") {
values["type"] = oAuthParams.responseType values["type"] = oAuthParams.responseType;
}else{ } else {
values["type"] = this.state.type; values["type"] = this.state.type;
} }
values["phonePrefix"] = this.getApplicationObj()?.organizationObj.phonePrefix; values["phonePrefix"] = this.getApplicationObj()?.organizationObj.phonePrefix;
if (oAuthParams !== null){ if (oAuthParams !== null) {
values["samlRequest"] = oAuthParams.samlRequest; values["samlRequest"] = oAuthParams.samlRequest;
} }
if (values["samlRequest"] != null && values["samlRequest"] !== "") { if (values["samlRequest"] != null && values["samlRequest"] !== "") {
values["type"] = "saml"; values["type"] = "saml";
} }
AuthBackend.login(values, oAuthParams) AuthBackend.login(values, oAuthParams)
.then((res) => { .then((res) => {
if (res.status === 'ok') { if (res.status === 'ok') {
@@ -279,8 +284,12 @@ class LoginPage extends React.Component {
return <SlackLoginButton text={text} align={"center"} /> return <SlackLoginButton text={text} align={"center"} />
} else if (type === "Steam") { } else if (type === "Steam") {
return <SteamLoginButton text={text} align={"center"} /> return <SteamLoginButton text={text} align={"center"} />
} else if (type === "Bilibili") {
return <BilibiliLoginButton text={text} align={"center"} />
} else if (type === "Okta") { } else if (type === "Okta") {
return <OktaLoginButton text={text} align={"center"} /> return <OktaLoginButton text={text} align={"center"} />
} else if (type === "Douyin") {
return <DouyinLoginButton text={text} align={"center"} />
} }
return text; return text;

View File

@@ -111,9 +111,16 @@ const authInfo = {
scope: "openid%20profile%20email", scope: "openid%20profile%20email",
endpoint: "http://example.com", endpoint: "http://example.com",
}, },
Douyin: {
scope: "user_info",
endpoint: "https://open.douyin.com/platform/oauth/connect",
},
Custom: { Custom: {
endpoint: "https://example.com/", endpoint: "https://example.com/",
}, },
Bilibili: {
endpoint: "https://passport.bilibili.com/register/pc_oauth2.html"
}
}; };
export function getProviderUrl(provider) { export function getProviderUrl(provider) {
@@ -236,7 +243,11 @@ export function getAuthUrl(application, provider, method) {
return `${endpoint}?openid.claimed_id=http://specs.openid.net/auth/2.0/identifier_select&openid.identity=http://specs.openid.net/auth/2.0/identifier_select&openid.mode=checkid_setup&openid.ns=http://specs.openid.net/auth/2.0&openid.realm=${window.location.origin}&openid.return_to=${redirectUri}?state=${state}`; return `${endpoint}?openid.claimed_id=http://specs.openid.net/auth/2.0/identifier_select&openid.identity=http://specs.openid.net/auth/2.0/identifier_select&openid.mode=checkid_setup&openid.ns=http://specs.openid.net/auth/2.0&openid.realm=${window.location.origin}&openid.return_to=${redirectUri}?state=${state}`;
} else if (provider.type === "Okta") { } else if (provider.type === "Okta") {
return `${provider.domain}/v1/authorize?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`; return `${provider.domain}/v1/authorize?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
} else if (provider.type === "Douyin") {
return `${endpoint}?client_key=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
} else if (provider.type === "Custom") { } else if (provider.type === "Custom") {
return `${provider.customAuthUrl}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${provider.customScope}&response_type=code&state=${state}`; return `${provider.customAuthUrl}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${provider.customScope}&response_type=code&state=${state}`;
} } else if (provider.type === "Bilibili") {
return `${endpoint}#/?client_id=${provider.clientId}&return_url=${redirectUri}&state=${state}&response_type=code`
}
} }

View File

@@ -25,6 +25,8 @@ import {CountDownInput} from "../common/CountDownInput";
import SelectRegionBox from "../SelectRegionBox"; import SelectRegionBox from "../SelectRegionBox";
import CustomGithubCorner from "../CustomGithubCorner"; import CustomGithubCorner from "../CustomGithubCorner";
/* eslint-disable jsx-a11y/anchor-is-valid */
const formItemLayout = { const formItemLayout = {
labelCol: { labelCol: {
xs: { xs: {

View File

@@ -69,3 +69,10 @@ export function deleteApplication(application) {
body: JSON.stringify(newApplication), body: JSON.stringify(newApplication),
}).then(res => res.json()); }).then(res => res.json());
} }
export function getSamlMetadata(owner, name) {
return fetch(`${Setting.ServerUrl}/api/saml/metadata?application=${owner}/${encodeURIComponent(name)}`, {
method: "GET",
credentials: "include"
}).then(res => res.text());
}

View File

@@ -0,0 +1,56 @@
// 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 * as Setting from "../Setting";
export function getModels(owner, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
return fetch(`${Setting.ServerUrl}/api/get-models?owner=${owner}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, {
method: "GET",
credentials: "include"
}).then(res => res.json());
}
export function getModel(owner, name) {
return fetch(`${Setting.ServerUrl}/api/get-model?id=${owner}/${encodeURIComponent(name)}`, {
method: "GET",
credentials: "include"
}).then(res => res.json());
}
export function updateModel(owner, name, model) {
let newModel = Setting.deepCopy(model);
return fetch(`${Setting.ServerUrl}/api/update-model?id=${owner}/${encodeURIComponent(name)}`, {
method: 'POST',
credentials: 'include',
body: JSON.stringify(newModel),
}).then(res => res.json());
}
export function addModel(model) {
let newModel = Setting.deepCopy(model);
return fetch(`${Setting.ServerUrl}/api/add-model`, {
method: 'POST',
credentials: 'include',
body: JSON.stringify(newModel),
}).then(res => res.json());
}
export function deleteModel(model) {
let newModel = Setting.deepCopy(model);
return fetch(`${Setting.ServerUrl}/api/delete-model`, {
method: 'POST',
credentials: 'include',
body: JSON.stringify(newModel),
}).then(res => res.json());
}

View File

@@ -92,7 +92,7 @@ class HomePage extends React.Component {
) )
} else { } else {
return ( return (
<div style={{marginRight:'15px',marginLeft:'15px'}}> <div style={{marginRight: "15px", marginLeft: "15px"}}>
<Row style={{marginLeft: "-20px", marginRight: "-20px", marginTop: "20px"}} gutter={24}> <Row style={{marginLeft: "-20px", marginRight: "-20px", marginTop: "20px"}} gutter={24}>
{ {
items.map(item => { items.map(item => {

View File

@@ -142,7 +142,7 @@ class OAuthWidget extends React.Component {
</span> </span>
</Col> </Col>
<Col span={24 - this.props.labelSpan} > <Col span={24 - this.props.labelSpan} >
<img style={{marginRight: '10px'}} width={30} height={30} src={avatarUrl} alt={name} /> <img style={{marginRight: '10px'}} width={30} height={30} src={avatarUrl} alt={name} referrerPolicy="no-referrer" />
<span style={{width: this.props.labelSpan === 3 ? '300px' : '130px', display: (Setting.isMobile()) ? 'inline' : "inline-block"}}> <span style={{width: this.props.labelSpan === 3 ? '300px' : '130px', display: (Setting.isMobile()) ? 'inline' : "inline-block"}}>
{ {
linkedValue === "" ? ( linkedValue === "" ? (

View File

@@ -6,6 +6,9 @@
"Sign Up": "Registrieren" "Sign Up": "Registrieren"
}, },
"application": { "application": {
"Copy prompt page URL": "Copy prompt page URL",
"Copy signin page URL": "Copy signin page URL",
"Copy signup page URL": "Copy signup page URL",
"Edit Application": "Anwendung bearbeiten", "Edit Application": "Anwendung bearbeiten",
"Enable code signin": "Code-Anmeldung aktivieren", "Enable code signin": "Code-Anmeldung aktivieren",
"Enable code signin - Tooltip": "Aktiviere Codeanmeldung - Tooltip", "Enable code signin - Tooltip": "Aktiviere Codeanmeldung - Tooltip",
@@ -19,17 +22,19 @@
"Password ON": "Passwort AN", "Password ON": "Passwort AN",
"Password ON - Tooltip": "Whether to allow password login", "Password ON - Tooltip": "Whether to allow password login",
"Please select a HTML file": "Bitte wählen Sie eine HTML-Datei", "Please select a HTML file": "Bitte wählen Sie eine HTML-Datei",
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
"Redirect URL": "Weiterleitungs-URL", "Redirect URL": "Weiterleitungs-URL",
"Redirect URLs": "Umleitungs-URLs", "Redirect URLs": "Umleitungs-URLs",
"Redirect URLs - Tooltip": "List of redirect addresses after successful login", "Redirect URLs - Tooltip": "List of redirect addresses after successful login",
"Refresh token expire": "Aktualisierungs-Token läuft ab", "Refresh token expire": "Aktualisierungs-Token läuft ab",
"Refresh token expire - Tooltip": "Aktualisierungs-Token läuft ab - Tooltip", "Refresh token expire - Tooltip": "Aktualisierungs-Token läuft ab - Tooltip",
"SAML metadata": "SAML metadata",
"SAML metadata - Tooltip": "SAML metadata - Tooltip",
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
"Signin session": "Anmeldesitzung", "Signin session": "Anmeldesitzung",
"Signup items": "Artikel registrieren", "Signup items": "Artikel registrieren",
"Signup items - Tooltip": "Signup items that need to be filled in when users register", "Signup items - Tooltip": "Signup items that need to be filled in when users register",
"Test prompt page..": "Test-Nachfrageseite..", "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
"Test signin page..": "Anmeldeseite testen..",
"Test signup page..": "Anmeldeseite testen..",
"Token expire": "Token läuft ab", "Token expire": "Token läuft ab",
"Token expire - Tooltip": "Token läuft ab - Tooltip", "Token expire - Tooltip": "Token läuft ab - Tooltip",
"Token format": "Token-Format", "Token format": "Token-Format",
@@ -99,6 +104,7 @@
"Cert": "Cert", "Cert": "Cert",
"Cert - Tooltip": "Cert - Tooltip", "Cert - Tooltip": "Cert - Tooltip",
"Certs": "Certs", "Certs": "Certs",
"Click to Upload": "Click to Upload",
"Client IP": "Client-IP", "Client IP": "Client-IP",
"Created time": "Erstellte Zeit", "Created time": "Erstellte Zeit",
"Default avatar": "Standard Avatar", "Default avatar": "Standard Avatar",
@@ -131,6 +137,9 @@
"Master password": "Master-Passwort", "Master password": "Master-Passwort",
"Master password - Tooltip": "Masterpasswort - Tooltip", "Master password - Tooltip": "Masterpasswort - Tooltip",
"Method": "Methode", "Method": "Methode",
"Model": "Model",
"Model - Tooltip": "Model - Tooltip",
"Models": "Models",
"Name": "Name", "Name": "Name",
"Name - Tooltip": "Unique string-style identifier", "Name - Tooltip": "Unique string-style identifier",
"OAuth providers": "OAuth-Anbieter", "OAuth providers": "OAuth-Anbieter",
@@ -243,6 +252,12 @@
"sign up now": "jetzt anmelden", "sign up now": "jetzt anmelden",
"username, Email or phone": "Benutzername, E-Mail oder Telefon" "username, Email or phone": "Benutzername, E-Mail oder Telefon"
}, },
"model": {
"Edit Model": "Edit Model",
"Model text": "Model text",
"Model text - Tooltip": "Model text - Tooltip",
"New Model": "New Model"
},
"organization": { "organization": {
"Default avatar": "Standard Avatar", "Default avatar": "Standard Avatar",
"Edit Organization": "Organisation bearbeiten", "Edit Organization": "Organisation bearbeiten",

View File

@@ -6,6 +6,9 @@
"Sign Up": "Sign Up" "Sign Up": "Sign Up"
}, },
"application": { "application": {
"Copy prompt page URL": "Copy prompt page URL",
"Copy signin page URL": "Copy signin page URL",
"Copy signup page URL": "Copy signup page URL",
"Edit Application": "Edit Application", "Edit Application": "Edit Application",
"Enable code signin": "Enable code signin", "Enable code signin": "Enable code signin",
"Enable code signin - Tooltip": "Enable code signin - Tooltip", "Enable code signin - Tooltip": "Enable code signin - Tooltip",
@@ -19,17 +22,19 @@
"Password ON": "Password ON", "Password ON": "Password ON",
"Password ON - Tooltip": "Password ON - Tooltip", "Password ON - Tooltip": "Password ON - Tooltip",
"Please select a HTML file": "Please select a HTML file", "Please select a HTML file": "Please select a HTML file",
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
"Redirect URL": "Redirect URL", "Redirect URL": "Redirect URL",
"Redirect URLs": "Redirect URLs", "Redirect URLs": "Redirect URLs",
"Redirect URLs - Tooltip": "Redirect URLs - Tooltip", "Redirect URLs - Tooltip": "Redirect URLs - Tooltip",
"Refresh token expire": "Refresh token expire", "Refresh token expire": "Refresh token expire",
"Refresh token expire - Tooltip": "Refresh token expire - Tooltip", "Refresh token expire - Tooltip": "Refresh token expire - Tooltip",
"SAML metadata": "SAML metadata",
"SAML metadata - Tooltip": "SAML metadata - Tooltip",
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
"Signin session": "Signin session", "Signin session": "Signin session",
"Signup items": "Signup items", "Signup items": "Signup items",
"Signup items - Tooltip": "Signup items - Tooltip", "Signup items - Tooltip": "Signup items - Tooltip",
"Test prompt page..": "Test prompt page..", "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
"Test signin page..": "Test signin page..",
"Test signup page..": "Test signup page..",
"Token expire": "Token expire", "Token expire": "Token expire",
"Token expire - Tooltip": "Token expire - Tooltip", "Token expire - Tooltip": "Token expire - Tooltip",
"Token format": "Token format", "Token format": "Token format",
@@ -99,6 +104,7 @@
"Cert": "Cert", "Cert": "Cert",
"Cert - Tooltip": "Cert - Tooltip", "Cert - Tooltip": "Cert - Tooltip",
"Certs": "Certs", "Certs": "Certs",
"Click to Upload": "Click to Upload",
"Client IP": "Client IP", "Client IP": "Client IP",
"Created time": "Created time", "Created time": "Created time",
"Default avatar": "Default avatar", "Default avatar": "Default avatar",
@@ -131,6 +137,9 @@
"Master password": "Master password", "Master password": "Master password",
"Master password - Tooltip": "Master password - Tooltip", "Master password - Tooltip": "Master password - Tooltip",
"Method": "Method", "Method": "Method",
"Model": "Model",
"Model - Tooltip": "Model - Tooltip",
"Models": "Models",
"Name": "Name", "Name": "Name",
"Name - Tooltip": "Name - Tooltip", "Name - Tooltip": "Name - Tooltip",
"OAuth providers": "OAuth providers", "OAuth providers": "OAuth providers",
@@ -243,6 +252,12 @@
"sign up now": "sign up now", "sign up now": "sign up now",
"username, Email or phone": "username, Email or phone" "username, Email or phone": "username, Email or phone"
}, },
"model": {
"Edit Model": "Edit Model",
"Model text": "Model text",
"Model text - Tooltip": "Model text - Tooltip",
"New Model": "New Model"
},
"organization": { "organization": {
"Default avatar": "Default avatar", "Default avatar": "Default avatar",
"Edit Organization": "Edit Organization", "Edit Organization": "Edit Organization",

View File

@@ -6,6 +6,9 @@
"Sign Up": "S'inscrire" "Sign Up": "S'inscrire"
}, },
"application": { "application": {
"Copy prompt page URL": "Copy prompt page URL",
"Copy signin page URL": "Copy signin page URL",
"Copy signup page URL": "Copy signup page URL",
"Edit Application": "Modifier l'application", "Edit Application": "Modifier l'application",
"Enable code signin": "Activer la connexion au code", "Enable code signin": "Activer la connexion au code",
"Enable code signin - Tooltip": "Activer la connexion au code - infobulle", "Enable code signin - Tooltip": "Activer la connexion au code - infobulle",
@@ -19,17 +22,19 @@
"Password ON": "Mot de passe activé", "Password ON": "Mot de passe activé",
"Password ON - Tooltip": "Whether to allow password login", "Password ON - Tooltip": "Whether to allow password login",
"Please select a HTML file": "Veuillez sélectionner un fichier HTML", "Please select a HTML file": "Veuillez sélectionner un fichier HTML",
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
"Redirect URL": "URL de redirection", "Redirect URL": "URL de redirection",
"Redirect URLs": "URL de redirection", "Redirect URLs": "URL de redirection",
"Redirect URLs - Tooltip": "List of redirect addresses after successful login", "Redirect URLs - Tooltip": "List of redirect addresses after successful login",
"Refresh token expire": "Expiration du jeton d'actualisation", "Refresh token expire": "Expiration du jeton d'actualisation",
"Refresh token expire - Tooltip": "Expiration du jeton d'actualisation - infobulle", "Refresh token expire - Tooltip": "Expiration du jeton d'actualisation - infobulle",
"SAML metadata": "SAML metadata",
"SAML metadata - Tooltip": "SAML metadata - Tooltip",
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
"Signin session": "Connexion à la session", "Signin session": "Connexion à la session",
"Signup items": "Inscrire des éléments", "Signup items": "Inscrire des éléments",
"Signup items - Tooltip": "Signup items that need to be filled in when users register", "Signup items - Tooltip": "Signup items that need to be filled in when users register",
"Test prompt page..": "Tester la vitesse d'exécution.", "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
"Test signin page..": "Tester la connexion en ligne.",
"Test signup page..": "Tester l'inscription.",
"Token expire": "Expiration du jeton", "Token expire": "Expiration du jeton",
"Token expire - Tooltip": "Expiration du jeton - Info-bulle", "Token expire - Tooltip": "Expiration du jeton - Info-bulle",
"Token format": "Format du jeton", "Token format": "Format du jeton",
@@ -99,6 +104,7 @@
"Cert": "Cert", "Cert": "Cert",
"Cert - Tooltip": "Cert - Tooltip", "Cert - Tooltip": "Cert - Tooltip",
"Certs": "Certes", "Certs": "Certes",
"Click to Upload": "Click to Upload",
"Client IP": "IP du client", "Client IP": "IP du client",
"Created time": "Date de création", "Created time": "Date de création",
"Default avatar": "Avatar par défaut", "Default avatar": "Avatar par défaut",
@@ -131,6 +137,9 @@
"Master password": "Mot de passe maître", "Master password": "Mot de passe maître",
"Master password - Tooltip": "Mot de passe maître - Infobulle", "Master password - Tooltip": "Mot de passe maître - Infobulle",
"Method": "Méthode", "Method": "Méthode",
"Model": "Model",
"Model - Tooltip": "Model - Tooltip",
"Models": "Models",
"Name": "Nom", "Name": "Nom",
"Name - Tooltip": "Unique string-style identifier", "Name - Tooltip": "Unique string-style identifier",
"OAuth providers": "Fournisseurs OAuth", "OAuth providers": "Fournisseurs OAuth",
@@ -243,6 +252,12 @@
"sign up now": "inscrivez-vous maintenant", "sign up now": "inscrivez-vous maintenant",
"username, Email or phone": "nom d'utilisateur, e-mail ou téléphone" "username, Email or phone": "nom d'utilisateur, e-mail ou téléphone"
}, },
"model": {
"Edit Model": "Edit Model",
"Model text": "Model text",
"Model text - Tooltip": "Model text - Tooltip",
"New Model": "New Model"
},
"organization": { "organization": {
"Default avatar": "Avatar par défaut", "Default avatar": "Avatar par défaut",
"Edit Organization": "Modifier l'organisation", "Edit Organization": "Modifier l'organisation",

View File

@@ -6,6 +6,9 @@
"Sign Up": "新規登録" "Sign Up": "新規登録"
}, },
"application": { "application": {
"Copy prompt page URL": "Copy prompt page URL",
"Copy signin page URL": "Copy signin page URL",
"Copy signup page URL": "Copy signup page URL",
"Edit Application": "アプリケーションを編集", "Edit Application": "アプリケーションを編集",
"Enable code signin": "コードサインインを有効にする", "Enable code signin": "コードサインインを有効にする",
"Enable code signin - Tooltip": "Enable code signin - Tooltip", "Enable code signin - Tooltip": "Enable code signin - Tooltip",
@@ -19,17 +22,19 @@
"Password ON": "パスワードON", "Password ON": "パスワードON",
"Password ON - Tooltip": "Whether to allow password login", "Password ON - Tooltip": "Whether to allow password login",
"Please select a HTML file": "HTMLファイルを選択してください", "Please select a HTML file": "HTMLファイルを選択してください",
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
"Redirect URL": "リダイレクトURL", "Redirect URL": "リダイレクトURL",
"Redirect URLs": "リダイレクトURL", "Redirect URLs": "リダイレクトURL",
"Redirect URLs - Tooltip": "List of redirect addresses after successful login", "Redirect URLs - Tooltip": "List of redirect addresses after successful login",
"Refresh token expire": "トークンの更新の期限が切れます", "Refresh token expire": "トークンの更新の期限が切れます",
"Refresh token expire - Tooltip": "トークンの有効期限を更新する - ツールチップ", "Refresh token expire - Tooltip": "トークンの有効期限を更新する - ツールチップ",
"SAML metadata": "SAML metadata",
"SAML metadata - Tooltip": "SAML metadata - Tooltip",
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
"Signin session": "サインインセッション", "Signin session": "サインインセッション",
"Signup items": "アイテムの登録", "Signup items": "アイテムの登録",
"Signup items - Tooltip": "Signup items that need to be filled in when users register", "Signup items - Tooltip": "Signup items that need to be filled in when users register",
"Test prompt page..": "テストプロンプトページ...", "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
"Test signin page..": "サインインテストページ...",
"Test signup page..": "登録ページのテスト",
"Token expire": "トークンの有効期限", "Token expire": "トークンの有効期限",
"Token expire - Tooltip": "トークンの有効期限 - ツールチップ", "Token expire - Tooltip": "トークンの有効期限 - ツールチップ",
"Token format": "トークンのフォーマット", "Token format": "トークンのフォーマット",
@@ -99,6 +104,7 @@
"Cert": "Cert", "Cert": "Cert",
"Cert - Tooltip": "Cert - Tooltip", "Cert - Tooltip": "Cert - Tooltip",
"Certs": "Certs", "Certs": "Certs",
"Click to Upload": "Click to Upload",
"Client IP": "クライアント IP", "Client IP": "クライアント IP",
"Created time": "作成日時", "Created time": "作成日時",
"Default avatar": "デフォルトのアバター", "Default avatar": "デフォルトのアバター",
@@ -131,6 +137,9 @@
"Master password": "マスターパスワード", "Master password": "マスターパスワード",
"Master password - Tooltip": "マスターパスワード - ツールチップ", "Master password - Tooltip": "マスターパスワード - ツールチップ",
"Method": "方法", "Method": "方法",
"Model": "Model",
"Model - Tooltip": "Model - Tooltip",
"Models": "Models",
"Name": "名前", "Name": "名前",
"Name - Tooltip": "Unique string-style identifier", "Name - Tooltip": "Unique string-style identifier",
"OAuth providers": "OAuthプロバイダー", "OAuth providers": "OAuthプロバイダー",
@@ -243,6 +252,12 @@
"sign up now": "今すぐサインアップ", "sign up now": "今すぐサインアップ",
"username, Email or phone": "ユーザー名、メールアドレスまたは電話番号" "username, Email or phone": "ユーザー名、メールアドレスまたは電話番号"
}, },
"model": {
"Edit Model": "Edit Model",
"Model text": "Model text",
"Model text - Tooltip": "Model text - Tooltip",
"New Model": "New Model"
},
"organization": { "organization": {
"Default avatar": "デフォルトのアバター", "Default avatar": "デフォルトのアバター",
"Edit Organization": "組織を編集", "Edit Organization": "組織を編集",

View File

@@ -6,6 +6,9 @@
"Sign Up": "Sign Up" "Sign Up": "Sign Up"
}, },
"application": { "application": {
"Copy prompt page URL": "Copy prompt page URL",
"Copy signin page URL": "Copy signin page URL",
"Copy signup page URL": "Copy signup page URL",
"Edit Application": "Edit Application", "Edit Application": "Edit Application",
"Enable code signin": "Enable code signin", "Enable code signin": "Enable code signin",
"Enable code signin - Tooltip": "Enable code signin - Tooltip", "Enable code signin - Tooltip": "Enable code signin - Tooltip",
@@ -19,17 +22,19 @@
"Password ON": "Password ON", "Password ON": "Password ON",
"Password ON - Tooltip": "Whether to allow password login", "Password ON - Tooltip": "Whether to allow password login",
"Please select a HTML file": "Please select a HTML file", "Please select a HTML file": "Please select a HTML file",
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
"Redirect URL": "Redirect URL", "Redirect URL": "Redirect URL",
"Redirect URLs": "Redirect URLs", "Redirect URLs": "Redirect URLs",
"Redirect URLs - Tooltip": "List of redirect addresses after successful login", "Redirect URLs - Tooltip": "List of redirect addresses after successful login",
"Refresh token expire": "Refresh token expire", "Refresh token expire": "Refresh token expire",
"Refresh token expire - Tooltip": "Refresh token expire - Tooltip", "Refresh token expire - Tooltip": "Refresh token expire - Tooltip",
"SAML metadata": "SAML metadata",
"SAML metadata - Tooltip": "SAML metadata - Tooltip",
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
"Signin session": "Signin session", "Signin session": "Signin session",
"Signup items": "Signup items", "Signup items": "Signup items",
"Signup items - Tooltip": "Signup items that need to be filled in when users register", "Signup items - Tooltip": "Signup items that need to be filled in when users register",
"Test prompt page..": "Test prompt page..", "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
"Test signin page..": "Test signin page..",
"Test signup page..": "Test signup page..",
"Token expire": "Token expire", "Token expire": "Token expire",
"Token expire - Tooltip": "Token expire - Tooltip", "Token expire - Tooltip": "Token expire - Tooltip",
"Token format": "Token format", "Token format": "Token format",
@@ -99,6 +104,7 @@
"Cert": "Cert", "Cert": "Cert",
"Cert - Tooltip": "Cert - Tooltip", "Cert - Tooltip": "Cert - Tooltip",
"Certs": "Certs", "Certs": "Certs",
"Click to Upload": "Click to Upload",
"Client IP": "Client IP", "Client IP": "Client IP",
"Created time": "Created time", "Created time": "Created time",
"Default avatar": "Default avatar", "Default avatar": "Default avatar",
@@ -131,6 +137,9 @@
"Master password": "Master password", "Master password": "Master password",
"Master password - Tooltip": "Master password - Tooltip", "Master password - Tooltip": "Master password - Tooltip",
"Method": "Method", "Method": "Method",
"Model": "Model",
"Model - Tooltip": "Model - Tooltip",
"Models": "Models",
"Name": "Name", "Name": "Name",
"Name - Tooltip": "Unique string-style identifier", "Name - Tooltip": "Unique string-style identifier",
"OAuth providers": "OAuth providers", "OAuth providers": "OAuth providers",
@@ -243,6 +252,12 @@
"sign up now": "sign up now", "sign up now": "sign up now",
"username, Email or phone": "username, Email or phone" "username, Email or phone": "username, Email or phone"
}, },
"model": {
"Edit Model": "Edit Model",
"Model text": "Model text",
"Model text - Tooltip": "Model text - Tooltip",
"New Model": "New Model"
},
"organization": { "organization": {
"Default avatar": "Default avatar", "Default avatar": "Default avatar",
"Edit Organization": "Edit Organization", "Edit Organization": "Edit Organization",

View File

@@ -6,6 +6,9 @@
"Sign Up": "Регистрация" "Sign Up": "Регистрация"
}, },
"application": { "application": {
"Copy prompt page URL": "Copy prompt page URL",
"Copy signin page URL": "Copy signin page URL",
"Copy signup page URL": "Copy signup page URL",
"Edit Application": "Изменить приложение", "Edit Application": "Изменить приложение",
"Enable code signin": "Включить кодовый вход", "Enable code signin": "Включить кодовый вход",
"Enable code signin - Tooltip": "Включить вход с кодом - Tooltip", "Enable code signin - Tooltip": "Включить вход с кодом - Tooltip",
@@ -19,17 +22,19 @@
"Password ON": "Пароль ВКЛ", "Password ON": "Пароль ВКЛ",
"Password ON - Tooltip": "Whether to allow password login", "Password ON - Tooltip": "Whether to allow password login",
"Please select a HTML file": "Пожалуйста, выберите HTML-файл", "Please select a HTML file": "Пожалуйста, выберите HTML-файл",
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
"Redirect URL": "URL перенаправления", "Redirect URL": "URL перенаправления",
"Redirect URLs": "Перенаправление URL", "Redirect URLs": "Перенаправление URL",
"Redirect URLs - Tooltip": "List of redirect addresses after successful login", "Redirect URLs - Tooltip": "List of redirect addresses after successful login",
"Refresh token expire": "Срок действия обновления токена истекает", "Refresh token expire": "Срок действия обновления токена истекает",
"Refresh token expire - Tooltip": "Срок обновления токена истекает - Подсказка", "Refresh token expire - Tooltip": "Срок обновления токена истекает - Подсказка",
"SAML metadata": "SAML metadata",
"SAML metadata - Tooltip": "SAML metadata - Tooltip",
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
"Signin session": "Сессия входа", "Signin session": "Сессия входа",
"Signup items": "Элементы регистрации", "Signup items": "Элементы регистрации",
"Signup items - Tooltip": "Signup items that need to be filled in when users register", "Signup items - Tooltip": "Signup items that need to be filled in when users register",
"Test prompt page..": "Тестовая страница запроса..", "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
"Test signin page..": "Тестовая страница входа..",
"Test signup page..": "Тестовая страница регистрации..",
"Token expire": "Токен истекает", "Token expire": "Токен истекает",
"Token expire - Tooltip": "Истек токен - Подсказка", "Token expire - Tooltip": "Истек токен - Подсказка",
"Token format": "Формат токена", "Token format": "Формат токена",
@@ -99,6 +104,7 @@
"Cert": "Cert", "Cert": "Cert",
"Cert - Tooltip": "Cert - Tooltip", "Cert - Tooltip": "Cert - Tooltip",
"Certs": "Сертификаты", "Certs": "Сертификаты",
"Click to Upload": "Click to Upload",
"Client IP": "IP клиента", "Client IP": "IP клиента",
"Created time": "Время создания", "Created time": "Время создания",
"Default avatar": "Аватар по умолчанию", "Default avatar": "Аватар по умолчанию",
@@ -131,6 +137,9 @@
"Master password": "Мастер-пароль", "Master password": "Мастер-пароль",
"Master password - Tooltip": "Мастер-пароль - Tooltip", "Master password - Tooltip": "Мастер-пароль - Tooltip",
"Method": "Метод", "Method": "Метод",
"Model": "Model",
"Model - Tooltip": "Model - Tooltip",
"Models": "Models",
"Name": "Наименование", "Name": "Наименование",
"Name - Tooltip": "Unique string-style identifier", "Name - Tooltip": "Unique string-style identifier",
"OAuth providers": "Поставщики OAuth", "OAuth providers": "Поставщики OAuth",
@@ -243,6 +252,12 @@
"sign up now": "зарегистрироваться", "sign up now": "зарегистрироваться",
"username, Email or phone": "имя пользователя, адрес электронной почты или телефон" "username, Email or phone": "имя пользователя, адрес электронной почты или телефон"
}, },
"model": {
"Edit Model": "Edit Model",
"Model text": "Model text",
"Model text - Tooltip": "Model text - Tooltip",
"New Model": "New Model"
},
"organization": { "organization": {
"Default avatar": "Аватар по умолчанию", "Default avatar": "Аватар по умолчанию",
"Edit Organization": "Изменить организацию", "Edit Organization": "Изменить организацию",

View File

@@ -6,6 +6,9 @@
"Sign Up": "注册" "Sign Up": "注册"
}, },
"application": { "application": {
"Copy prompt page URL": "复制提醒页面URL",
"Copy signin page URL": "复制登录页面URL",
"Copy signup page URL": "复制注册页面URL",
"Edit Application": "编辑应用", "Edit Application": "编辑应用",
"Enable code signin": "启用验证码登录", "Enable code signin": "启用验证码登录",
"Enable code signin - Tooltip": "是否允许用手机或邮箱验证码登录", "Enable code signin - Tooltip": "是否允许用手机或邮箱验证码登录",
@@ -19,18 +22,20 @@
"Password ON": "开启密码", "Password ON": "开启密码",
"Password ON - Tooltip": "是否允许密码登录", "Password ON - Tooltip": "是否允许密码登录",
"Please select a HTML file": "请选择一个HTML文件", "Please select a HTML file": "请选择一个HTML文件",
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "提醒页面URL已成功复制到剪贴板请粘贴到当前浏览器的隐身模式窗口或另一个浏览器访问",
"Redirect URL": "重定向 URL", "Redirect URL": "重定向 URL",
"Redirect URLs": "重定向 URLs", "Redirect URLs": "重定向 URLs",
"Redirect URLs - Tooltip": "登录成功后重定向地址列表", "Redirect URLs - Tooltip": "登录成功后重定向地址列表",
"Refresh token expire": "Refresh Token过期时间", "Refresh token expire": "Refresh Token过期",
"Refresh token expire - Tooltip": "Refresh Token过期时间", "Refresh token expire - Tooltip": "Refresh Token过期时间",
"SAML metadata": "SAML元数据",
"SAML metadata - Tooltip": "SAML协议的元数据Metadata信息",
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "登录页面URL已成功复制到剪贴板请粘贴到当前浏览器的隐身模式窗口或另一个浏览器访问",
"Signin session": "保持登录会话", "Signin session": "保持登录会话",
"Signup items": "注册项", "Signup items": "注册项",
"Signup items - Tooltip": "注册用户注册时需要填写的项目", "Signup items - Tooltip": "注册用户注册时需要填写的项目",
"Test prompt page..": "测试提醒页面..", "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "注册页面URL已成功复制到剪贴板请粘贴到当前浏览器的隐身模式窗口或另一个浏览器访问",
"Test signin page..": "测试登录页面..", "Token expire": "Access Token过期",
"Test signup page..": "测试注册页面..",
"Token expire": "Access Token过期时间",
"Token expire - Tooltip": "Access Token过期时间", "Token expire - Tooltip": "Access Token过期时间",
"Token format": "Access Token格式", "Token format": "Access Token格式",
"Token format - Tooltip": "Access Token格式" "Token format - Tooltip": "Access Token格式"
@@ -99,6 +104,7 @@
"Cert": "证书", "Cert": "证书",
"Cert - Tooltip": "该应用所对应的客户端SDK需要验证的公钥证书", "Cert - Tooltip": "该应用所对应的客户端SDK需要验证的公钥证书",
"Certs": "证书", "Certs": "证书",
"Click to Upload": "点击上传",
"Client IP": "客户端IP", "Client IP": "客户端IP",
"Created time": "创建时间", "Created time": "创建时间",
"Default avatar": "默认头像", "Default avatar": "默认头像",
@@ -131,6 +137,9 @@
"Master password": "万能密码", "Master password": "万能密码",
"Master password - Tooltip": "可用来登录该组织下的所有用户,方便管理员以该用户身份登录,以解决技术问题", "Master password - Tooltip": "可用来登录该组织下的所有用户,方便管理员以该用户身份登录,以解决技术问题",
"Method": "方法", "Method": "方法",
"Model": "模型",
"Model - Tooltip": "Casbin模型",
"Models": "模型",
"Name": "名称", "Name": "名称",
"Name - Tooltip": "唯一的、字符串式的ID", "Name - Tooltip": "唯一的、字符串式的ID",
"OAuth providers": "OAuth提供方", "OAuth providers": "OAuth提供方",
@@ -243,11 +252,17 @@
"sign up now": "立即注册", "sign up now": "立即注册",
"username, Email or phone": "用户名、Email或手机号" "username, Email or phone": "用户名、Email或手机号"
}, },
"model": {
"Edit Model": "编辑模型",
"Model text": "模型文本",
"Model text - Tooltip": "Casbin访问控制模型",
"New Model": "添加模型"
},
"organization": { "organization": {
"Default avatar": "默认头像", "Default avatar": "默认头像",
"Edit Organization": "编辑组织", "Edit Organization": "编辑组织",
"Favicon": "图标", "Favicon": "图标",
"Is profile public": "公开用户主页", "Is profile public": "用户个人页公开",
"Is profile public - Tooltip": "关闭后,只有全局管理员或同组织用户才能访问用户主页", "Is profile public - Tooltip": "关闭后,只有全局管理员或同组织用户才能访问用户主页",
"New Organization": "添加组织", "New Organization": "添加组织",
"Soft deletion": "软删除", "Soft deletion": "软删除",
@@ -399,7 +414,7 @@
"IdP public key": "IdP 公钥", "IdP public key": "IdP 公钥",
"Issuer URL": "发行者网址", "Issuer URL": "发行者网址",
"Issuer URL - Tooltip": "发行者URL - 工具提示", "Issuer URL - Tooltip": "发行者URL - 工具提示",
"Link copied to clipboard successfully": "链接复制到剪贴板成功", "Link copied to clipboard successfully": "链接已成功复制到剪贴板",
"Metadata": "元数据", "Metadata": "元数据",
"Metadata - Tooltip": "元数据 - 工具提示", "Metadata - Tooltip": "元数据 - 工具提示",
"Method": "方法", "Method": "方法",
@@ -430,16 +445,16 @@
"Sign Name - Tooltip": "签名名称", "Sign Name - Tooltip": "签名名称",
"Sign request": "签名请求", "Sign request": "签名请求",
"Sign request - Tooltip": "签名请求 - 工具提示", "Sign request - Tooltip": "签名请求 - 工具提示",
"Signin HTML": "登录 HTML", "Signin HTML": "登录页面HTML",
"Signin HTML - Edit": "登录 HTML - 编辑", "Signin HTML - Edit": "登录页面 - 编辑",
"Signin HTML - Tooltip": "登录 HTML - 工具提示", "Signin HTML - Tooltip": "自定义HTML,用于替换默认的登录页面样式",
"Signup HTML": "注册 HTML", "Signup HTML": "注册页面HTML",
"Signup HTML - Edit": "注册 HTML - 编辑", "Signup HTML - Edit": "注册页面HTML - 编辑",
"Signup HTML - Tooltip": "注册 HTML - 工具提示", "Signup HTML - Tooltip": "自定义HTML,用于替换默认的注册页面样式",
"Sub type": "子类型", "Sub type": "子类型",
"Sub type - Tooltip": "子类型", "Sub type - Tooltip": "子类型",
"Template Code": "模板CODE", "Template Code": "模板代码",
"Template Code - Tooltip": "模板CODE", "Template Code - Tooltip": "模板代码",
"Terms of Use": "使用条款", "Terms of Use": "使用条款",
"Terms of Use - Tooltip": "使用条款 - 工具提示", "Terms of Use - Tooltip": "使用条款 - 工具提示",
"Token URL": "Token URL", "Token URL": "Token URL",
@@ -449,13 +464,13 @@
"UserInfo URL": "UserInfo URL", "UserInfo URL": "UserInfo URL",
"UserInfo URL - Tooltip": "UserInfo URL - 工具提示", "UserInfo URL - Tooltip": "UserInfo URL - 工具提示",
"alertType": "警报类型", "alertType": "警报类型",
"canSignIn": "canSignIn", "canSignIn": "可用于登录",
"canSignUp": "canSignUp", "canSignUp": "可用于注册",
"canUnlink": "canUnlink", "canUnlink": "可解绑定",
"prompted": "提示", "prompted": "注册后提醒绑定",
"required": "必需", "required": "是否必填项",
"rule": "规则", "rule": "规则",
"visible": "可见" "visible": "是否可见"
}, },
"record": { "record": {
"Is Triggered": "已触发" "Is Triggered": "已触发"