mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-15 01:43:49 +08:00
Compare commits
16 Commits
Author | SHA1 | Date | |
---|---|---|---|
ff94e5164a | |||
15a6fd2b52 | |||
37b6b50751 | |||
efe5431f54 | |||
e9159902eb | |||
604e2757c8 | |||
88c5aae9e9 | |||
3d0cf8788b | |||
e78ea2546f | |||
f7705931f7 | |||
5d8b710bf7 | |||
b85ad896bf | |||
42c2210178 | |||
d52caed3a9 | |||
27d8cd758d | |||
98f77960de |
152
README.md
152
README.md
@ -42,166 +42,66 @@
|
|||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Online demo
|
## Online demo
|
||||||
|
|
||||||
Deployed site: https://door.casdoor.com/
|
- International: https://door.casdoor.org (read-only)
|
||||||
|
- Asian mirror: https://door.casdoor.com (read-only)
|
||||||
|
- Asian mirror: https://demo.casdoor.com (read-write, will restore for every 5 minutes)
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
Run your own casdoor program in a few minutes.
|
|
||||||
|
|
||||||
### Download
|
|
||||||
|
|
||||||
There are two methods, get code via go subcommand `get`:
|
## Documentation
|
||||||
|
|
||||||
```shell
|
- International: https://casdoor.org
|
||||||
go get github.com/casdoor/casdoor
|
- Asian mirror: https://docs.casdoor.cn
|
||||||
```
|
|
||||||
|
|
||||||
or `git`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/casdoor/casdoor
|
|
||||||
```
|
|
||||||
|
|
||||||
Finally, change directory:
|
## Install
|
||||||
|
|
||||||
```bash
|
- By source code: https://casdoor.org/docs/basic/server-installation
|
||||||
cd casdoor/
|
- By Docker: https://casdoor.org/docs/basic/try-with-docker
|
||||||
```
|
|
||||||
|
|
||||||
We provide two start up methods for all kinds of users.
|
|
||||||
|
|
||||||
### Manual
|
|
||||||
|
|
||||||
#### Simple configuration
|
## How to connect to Casdoor?
|
||||||
Casdoor requires a running Relational database to be operational.Thus you need to modify configuration to point out the location of database.
|
|
||||||
|
|
||||||
Edit `conf/app.conf`, modify `dataSourceName` to correct database info, which follows this format:
|
https://casdoor.org/docs/how-to-connect/overview
|
||||||
|
|
||||||
```bash
|
|
||||||
username:password@tcp(database_ip:database_port)/
|
|
||||||
```
|
|
||||||
|
|
||||||
Then create an empty schema (database) named `casdoor` in your relational database. After the program runs for the first time, it will automatically create tables in this schema.
|
|
||||||
|
|
||||||
You can also edit `main.go`, modify `false` to `true`. It will automatically create the schema (database) named `casdoor` in this database.
|
## Casdoor Public API
|
||||||
|
|
||||||
```bash
|
- Docs: https://casdoor.org/docs/basic/public-api
|
||||||
createDatabase := flag.Bool("createDatabase", false, "true if you need casdoor to create database")
|
- Swagger: https://door.casdoor.com/swagger
|
||||||
```
|
|
||||||
|
|
||||||
#### Run
|
|
||||||
|
|
||||||
Casdoor provides two run modes, the difference is binary size and user prompt.
|
|
||||||
|
|
||||||
##### Dev Mode
|
## Integrations
|
||||||
|
|
||||||
Edit `conf/app.conf`, set `runmode=dev`. Firstly build front-end files:
|
https://casdoor.org/docs/integration/apisix
|
||||||
|
|
||||||
```bash
|
|
||||||
cd web/ && yarn && yarn run start
|
|
||||||
```
|
|
||||||
*❗ A word of caution ❗: Casdoor's front-end is built using yarn. You should use `yarn` instead of `npm`. It has a potential failure during building the files if you use `npm`.*
|
|
||||||
|
|
||||||
Then build back-end binary file, change directory to root(Relative to casdoor):
|
## How to contact?
|
||||||
|
|
||||||
```bash
|
- Gitter: https://gitter.im/casbin/casdoor
|
||||||
go run main.go
|
- Forum: https://forum.casbin.com
|
||||||
```
|
- Contact: https://tawk.to/chat/623352fea34c2456412b8c51/1fuc7od6e
|
||||||
|
|
||||||
That's it! Try to visit http://127.0.0.1:7001/. :small_airplane:
|
|
||||||
**But make sure you always request the backend port 8000 when you are using SDKs.**
|
|
||||||
|
|
||||||
##### Production Mode
|
|
||||||
|
|
||||||
Edit `conf/app.conf`, set `runmode=prod`. Firstly build front-end files:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd web/ && yarn && yarn run build
|
|
||||||
```
|
|
||||||
|
|
||||||
Then build back-end binary file, change directory to root(Relative to casdoor):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go build main.go && sudo ./main
|
|
||||||
```
|
|
||||||
|
|
||||||
> Notice, you should visit back-end port, default 8000. Now try to visit **http://SERVER_IP:8000/**
|
|
||||||
|
|
||||||
### Docker
|
|
||||||
|
|
||||||
Casdoor provide 2 kinds of image:
|
|
||||||
- casbin/casdoor-all-in-one, in which casdoor binary, a mysql database and all necessary configurations are packed up. This image is for new user to have a trial on casdoor quickly. **With this image you can start a casdoor immediately with one single command (or two) without any complex configuration**. **Note: we DO NOT recommend you to use this image in productive environment**
|
|
||||||
|
|
||||||
- casbin/casdoor: normal & graceful casdoor image with only casdoor and environment installed.
|
|
||||||
|
|
||||||
This method requires [docker](https://docs.docker.com/get-docker/) and [docker-compose](https://docs.docker.com/compose/install/) to be installed first.
|
|
||||||
|
|
||||||
### Start casdoor with casbin/casdoor-all-in-one
|
|
||||||
if the image is not pulled, pull it from dockerhub
|
|
||||||
```shell
|
|
||||||
docker pull casbin/casdoor-all-in-one
|
|
||||||
```
|
|
||||||
Start it with
|
|
||||||
```shell
|
|
||||||
docker run -p 8000:8000 casbin/casdoor-all-in-one
|
|
||||||
```
|
|
||||||
Now you can visit http://localhost:8000 and have a try. Default account and password is 'admin' and '123'. Go for it!
|
|
||||||
|
|
||||||
### Start casdoor with casbin/casdoor
|
|
||||||
#### modify the configurations
|
|
||||||
For the convenience of your first attempt, docker-compose.yml contains commands to start a database via docker.
|
|
||||||
|
|
||||||
Thus edit `conf/app.conf` to point out the location of database(db:3306), modify `dataSourceName` to the fixed content:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
dataSourceName = root:123456@tcp(db:3306)/
|
|
||||||
```
|
|
||||||
|
|
||||||
> If you need to modify `conf/app.conf`, you need to re-run `docker-compose up`.
|
|
||||||
|
|
||||||
#### Run
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker-compose up
|
|
||||||
```
|
|
||||||
|
|
||||||
### K8S
|
|
||||||
You could use helm to deploy casdoor in k8s. At first, you should modify the [configmap](./manifests/casdoor/templates/configmap.yaml) for your application.
|
|
||||||
And then run bellow command to deploy it.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
IMG_TAG=latest make deploy
|
|
||||||
```
|
|
||||||
|
|
||||||
And undeploy it with:
|
|
||||||
```bash
|
|
||||||
make undeploy
|
|
||||||
```
|
|
||||||
|
|
||||||
That's it! Try to visit http://localhost:8000/. :small_airplane:
|
|
||||||
|
|
||||||
## Detailed documentation
|
|
||||||
|
|
||||||
We also provide a complete [document](https://casdoor.org/) as a reference.
|
|
||||||
|
|
||||||
## Other examples
|
|
||||||
|
|
||||||
These all use casdoor as a centralized authentication platform.
|
|
||||||
|
|
||||||
- [Casnode](https://github.com/casbin/casnode): Next-generation forum software based on React + Golang.
|
|
||||||
- [Casbin-OA](https://github.com/casbin/casbin-oa): A full-featured OA(Office Assistant) system.
|
|
||||||
- ......
|
|
||||||
|
|
||||||
## Contribute
|
## Contribute
|
||||||
|
|
||||||
For casdoor, if you have any questions, you can give Issues, or you can also directly start Pull Requests(but we recommend giving issues first to communicate with the community).
|
For casdoor, if you have any questions, you can give Issues, or you can also directly start Pull Requests(but we recommend giving issues first to communicate with the community).
|
||||||
|
|
||||||
### I18n notice
|
### I18n translation
|
||||||
|
|
||||||
If you are contributing to casdoor, please note that we use [Crowdin](https://crowdin.com/project/casdoor-web) as translating platform and i18next as translating tool. When you add some words using i18next in the ```web/``` directory, please remember to add what you have added to the ```web/src/locales/en/data.json``` file.
|
If you are contributing to casdoor, please note that we use [Crowdin](https://crowdin.com/project/casdoor-web) as translating platform and i18next as translating tool. When you add some words using i18next in the ```web/``` directory, please remember to add what you have added to the ```web/src/locales/en/data.json``` file.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[Apache-2.0](https://github.com/casdoor/casdoor/blob/master/LICENSE)
|
[Apache-2.0](https://github.com/casdoor/casdoor/blob/master/LICENSE)
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
@ -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
120
controllers/model.go
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/utils/pagination"
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetModels
|
||||||
|
// @Title GetModels
|
||||||
|
// @Tag Model API
|
||||||
|
// @Description get models
|
||||||
|
// @Param owner query string true "The owner of models"
|
||||||
|
// @Success 200 {array} object.Model The Response object
|
||||||
|
// @router /get-models [get]
|
||||||
|
func (c *ApiController) GetModels() {
|
||||||
|
owner := c.Input().Get("owner")
|
||||||
|
limit := c.Input().Get("pageSize")
|
||||||
|
page := c.Input().Get("p")
|
||||||
|
field := c.Input().Get("field")
|
||||||
|
value := c.Input().Get("value")
|
||||||
|
sortField := c.Input().Get("sortField")
|
||||||
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
if limit == "" || page == "" {
|
||||||
|
c.Data["json"] = object.GetModels(owner)
|
||||||
|
c.ServeJSON()
|
||||||
|
} else {
|
||||||
|
limit := util.ParseInt(limit)
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetModelCount(owner, field, value)))
|
||||||
|
models := object.GetPaginationModels(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
c.ResponseOk(models, paginator.Nums())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetModel
|
||||||
|
// @Title GetModel
|
||||||
|
// @Tag Model API
|
||||||
|
// @Description get model
|
||||||
|
// @Param id query string true "The id of the model"
|
||||||
|
// @Success 200 {object} object.Model The Response object
|
||||||
|
// @router /get-model [get]
|
||||||
|
func (c *ApiController) GetModel() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
c.Data["json"] = object.GetModel(id)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateModel
|
||||||
|
// @Title UpdateModel
|
||||||
|
// @Tag Model API
|
||||||
|
// @Description update model
|
||||||
|
// @Param id query string true "The id of the model"
|
||||||
|
// @Param body body object.Model true "The details of the model"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /update-model [post]
|
||||||
|
func (c *ApiController) UpdateModel() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
var model object.Model
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &model)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.UpdateModel(id, &model))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddModel
|
||||||
|
// @Title AddModel
|
||||||
|
// @Tag Model API
|
||||||
|
// @Description add model
|
||||||
|
// @Param body body object.Model true "The details of the model"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /add-model [post]
|
||||||
|
func (c *ApiController) AddModel() {
|
||||||
|
var model object.Model
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &model)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.AddModel(&model))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteModel
|
||||||
|
// @Title DeleteModel
|
||||||
|
// @Tag Model API
|
||||||
|
// @Description delete model
|
||||||
|
// @Param body body object.Model true "The details of the model"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /delete-model [post]
|
||||||
|
func (c *ApiController) DeleteModel() {
|
||||||
|
var model object.Model
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &model)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.DeleteModel(&model))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
@ -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
|
||||||
|
@ -187,9 +187,10 @@ func (idp *BilibiliIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
userInfo := &UserInfo{
|
userInfo := &UserInfo{
|
||||||
Id: bUserInfoResponse.Data.OpenId,
|
Id: bUserInfoResponse.Data.OpenId,
|
||||||
Username: bUserInfoResponse.Data.Name,
|
Username: bUserInfoResponse.Data.Name,
|
||||||
AvatarUrl: bUserInfoResponse.Data.Face,
|
DisplayName: bUserInfoResponse.Data.Name,
|
||||||
|
AvatarUrl: bUserInfoResponse.Data.Face,
|
||||||
}
|
}
|
||||||
|
|
||||||
return userInfo, nil
|
return userInfo, nil
|
||||||
|
198
idp/douyin.go
Normal file
198
idp/douyin.go
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package idp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DouyinIdProvider struct {
|
||||||
|
Client *http.Client
|
||||||
|
Config *oauth2.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDouyinIdProvider(clientId string, clientSecret string, redirectUrl string) *DouyinIdProvider {
|
||||||
|
idp := &DouyinIdProvider{}
|
||||||
|
idp.Config = idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||||
|
return idp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *DouyinIdProvider) SetHttpClient(client *http.Client) {
|
||||||
|
idp.Client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *DouyinIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||||
|
var endpoint = oauth2.Endpoint{
|
||||||
|
TokenURL: "https://open.douyin.com/oauth/access_token",
|
||||||
|
AuthURL: "https://open.douyin.com/platform/oauth/connect",
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = &oauth2.Config{
|
||||||
|
Scopes: []string{"user_info"},
|
||||||
|
Endpoint: endpoint,
|
||||||
|
ClientID: clientId,
|
||||||
|
ClientSecret: clientSecret,
|
||||||
|
RedirectURL: redirectUrl,
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
// get more details via: https://open.douyin.com/platform/doc?doc=docs/openapi/account-permission/get-access-token
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"access_token": "access_token",
|
||||||
|
"description": "",
|
||||||
|
"error_code": "0",
|
||||||
|
"expires_in": "86400",
|
||||||
|
"open_id": "aaa-bbb-ccc",
|
||||||
|
"refresh_expires_in": "86400",
|
||||||
|
"refresh_token": "refresh_token",
|
||||||
|
"scope": "user_info"
|
||||||
|
},
|
||||||
|
"message": "<nil>"
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
type DouyinTokenResp struct {
|
||||||
|
Data struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
ExpiresIn int64 `json:"expires_in"`
|
||||||
|
OpenId string `json:"open_id"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
Scope string `json:"scope"`
|
||||||
|
} `json:"data"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetToken use code to get access_token
|
||||||
|
// get more details via: https://open.douyin.com/platform/doc?doc=docs/openapi/account-permission/get-access-token
|
||||||
|
func (idp *DouyinIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||||
|
payload := url.Values{}
|
||||||
|
payload.Set("code", code)
|
||||||
|
payload.Set("grant_type", "authorization_code")
|
||||||
|
payload.Set("client_key", idp.Config.ClientID)
|
||||||
|
payload.Set("client_secret", idp.Config.ClientSecret)
|
||||||
|
resp, err := idp.Client.PostForm(idp.Config.Endpoint.TokenURL, payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tokenResp := &DouyinTokenResp{}
|
||||||
|
err = json.Unmarshal(data, tokenResp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("fail to unmarshal token response: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
token := &oauth2.Token{
|
||||||
|
AccessToken: tokenResp.Data.AccessToken,
|
||||||
|
RefreshToken: tokenResp.Data.RefreshToken,
|
||||||
|
Expiry: time.Unix(time.Now().Unix()+tokenResp.Data.ExpiresIn, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
raw := make(map[string]interface{})
|
||||||
|
raw["open_id"] = tokenResp.Data.OpenId
|
||||||
|
token = token.WithExtra(raw)
|
||||||
|
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// get more details via: https://open.douyin.com/platform/doc?doc=docs/openapi/account-management/get-account-open-info
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"avatar": "https://example.com/x.jpeg",
|
||||||
|
"city": "上海",
|
||||||
|
"country": "中国",
|
||||||
|
"description": "",
|
||||||
|
"e_account_role": "<nil>",
|
||||||
|
"error_code": "0",
|
||||||
|
"gender": "<nil>",
|
||||||
|
"nickname": "张伟",
|
||||||
|
"open_id": "0da22181-d833-447f-995f-1beefea5bef3",
|
||||||
|
"province": "上海",
|
||||||
|
"union_id": "1ad4e099-4a0c-47d1-a410-bffb4f2f64a4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
type DouyinUserInfo struct {
|
||||||
|
Data struct {
|
||||||
|
Avatar string `json:"avatar"`
|
||||||
|
City string `json:"city"`
|
||||||
|
Country string `json:"country"`
|
||||||
|
// 0->unknown, 1->male, 2->female
|
||||||
|
Gender int64 `json:"gender"`
|
||||||
|
Nickname string `json:"nickname"`
|
||||||
|
OpenId string `json:"open_id"`
|
||||||
|
Province string `json:"province"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserInfo use token to get user profile
|
||||||
|
func (idp *DouyinIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||||
|
body := &struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
OpenId string `json:"open_id"`
|
||||||
|
}{token.AccessToken, token.Extra("open_id").(string)}
|
||||||
|
data, err := json.Marshal(body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest("GET", "https://open.douyin.com/oauth/userinfo/", bytes.NewReader(data))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Add("access-token", token.AccessToken)
|
||||||
|
req.Header.Add("Accept", "application/json")
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
|
resp, err := idp.Client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
respBody, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var douyinUserInfo DouyinUserInfo
|
||||||
|
err = json.Unmarshal(respBody, &douyinUserInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo := UserInfo{
|
||||||
|
Id: douyinUserInfo.Data.OpenId,
|
||||||
|
Username: douyinUserInfo.Data.Nickname,
|
||||||
|
DisplayName: douyinUserInfo.Data.Nickname,
|
||||||
|
AvatarUrl: douyinUserInfo.Data.Avatar,
|
||||||
|
}
|
||||||
|
return &userInfo, nil
|
||||||
|
}
|
@ -86,6 +86,8 @@ 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" {
|
} else if typ == "Bilibili" {
|
||||||
|
@ -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)
|
||||||
|
122
object/model.go
Normal file
122
object/model.go
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
"xorm.io/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Model struct {
|
||||||
|
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||||
|
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||||
|
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||||
|
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||||
|
|
||||||
|
ModelText string `xorm:"mediumtext" json:"modelText"`
|
||||||
|
IsEnabled bool `json:"isEnabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetModelCount(owner, field, value string) int {
|
||||||
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
|
count, err := session.Count(&Model{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetModels(owner string) []*Model {
|
||||||
|
models := []*Model{}
|
||||||
|
err := adapter.Engine.Desc("created_time").Find(&models, &Model{Owner: owner})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return models
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPaginationModels(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Model {
|
||||||
|
models := []*Model{}
|
||||||
|
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||||
|
err := session.Find(&models)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return models
|
||||||
|
}
|
||||||
|
|
||||||
|
func getModel(owner string, name string) *Model {
|
||||||
|
if owner == "" || name == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
model := Model{Owner: owner, Name: name}
|
||||||
|
existed, err := adapter.Engine.Get(&model)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &model
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetModel(id string) *Model {
|
||||||
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
|
return getModel(owner, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateModel(id string, model *Model) bool {
|
||||||
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
|
if getModel(owner, name) == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(model)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddModel(model *Model) bool {
|
||||||
|
affected, err := adapter.Engine.Insert(model)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteModel(model *Model) bool {
|
||||||
|
affected, err := adapter.Engine.ID(core.PK{model.Owner, model.Name}).Delete(&Model{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (model *Model) GetId() string {
|
||||||
|
return fmt.Sprintf("%s/%s", model.Owner, model.Name)
|
||||||
|
}
|
@ -121,11 +121,15 @@ 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 != "" && organization.MasterPassword != "***" {
|
if organization.MasterPassword != "" && organization.MasterPassword != "***" {
|
||||||
|
@ -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"`
|
||||||
|
@ -96,6 +96,7 @@ type User struct {
|
|||||||
Steam string `xorm:"steam varchar(100)" json:"steam"`
|
Steam string `xorm:"steam varchar(100)" json:"steam"`
|
||||||
Bilibili string `xorm:"bilibili varchar(100)" json:"bilibili"`
|
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"`
|
||||||
|
@ -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) })
|
||||||
}
|
}
|
||||||
|
@ -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")
|
||||||
|
@ -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" />
|
||||||
<!--
|
<!--
|
||||||
|
@ -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} />)}/>
|
||||||
|
@ -534,6 +534,7 @@ class ApplicationEditPage extends React.Component {
|
|||||||
renderPreview() {
|
renderPreview() {
|
||||||
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");
|
||||||
}
|
}
|
||||||
@ -546,7 +547,7 @@ class ApplicationEditPage extends React.Component {
|
|||||||
</a>
|
</a>
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<br/>
|
||||||
<div style={{width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems:"center", overflow:"auto", flexDirection:"column", flex: "auto"}}>
|
<div style={{position:'relative', width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems:"center", overflow:"auto", flexDirection:"column", flex: "auto"}}>
|
||||||
{
|
{
|
||||||
this.state.application.enablePassword ? (
|
this.state.application.enablePassword ? (
|
||||||
<SignupPage application={this.state.application} />
|
<SignupPage application={this.state.application} />
|
||||||
@ -554,6 +555,7 @@ 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} style={{display:"flex", flexDirection: "column"}}>
|
||||||
@ -562,8 +564,9 @@ class ApplicationEditPage extends React.Component {
|
|||||||
</a>
|
</a>
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<br/>
|
||||||
<div style={{width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems:"center", overflow:"auto", flexDirection:"column", flex: "auto"}}>
|
<div style={{position:'relative', width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems:"center", overflow:"auto", flexDirection:"column", flex: "auto"}}>
|
||||||
<LoginPage type={"login"} mode={"signin"} application={this.state.application} />
|
<LoginPage type={"login"} mode={"signin"} application={this.state.application} />
|
||||||
|
<div style={maskStyle}></div>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
@ -575,7 +578,7 @@ class ApplicationEditPage extends React.Component {
|
|||||||
<a style={{marginBottom: "10px", display: "flex"}} target="_blank" rel="noreferrer" href={signUpUrl}>
|
<a style={{marginBottom: "10px", display: "flex"}} target="_blank" rel="noreferrer" href={signUpUrl}>
|
||||||
<Button type="primary">{i18next.t("application:Test signup page..")}</Button>
|
<Button type="primary">{i18next.t("application:Test signup page..")}</Button>
|
||||||
</a>
|
</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"}}>
|
<div style={{position:'relative', 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 ? (
|
this.state.application.enablePassword ? (
|
||||||
<SignupPage application={this.state.application} />
|
<SignupPage application={this.state.application} />
|
||||||
@ -583,12 +586,14 @@ 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>
|
||||||
<a style={{marginBottom: "10px", display: "flex"}} target="_blank" rel="noreferrer" href={signInUrl}>
|
<a style={{marginBottom: "10px", display: "flex"}} target="_blank" rel="noreferrer" href={signInUrl}>
|
||||||
<Button type="primary">{i18next.t("application:Test signin page..")}</Button>
|
<Button type="primary">{i18next.t("application:Test signin page..")}</Button>
|
||||||
</a>
|
</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"}}>
|
<div style={{position:'relative', width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems: "center", overflow: "auto", flexDirection: "column", flex: "auto"}}>
|
||||||
<LoginPage type={"login"} mode={"signin"} application={this.state.application} />
|
<LoginPage type={"login"} mode={"signin"} application={this.state.application} />
|
||||||
|
<div style={maskStyle}></div>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
208
web/src/ModelEditPage.js
Normal file
208
web/src/ModelEditPage.js
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import {Button, Card, Col, Input, Row, Select, Switch} from 'antd';
|
||||||
|
import * as ModelBackend from "./backend/ModelBackend";
|
||||||
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import TextArea from "antd/es/input/TextArea";
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
|
class ModelEditPage extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
classes: props,
|
||||||
|
organizationName: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
|
||||||
|
modelName: props.match.params.modelName,
|
||||||
|
model: null,
|
||||||
|
organizations: [],
|
||||||
|
users: [],
|
||||||
|
models: [],
|
||||||
|
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
UNSAFE_componentWillMount() {
|
||||||
|
this.getModel();
|
||||||
|
this.getOrganizations();
|
||||||
|
}
|
||||||
|
|
||||||
|
getModel() {
|
||||||
|
ModelBackend.getModel(this.state.organizationName, this.state.modelName)
|
||||||
|
.then((model) => {
|
||||||
|
this.setState({
|
||||||
|
model: model,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.getModels(model.owner);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrganizations() {
|
||||||
|
OrganizationBackend.getOrganizations("admin")
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
organizations: (res.msg === undefined) ? res : [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getModels(organizationName) {
|
||||||
|
ModelBackend.getModels(organizationName)
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
models: res,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
parseModelField(key, value) {
|
||||||
|
if ([""].includes(key)) {
|
||||||
|
value = Setting.myParseInt(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateModelField(key, value) {
|
||||||
|
value = this.parseModelField(key, value);
|
||||||
|
|
||||||
|
let model = this.state.model;
|
||||||
|
model[key] = value;
|
||||||
|
this.setState({
|
||||||
|
model: model,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderModel() {
|
||||||
|
return (
|
||||||
|
<Card size="small" title={
|
||||||
|
<div>
|
||||||
|
{this.state.mode === "add" ? i18next.t("model:New Model") : i18next.t("model:Edit Model")}
|
||||||
|
<Button onClick={() => this.submitModelEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||||
|
<Button style={{marginLeft: '20px'}} type="primary" onClick={() => this.submitModelEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||||
|
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} onClick={() => this.deleteModel()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||||
|
</div>
|
||||||
|
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner">
|
||||||
|
<Row style={{marginTop: '10px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: '100%'}} value={this.state.model.owner} onChange={(value => {this.updateModelField('owner', value);})}>
|
||||||
|
{
|
||||||
|
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.model.name} onChange={e => {
|
||||||
|
this.updateModelField('name', e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.model.displayName} onChange={e => {
|
||||||
|
this.updateModelField('displayName', e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("model:Model"), i18next.t("model:Model - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22}>
|
||||||
|
<TextArea rows={10} value={this.state.model.modelText} onChange={e => {
|
||||||
|
this.updateModelField('modelText', e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={1} >
|
||||||
|
<Switch checked={this.state.model.isEnabled} onChange={checked => {
|
||||||
|
this.updateModelField('isEnabled', checked);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
submitModelEdit(willExist) {
|
||||||
|
let model = Setting.deepCopy(this.state.model);
|
||||||
|
ModelBackend.updateModel(this.state.organizationName, this.state.modelName, model)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.msg === "") {
|
||||||
|
Setting.showMessage("success", `Successfully saved`);
|
||||||
|
this.setState({
|
||||||
|
modelName: this.state.model.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (willExist) {
|
||||||
|
this.props.history.push(`/models`);
|
||||||
|
} else {
|
||||||
|
this.props.history.push(`/models/${this.state.model.owner}/${this.state.model.name}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", res.msg);
|
||||||
|
this.updateModelField('name', this.state.modelName);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `Failed to connect to server: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteModel() {
|
||||||
|
ModelBackend.deleteModel(this.state.model)
|
||||||
|
.then(() => {
|
||||||
|
this.props.history.push(`/models`);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `Model failed to delete: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
this.state.model !== null ? this.renderModel() : null
|
||||||
|
}
|
||||||
|
<div style={{marginTop: '20px', marginLeft: '40px'}}>
|
||||||
|
<Button size="large" onClick={() => this.submitModelEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||||
|
<Button style={{marginLeft: '20px'}} type="primary" size="large" onClick={() => this.submitModelEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||||
|
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} size="large" onClick={() => this.deleteModel()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ModelEditPage;
|
201
web/src/ModelListPage.js
Normal file
201
web/src/ModelListPage.js
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import {Link} from "react-router-dom";
|
||||||
|
import {Button, Popconfirm, Switch, Table} from 'antd';
|
||||||
|
import moment from "moment";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import * as ModelBackend from "./backend/ModelBackend";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import BaseListPage from "./BaseListPage";
|
||||||
|
|
||||||
|
class ModelListPage extends BaseListPage {
|
||||||
|
newModel() {
|
||||||
|
const randomName = Setting.getRandomName();
|
||||||
|
return {
|
||||||
|
owner: "built-in",
|
||||||
|
name: `model_${randomName}`,
|
||||||
|
createdTime: moment().format(),
|
||||||
|
displayName: `New Model - ${randomName}`,
|
||||||
|
modelText: "",
|
||||||
|
isEnabled: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addModel() {
|
||||||
|
const newModel = this.newModel();
|
||||||
|
ModelBackend.addModel(newModel)
|
||||||
|
.then((res) => {
|
||||||
|
this.props.history.push({pathname: `/models/${newModel.owner}/${newModel.name}`, mode: "add"});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `Model failed to add: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteModel(i) {
|
||||||
|
ModelBackend.deleteModel(this.state.data[i])
|
||||||
|
.then((res) => {
|
||||||
|
Setting.showMessage("success", `Model deleted successfully`);
|
||||||
|
this.setState({
|
||||||
|
data: Setting.deleteRow(this.state.data, i),
|
||||||
|
pagination: {total: this.state.pagination.total - 1},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `Model failed to delete: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTable(models) {
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Organization"),
|
||||||
|
dataIndex: 'owner',
|
||||||
|
key: 'owner',
|
||||||
|
width: '120px',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps('owner'),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Link to={`/organizations/${text}`}>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Name"),
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
width: '150px',
|
||||||
|
fixed: 'left',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps('name'),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Link to={`/models/${text}`}>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Created time"),
|
||||||
|
dataIndex: 'createdTime',
|
||||||
|
key: 'createdTime',
|
||||||
|
width: '160px',
|
||||||
|
sorter: true,
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return Setting.getFormattedDate(text);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Display name"),
|
||||||
|
dataIndex: 'displayName',
|
||||||
|
key: 'displayName',
|
||||||
|
width: '200px',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps('displayName'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Is enabled"),
|
||||||
|
dataIndex: 'isEnabled',
|
||||||
|
key: 'isEnabled',
|
||||||
|
width: '120px',
|
||||||
|
sorter: true,
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text}/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Action"),
|
||||||
|
dataIndex: '',
|
||||||
|
key: 'op',
|
||||||
|
width: '170px',
|
||||||
|
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary"
|
||||||
|
onClick={() => this.props.history.push(`/models/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
|
<Popconfirm
|
||||||
|
title={`Sure to delete model: ${record.name} ?`}
|
||||||
|
onConfirm={() => this.deleteModel(index)}
|
||||||
|
>
|
||||||
|
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const paginationProps = {
|
||||||
|
total: this.state.pagination.total,
|
||||||
|
showQuickJumper: true,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Table scroll={{x: 'max-content'}} columns={columns} dataSource={models} rowKey="name" size="middle" bordered
|
||||||
|
pagination={paginationProps}
|
||||||
|
title={() => (
|
||||||
|
<div>
|
||||||
|
{i18next.t("general:Models")}
|
||||||
|
<Button type="primary" size="small"
|
||||||
|
onClick={this.addModel.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
loading={this.state.loading}
|
||||||
|
onChange={this.handleTableChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch = (params = {}) => {
|
||||||
|
let field = params.searchedColumn, value = params.searchText;
|
||||||
|
let sortField = params.sortField, sortOrder = params.sortOrder;
|
||||||
|
if (params.type !== undefined && params.type !== null) {
|
||||||
|
field = "type";
|
||||||
|
value = params.type;
|
||||||
|
}
|
||||||
|
this.setState({loading: true});
|
||||||
|
ModelBackend.getModels("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
data: res.data,
|
||||||
|
pagination: {
|
||||||
|
...params.pagination,
|
||||||
|
total: res.data2,
|
||||||
|
},
|
||||||
|
searchText: params.searchText,
|
||||||
|
searchedColumn: params.searchedColumn,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ModelListPage;
|
@ -20,6 +20,7 @@ import * as 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"))} :
|
||||||
|
@ -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"}}>
|
||||||
|
@ -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"));
|
||||||
}
|
}
|
||||||
|
@ -206,7 +206,7 @@ class ResourceListPage extends BaseListPage {
|
|||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
if (record.fileType === "image") {
|
if (record.fileType === "image") {
|
||||||
return (
|
return (
|
||||||
<a target="_blank" href={record.url}>
|
<a target="_blank" rel="noreferrer" href={record.url}>
|
||||||
<img src={record.url} alt={record.name} width={100} />
|
<img src={record.url} alt={record.name} width={100} />
|
||||||
</a>
|
</a>
|
||||||
)
|
)
|
||||||
|
@ -21,7 +21,6 @@ import i18next from "i18next";
|
|||||||
import copy from "copy-to-clipboard";
|
import copy from "copy-to-clipboard";
|
||||||
import {authConfig} from "./auth/Auth";
|
import {authConfig} from "./auth/Auth";
|
||||||
import {Helmet} from "react-helmet";
|
import {Helmet} from "react-helmet";
|
||||||
import moment from "moment";
|
|
||||||
import * as Conf from "./Conf";
|
import * as Conf from "./Conf";
|
||||||
|
|
||||||
export let ServerUrl = "";
|
export let ServerUrl = "";
|
||||||
@ -225,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -474,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) {
|
||||||
@ -556,6 +554,7 @@ export function getProviderTypeOptions(category) {
|
|||||||
{id: 'Steam', name: 'Steam'},
|
{id: 'Steam', name: 'Steam'},
|
||||||
{id: 'Bilibili', name: 'Bilibili'},
|
{id: 'Bilibili', name: 'Bilibili'},
|
||||||
{id: 'Okta', name: 'Okta'},
|
{id: 'Okta', name: 'Okta'},
|
||||||
|
{id: 'Douyin', name: 'Douyin'},
|
||||||
{id: 'Custom', name: 'Custom'},
|
{id: 'Custom', name: 'Custom'},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
@ -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']
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
32
web/src/auth/DouyinLoginButton.js
Normal file
32
web/src/auth/DouyinLoginButton.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
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;
|
@ -44,6 +44,7 @@ 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";
|
import BilibiliLoginButton from "./BilibiliLoginButton";
|
||||||
@ -143,47 +144,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') {
|
||||||
@ -284,6 +286,8 @@ class LoginPage extends React.Component {
|
|||||||
return <BilibiliLoginButton text={text} align={"center"} />
|
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;
|
||||||
@ -316,7 +320,7 @@ class LoginPage extends React.Component {
|
|||||||
)
|
)
|
||||||
} else if (provider.category === "SAML") {
|
} else if (provider.category === "SAML") {
|
||||||
return (
|
return (
|
||||||
<a key={provider.displayName} onClick={this.getSamlUrl.bind(this, provider)}>
|
<a href="/#" key={provider.displayName} onClick={this.getSamlUrl.bind(this, provider)}>
|
||||||
<img width={width} height={width} src={Setting.getProviderLogoURL(provider)} alt={provider.displayName} style={{margin: margin}} />
|
<img width={width} height={width} src={Setting.getProviderLogoURL(provider)} alt={provider.displayName} style={{margin: margin}} />
|
||||||
</a>
|
</a>
|
||||||
)
|
)
|
||||||
@ -472,7 +476,7 @@ class LoginPage extends React.Component {
|
|||||||
{i18next.t("login:Auto sign in")}
|
{i18next.t("login:Auto sign in")}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<a style={{float: "right"}} onClick={() => {
|
<a href="/#" style={{float: "right"}} onClick={() => {
|
||||||
Setting.goToForget(this, application);
|
Setting.goToForget(this, application);
|
||||||
}}>
|
}}>
|
||||||
{i18next.t("login:Forgot password?")}
|
{i18next.t("login:Forgot password?")}
|
||||||
@ -551,7 +555,7 @@ class LoginPage extends React.Component {
|
|||||||
<span style={{float: "left"}}>
|
<span style={{float: "left"}}>
|
||||||
{
|
{
|
||||||
!application.enableCodeSignin ? null : (
|
!application.enableCodeSignin ? null : (
|
||||||
<a onClick={() => {
|
<a href="/#" onClick={() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
isCodeSignin: !this.state.isCodeSignin,
|
isCodeSignin: !this.state.isCodeSignin,
|
||||||
});
|
});
|
||||||
@ -563,7 +567,7 @@ class LoginPage extends React.Component {
|
|||||||
</span>
|
</span>
|
||||||
<span style={{float: "right"}}>
|
<span style={{float: "right"}}>
|
||||||
{i18next.t("login:No account?")}
|
{i18next.t("login:No account?")}
|
||||||
<a onClick={() => {
|
<a href="/#" onClick={() => {
|
||||||
sessionStorage.setItem("loginURL", window.location.href)
|
sessionStorage.setItem("loginURL", window.location.href)
|
||||||
Setting.goToSignup(this, application);
|
Setting.goToSignup(this, application);
|
||||||
}}>
|
}}>
|
||||||
|
@ -111,6 +111,10 @@ 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/",
|
||||||
},
|
},
|
||||||
@ -239,6 +243,8 @@ 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") {
|
} else if (provider.type === "Bilibili") {
|
||||||
|
@ -559,7 +559,7 @@ class SignupPage extends React.Component {
|
|||||||
{i18next.t("account:Sign Up")}
|
{i18next.t("account:Sign Up")}
|
||||||
</Button>
|
</Button>
|
||||||
{i18next.t("signup:Have account?")}
|
{i18next.t("signup:Have account?")}
|
||||||
<a onClick={() => {
|
<a href="/#" onClick={() => {
|
||||||
let linkInStorage = sessionStorage.getItem("loginURL")
|
let linkInStorage = sessionStorage.getItem("loginURL")
|
||||||
if(linkInStorage != null){
|
if(linkInStorage != null){
|
||||||
Setting.goToLink(linkInStorage)
|
Setting.goToLink(linkInStorage)
|
||||||
|
56
web/src/backend/ModelBackend.js
Normal file
56
web/src/backend/ModelBackend.js
Normal 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());
|
||||||
|
}
|
@ -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 === "" ? (
|
||||||
|
@ -131,6 +131,7 @@
|
|||||||
"Master password": "万能密码",
|
"Master password": "万能密码",
|
||||||
"Master password - Tooltip": "可用来登录该组织下的所有用户,方便管理员以该用户身份登录,以解决技术问题",
|
"Master password - Tooltip": "可用来登录该组织下的所有用户,方便管理员以该用户身份登录,以解决技术问题",
|
||||||
"Method": "方法",
|
"Method": "方法",
|
||||||
|
"Models": "模型",
|
||||||
"Name": "名称",
|
"Name": "名称",
|
||||||
"Name - Tooltip": "唯一的、字符串式的ID",
|
"Name - Tooltip": "唯一的、字符串式的ID",
|
||||||
"OAuth providers": "OAuth提供方",
|
"OAuth providers": "OAuth提供方",
|
||||||
@ -243,6 +244,10 @@
|
|||||||
"sign up now": "立即注册",
|
"sign up now": "立即注册",
|
||||||
"username, Email or phone": "用户名、Email或手机号"
|
"username, Email or phone": "用户名、Email或手机号"
|
||||||
},
|
},
|
||||||
|
"model": {
|
||||||
|
"Edit Model": "编辑模型",
|
||||||
|
"Model": "模型"
|
||||||
|
},
|
||||||
"organization": {
|
"organization": {
|
||||||
"Default avatar": "默认头像",
|
"Default avatar": "默认头像",
|
||||||
"Edit Organization": "编辑组织",
|
"Edit Organization": "编辑组织",
|
||||||
|
Reference in New Issue
Block a user