mirror of
https://github.com/casdoor/casdoor.git
synced 2025-08-07 05:17:48 +08:00
Compare commits
81 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
12bc419659 | ||
![]() |
d5f18f2d64 | ||
![]() |
02c06bc93c | ||
![]() |
40aa9a4693 | ||
![]() |
630b84f534 | ||
![]() |
339a85e4b0 | ||
![]() |
c22ab44894 | ||
![]() |
c3fb48f473 | ||
![]() |
a111fd672c | ||
![]() |
9fd175eefd | ||
![]() |
d9bcce9485 | ||
![]() |
d183b9eca9 | ||
![]() |
f24d9ae251 | ||
![]() |
030c1caa50 | ||
![]() |
cee2c608a2 | ||
![]() |
82d0e895e0 | ||
![]() |
dee9bac110 | ||
![]() |
e7a6986b62 | ||
![]() |
b91b4aec91 | ||
![]() |
fe48c38bc6 | ||
![]() |
1be777c08f | ||
![]() |
8d54bfad8a | ||
![]() |
728fe11a3c | ||
![]() |
69e0f4e40d | ||
![]() |
ba32a45693 | ||
![]() |
a4d83af768 | ||
![]() |
5b8f6415d9 | ||
![]() |
5389cb435c | ||
![]() |
9b6131890c | ||
![]() |
ffc0a0e0d5 | ||
![]() |
ff22bf507f | ||
![]() |
2d4103d751 | ||
![]() |
4611b59b08 | ||
![]() |
445d3c9d0e | ||
![]() |
dbebd1846f | ||
![]() |
2fcc8f5bfe | ||
![]() |
4b65320a96 | ||
![]() |
5e8897e41b | ||
![]() |
ba1646a0c3 | ||
![]() |
c1cd187558 | ||
![]() |
519fd655cf | ||
![]() |
377ac05928 | ||
![]() |
4f124ff140 | ||
![]() |
d5f802ec7d | ||
![]() |
64d3b7e87f | ||
![]() |
dfce1bd74c | ||
![]() |
067ae5448f | ||
![]() |
9943e3c316 | ||
![]() |
0c665edcbc | ||
![]() |
5015bf1c7d | ||
![]() |
2ec947d488 | ||
![]() |
10a85f2386 | ||
![]() |
0d13512eb1 | ||
![]() |
b60856be5e | ||
![]() |
4b4c9be71b | ||
![]() |
e79e3c36d0 | ||
![]() |
cc8c9b32ef | ||
![]() |
efdcb3279d | ||
![]() |
3818492065 | ||
![]() |
f4890a6a22 | ||
![]() |
fa45d3083a | ||
![]() |
caa1ffe3c8 | ||
![]() |
c88edc4d3e | ||
![]() |
f5bc76016d | ||
![]() |
8345295d0c | ||
![]() |
3bdc0e0d1b | ||
![]() |
8dcb56ea71 | ||
![]() |
b95f107a60 | ||
![]() |
d6c2d0f3e8 | ||
![]() |
7cfece3019 | ||
![]() |
3efbcc739d | ||
![]() |
99ef329325 | ||
![]() |
8f0995ca34 | ||
![]() |
e64f181e28 | ||
![]() |
db56f54b8c | ||
![]() |
bf642b35d4 | ||
![]() |
5ee5299a68 | ||
![]() |
e7f395cfd4 | ||
![]() |
29d512d316 | ||
![]() |
5814ae6baf | ||
![]() |
52145abdc8 |
14
.github/workflows/build.yml
vendored
14
.github/workflows/build.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
release-and-push:
|
||||
name: Release And Push
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'casbin/casdoor' && github.event_name == 'push'
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push'
|
||||
needs: [ frontend, backend ]
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -80,7 +80,7 @@ jobs:
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
if: github.repository == 'casbin/casdoor' && github.event_name == 'push' &&steps.should_push.outputs.push=='true'
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' &&steps.should_push.outputs.push=='true'
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
@@ -88,7 +88,15 @@ jobs:
|
||||
|
||||
- name: Push to Docker Hub
|
||||
uses: docker/build-push-action@v2
|
||||
if: github.repository == 'casbin/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||
with:
|
||||
push: true
|
||||
tags: casbin/casdoor:${{steps.get-current-tag.outputs.tag }},casbin/casdoor:latest
|
||||
|
||||
- name: Push All In One Version to Docker Hub
|
||||
uses: docker/build-push-action@v2
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||
with:
|
||||
target: ALLINONE
|
||||
push: true
|
||||
tags: casbin/casdoor-all-in-one:${{steps.get-current-tag.outputs.tag }},casbin/casdoor-all-in-one:latest
|
||||
|
2
.github/workflows/sync.yml
vendored
2
.github/workflows/sync.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
jobs:
|
||||
synchronize-with-crowdin:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'casbin/casdoor' && github.event_name == 'push'
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push'
|
||||
steps:
|
||||
|
||||
- name: Checkout
|
||||
|
16
Dockerfile
16
Dockerfile
@@ -10,6 +10,19 @@ COPY ./web .
|
||||
RUN yarn config set registry https://registry.npm.taobao.org
|
||||
RUN yarn install && yarn run build
|
||||
|
||||
|
||||
FROM debian:latest AS ALLINONE
|
||||
RUN apt update && apt install -y mariadb-server mariadb-client&&mkdir -p web/build && chmod 777 /tmp
|
||||
LABEL MAINTAINER="https://casdoor.org/"
|
||||
COPY --from=BACK /go/src/casdoor/ ./
|
||||
COPY --from=BACK /usr/bin/wait-for-it ./
|
||||
COPY --from=FRONT /web/build /web/build
|
||||
CMD chmod 777 /tmp && service mariadb start&&\
|
||||
if [ "${MYSQL_ROOT_PASSWORD}" = "" ] ;then MYSQL_ROOT_PASSWORD=123456 ; fi&&\
|
||||
mysqladmin -u root password ${MYSQL_ROOT_PASSWORD} &&\
|
||||
./wait-for-it localhost:3306 -- ./server --createDatabase=true
|
||||
|
||||
|
||||
FROM alpine:latest
|
||||
RUN sed -i 's/https/http/' /etc/apk/repositories
|
||||
RUN apk add curl
|
||||
@@ -19,4 +32,5 @@ COPY --from=BACK /go/src/casdoor/ ./
|
||||
COPY --from=BACK /usr/bin/wait-for-it ./
|
||||
RUN mkdir -p web/build && apk add --no-cache bash coreutils
|
||||
COPY --from=FRONT /web/build /web/build
|
||||
CMD ./wait-for-it db:3306 -- ./server
|
||||
CMD ./server
|
||||
|
||||
|
56
README.md
56
README.md
@@ -7,10 +7,10 @@
|
||||
<a href="https://hub.docker.com/r/casbin/casdoor">
|
||||
<img alt="docker pull casbin/casdoor" src="https://img.shields.io/docker/pulls/casbin/casdoor.svg">
|
||||
</a>
|
||||
<a href="https://github.com/casbin/casdoor/actions/workflows/build.yml">
|
||||
<a href="https://github.com/casdoor/casdoor/actions/workflows/build.yml">
|
||||
<img alt="GitHub Workflow Status (branch)" src="https://github.com/casbin/jcasbin/workflows/build/badge.svg?style=flat-square">
|
||||
</a>
|
||||
<a href="https://github.com/casbin/casdoor/releases/latest">
|
||||
<a href="https://github.com/casdoor/casdoor/releases/latest">
|
||||
<img alt="GitHub Release" src="https://img.shields.io/github/v/release/casbin/casdoor.svg">
|
||||
</a>
|
||||
<a href="https://hub.docker.com/repository/docker/casbin/casdoor">
|
||||
@@ -19,21 +19,27 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://goreportcard.com/report/github.com/casbin/casdoor">
|
||||
<img alt="Go Report Card" src="https://goreportcard.com/badge/github.com/casbin/casdoor?style=flat-square">
|
||||
<a href="https://goreportcard.com/report/github.com/casdoor/casdoor">
|
||||
<img alt="Go Report Card" src="https://goreportcard.com/badge/github.com/casdoor/casdoor?style=flat-square">
|
||||
</a>
|
||||
<a href="https://github.com/casbin/casdoor/blob/master/LICENSE">
|
||||
<a href="https://github.com/casdoor/casdoor/blob/master/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/casbin/casdoor?style=flat-square" alt="license">
|
||||
</a>
|
||||
<a href="https://github.com/casbin/casdoor/issues">
|
||||
<a href="https://github.com/casdoor/casdoor/issues">
|
||||
<img alt="GitHub issues" src="https://img.shields.io/github/issues/casbin/casdoor?style=flat-square">
|
||||
</a>
|
||||
<a href="#">
|
||||
<img alt="GitHub stars" src="https://img.shields.io/github/stars/casbin/casdoor?style=flat-square">
|
||||
</a>
|
||||
<a href="https://github.com/casbin/casdoor/network">
|
||||
<a href="https://github.com/casdoor/casdoor/network">
|
||||
<img alt="GitHub forks" src="https://img.shields.io/github/forks/casbin/casdoor?style=flat-square">
|
||||
</a>
|
||||
<a href="https://crowdin.com/project/casdoor-site">
|
||||
<img alt="Crowdin" src="https://badges.crowdin.net/casdoor-site/localized.svg">
|
||||
</a>
|
||||
<a href="https://gitter.im/casbin/casdoor">
|
||||
<img alt="Gitter" src="https://badges.gitter.im/casbin/casdoor.svg">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## Online demo
|
||||
@@ -41,21 +47,20 @@
|
||||
Deployed site: https://door.casbin.com/
|
||||
|
||||
## Quick Start
|
||||
|
||||
Run your own casdoor program in a few minutes:smiley:
|
||||
Run your own casdoor program in a few minutes.
|
||||
|
||||
### Download
|
||||
|
||||
There are two methods, get code via go subcommand `get`:
|
||||
|
||||
```shell
|
||||
go get github.com/casbin/casdoor
|
||||
go get github.com/casdoor/casdoor
|
||||
```
|
||||
|
||||
or `git`:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/casbin/casdoor
|
||||
git clone https://github.com/casdoor/casdoor
|
||||
```
|
||||
|
||||
Finally, change directory:
|
||||
@@ -117,9 +122,26 @@ go build main.go && sudo ./main
|
||||
|
||||
### 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.
|
||||
|
||||
#### Simple configuration
|
||||
### 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:
|
||||
@@ -138,14 +160,6 @@ docker-compose up
|
||||
|
||||
That's it! Try to visit http://localhost:8000/. :small_airplane:
|
||||
|
||||
### Docker Hub
|
||||
|
||||
This method requires [docker](https://docs.docker.com/get-docker/) and [docker-compose](https://docs.docker.com/compose/install/) to be installed first.
|
||||
|
||||
```bash
|
||||
docker pull casbin/casdoor
|
||||
```
|
||||
|
||||
## Detailed documentation
|
||||
|
||||
We also provide a complete [document](https://casdoor.org/) as a reference.
|
||||
@@ -168,5 +182,5 @@ If you are contributing to casdoor, please note that we use [Crowdin](https://cr
|
||||
|
||||
## License
|
||||
|
||||
[Apache-2.0](https://github.com/casbin/casdoor/blob/master/LICENSE)
|
||||
[Apache-2.0](https://github.com/casdoor/casdoor/blob/master/LICENSE)
|
||||
|
||||
|
@@ -18,8 +18,8 @@ import (
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casbin/casbin/v2"
|
||||
"github.com/casbin/casbin/v2/model"
|
||||
"github.com/casbin/casdoor/conf"
|
||||
xormadapter "github.com/casbin/xorm-adapter/v2"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
stringadapter "github.com/qiangmzsx/string-adapter/v2"
|
||||
)
|
||||
|
||||
@@ -28,7 +28,8 @@ var Enforcer *casbin.Enforcer
|
||||
func InitAuthz() {
|
||||
var err error
|
||||
|
||||
a, err := xormadapter.NewAdapter(beego.AppConfig.String("driverName"), conf.GetBeegoConfDataSourceName()+beego.AppConfig.String("dbName"), true)
|
||||
tableNamePrefix := beego.AppConfig.String("tableNamePrefix")
|
||||
a, err := xormadapter.NewAdapterWithTableName(beego.AppConfig.String("driverName"), conf.GetBeegoConfDataSourceName()+beego.AppConfig.String("dbName"), "casbin_rule", tableNamePrefix, true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -80,6 +81,7 @@ p, *, *, GET, /api/get-app-login, *, *
|
||||
p, *, *, POST, /api/logout, *, *
|
||||
p, *, *, GET, /api/get-account, *, *
|
||||
p, *, *, POST, /api/login/oauth/access_token, *, *
|
||||
p, *, *, POST, /api/login/oauth/refresh_token, *, *
|
||||
p, *, *, GET, /api/get-application, *, *
|
||||
p, *, *, GET, /api/get-users, *, *
|
||||
p, *, *, GET, /api/get-user, *, *
|
||||
|
@@ -6,6 +6,8 @@ copyrequestbody = true
|
||||
driverName = mysql
|
||||
dataSourceName = root:123456@tcp(localhost:3306)/
|
||||
dbName = casdoor
|
||||
tableNamePrefix =
|
||||
showSql = false
|
||||
redisEndpoint =
|
||||
defaultStorageProvider =
|
||||
isCloudIntranet = false
|
||||
|
@@ -19,8 +19,8 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -38,6 +38,7 @@ type RequestForm struct {
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Affiliation string `json:"affiliation"`
|
||||
IdCard string `json:"idCard"`
|
||||
Region string `json:"region"`
|
||||
|
||||
Application string `json:"application"`
|
||||
@@ -61,6 +62,7 @@ type Response struct {
|
||||
Status string `json:"status"`
|
||||
Msg string `json:"msg"`
|
||||
Sub string `json:"sub"`
|
||||
Name string `json:"name"`
|
||||
Data interface{} `json:"data"`
|
||||
Data2 interface{} `json:"data2"`
|
||||
}
|
||||
@@ -151,6 +153,7 @@ func (c *ApiController) Signup() {
|
||||
Phone: form.Phone,
|
||||
Address: []string{},
|
||||
Affiliation: form.Affiliation,
|
||||
IdCard: form.IdCard,
|
||||
Region: form.Region,
|
||||
Score: getInitScore(),
|
||||
IsAdmin: false,
|
||||
@@ -220,6 +223,7 @@ func (c *ApiController) GetAccount() {
|
||||
resp := Response{
|
||||
Status: "ok",
|
||||
Sub: user.Id,
|
||||
Name: user.Name,
|
||||
Data: user,
|
||||
Data2: organization,
|
||||
}
|
||||
|
@@ -18,8 +18,8 @@ import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/astaxie/beego/utils/pagination"
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetApplications
|
||||
@@ -30,16 +30,30 @@ import (
|
||||
// @Success 200 {array} object.Application The Response object
|
||||
// @router /get-applications [get]
|
||||
func (c *ApiController) GetApplications() {
|
||||
userId := c.GetSessionUsername()
|
||||
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")
|
||||
organization := c.Input().Get("organization")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
c.Data["json"] = object.GetApplications(owner)
|
||||
var applications []*object.Application
|
||||
if organization == "" {
|
||||
applications = object.GetApplications(owner)
|
||||
} else {
|
||||
applications = object.GetApplicationsByOrganizationName(owner, organization)
|
||||
}
|
||||
|
||||
c.Data["json"] = object.GetMaskedApplications(applications, userId)
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetApplicationCount(owner)))
|
||||
applications := object.GetPaginationApplications(owner, paginator.Offset(), limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetApplicationCount(owner, field, value)))
|
||||
applications := object.GetMaskedApplications(object.GetPaginationApplications(owner, paginator.Offset(), limit, field, value, sortField, sortOrder), userId)
|
||||
c.ResponseOk(applications, paginator.Nums())
|
||||
}
|
||||
}
|
||||
@@ -52,9 +66,10 @@ func (c *ApiController) GetApplications() {
|
||||
// @Success 200 {object} object.Application The Response object
|
||||
// @router /get-application [get]
|
||||
func (c *ApiController) GetApplication() {
|
||||
userId := c.GetSessionUsername()
|
||||
id := c.Input().Get("id")
|
||||
|
||||
c.Data["json"] = object.GetApplication(id)
|
||||
c.Data["json"] = object.GetMaskedApplication(object.GetApplication(id), userId)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
@@ -66,6 +81,7 @@ func (c *ApiController) GetApplication() {
|
||||
// @Success 200 {object} object.Application The Response object
|
||||
// @router /get-user-application [get]
|
||||
func (c *ApiController) GetUserApplication() {
|
||||
userId := c.GetSessionUsername()
|
||||
id := c.Input().Get("id")
|
||||
user := object.GetUser(id)
|
||||
if user == nil {
|
||||
@@ -73,7 +89,7 @@ func (c *ApiController) GetUserApplication() {
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = object.GetApplicationByUser(user)
|
||||
c.Data["json"] = object.GetMaskedApplication(object.GetApplicationByUser(user), userId)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
|
@@ -24,10 +24,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casbin/casdoor/idp"
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/proxy"
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/idp"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func codeToResponse(code *object.Code) *Response {
|
||||
@@ -52,10 +52,17 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
||||
scope := c.Input().Get("scope")
|
||||
state := c.Input().Get("state")
|
||||
nonce := c.Input().Get("nonce")
|
||||
code := object.GetOAuthCode(userId, clientId, responseType, redirectUri, scope, state, nonce)
|
||||
challengeMethod := c.Input().Get("code_challenge_method")
|
||||
codeChallenge := c.Input().Get("code_challenge")
|
||||
|
||||
if challengeMethod != "S256" && challengeMethod != "null" {
|
||||
c.ResponseError("Challenge method should be S256")
|
||||
return
|
||||
}
|
||||
code := object.GetOAuthCode(userId, clientId, responseType, redirectUri, scope, state, nonce, codeChallenge)
|
||||
resp = codeToResponse(code)
|
||||
|
||||
if application.HasPromptPage() {
|
||||
if application.EnableSigninSession || application.HasPromptPage() {
|
||||
// The prompt page needs the user to be signed in
|
||||
c.SetSessionUsername(userId)
|
||||
}
|
||||
@@ -206,7 +213,15 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
} else if provider.Category == "OAuth" {
|
||||
// OAuth
|
||||
idProvider := idp.GetIdProvider(provider.Type, provider.ClientId, provider.ClientSecret, form.RedirectUri)
|
||||
|
||||
clientId := provider.ClientId
|
||||
clientSecret := provider.ClientSecret
|
||||
if provider.Type == "WeChat" && strings.Contains(c.Ctx.Request.UserAgent(), "MicroMessenger") {
|
||||
clientId = provider.ClientId2
|
||||
clientSecret = provider.ClientSecret2
|
||||
}
|
||||
|
||||
idProvider := idp.GetIdProvider(provider.Type, clientId, clientSecret, form.RedirectUri)
|
||||
if idProvider == nil {
|
||||
c.ResponseError(fmt.Sprintf("The provider type: %s is not supported", provider.Type))
|
||||
return
|
||||
|
@@ -15,10 +15,12 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// controller for handlers under /api uri
|
||||
@@ -35,6 +37,21 @@ type SessionData struct {
|
||||
ExpireTime int64
|
||||
}
|
||||
|
||||
func (c *ApiController) IsGlobalAdmin() bool {
|
||||
username := c.GetSessionUsername()
|
||||
if strings.HasPrefix(username, "app/") {
|
||||
// e.g., "app/app-casnode"
|
||||
return true
|
||||
}
|
||||
|
||||
user := object.GetUser(username)
|
||||
if user == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return user.Owner == "built-in" || user.IsGlobalAdmin
|
||||
}
|
||||
|
||||
// GetSessionUsername ...
|
||||
func (c *ApiController) GetSessionUsername() string {
|
||||
// check if user session expired
|
||||
|
116
controllers/cert.go
Normal file
116
controllers/cert.go
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright 2021 The casbin 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"
|
||||
)
|
||||
|
||||
// GetCerts
|
||||
// @Title GetCerts
|
||||
// @Tag Cert API
|
||||
// @Description get certs
|
||||
// @Param owner query string true "The owner of certs"
|
||||
// @Success 200 {array} object.Cert The Response object
|
||||
// @router /get-certs [get]
|
||||
func (c *ApiController) GetCerts() {
|
||||
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.GetMaskedCerts(object.GetCerts(owner))
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetCertCount(owner, field, value)))
|
||||
certs := object.GetMaskedCerts(object.GetPaginationCerts(owner, paginator.Offset(), limit, field, value, sortField, sortOrder))
|
||||
c.ResponseOk(certs, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// @Title GetCert
|
||||
// @Tag Cert API
|
||||
// @Description get cert
|
||||
// @Param id query string true "The id of the cert"
|
||||
// @Success 200 {object} object.Cert The Response object
|
||||
// @router /get-cert [get]
|
||||
func (c *ApiController) GetCert() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
c.Data["json"] = object.GetMaskedCert(object.GetCert(id))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title UpdateCert
|
||||
// @Tag Cert API
|
||||
// @Description update cert
|
||||
// @Param id query string true "The id of the cert"
|
||||
// @Param body body object.Cert true "The details of the cert"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-cert [post]
|
||||
func (c *ApiController) UpdateCert() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
var cert object.Cert
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &cert)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateCert(id, &cert))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title AddCert
|
||||
// @Tag Cert API
|
||||
// @Description add cert
|
||||
// @Param body body object.Cert true "The details of the cert"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-cert [post]
|
||||
func (c *ApiController) AddCert() {
|
||||
var cert object.Cert
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &cert)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddCert(&cert))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title DeleteCert
|
||||
// @Tag Cert API
|
||||
// @Description delete cert
|
||||
// @Param body body object.Cert true "The details of the cert"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-cert [post]
|
||||
func (c *ApiController) DeleteCert() {
|
||||
var cert object.Cert
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &cert)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteCert(&cert))
|
||||
c.ServeJSON()
|
||||
}
|
@@ -17,8 +17,8 @@ package controllers
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
type LdapServer struct {
|
||||
|
@@ -17,7 +17,7 @@ package controllers
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
)
|
||||
|
||||
type LinkForm struct {
|
||||
|
@@ -14,7 +14,7 @@
|
||||
|
||||
package controllers
|
||||
|
||||
import "github.com/casbin/casdoor/object"
|
||||
import "github.com/casdoor/casdoor/object"
|
||||
|
||||
// @Title GetOidcDiscovery
|
||||
// @Tag OIDC API
|
||||
@@ -28,7 +28,7 @@ func (c *RootController) GetOidcDiscovery() {
|
||||
// @Tag OIDC API
|
||||
// @router /api/certs [get]
|
||||
func (c *RootController) GetOidcCert() {
|
||||
jwks, err := object.GetJSONWebKeySet()
|
||||
jwks, err := object.GetJsonWebKeySet()
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
|
@@ -18,8 +18,8 @@ import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/astaxie/beego/utils/pagination"
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetOrganizations ...
|
||||
@@ -33,13 +33,17 @@ func (c *ApiController) GetOrganizations() {
|
||||
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.GetMaskedOrganizations(object.GetOrganizations(owner))
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetOrganizationCount(owner)))
|
||||
organizations := object.GetMaskedOrganizations(object.GetPaginationOrganizations(owner, paginator.Offset(), limit))
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetOrganizationCount(owner, field, value)))
|
||||
organizations := object.GetMaskedOrganizations(object.GetPaginationOrganizations(owner, paginator.Offset(), limit, field, value, sortField, sortOrder))
|
||||
c.ResponseOk(organizations, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
116
controllers/permission.go
Normal file
116
controllers/permission.go
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright 2021 The casbin 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"
|
||||
)
|
||||
|
||||
// GetPermissions
|
||||
// @Title GetPermissions
|
||||
// @Tag Permission API
|
||||
// @Description get permissions
|
||||
// @Param owner query string true "The owner of permissions"
|
||||
// @Success 200 {array} object.Permission The Response object
|
||||
// @router /get-permissions [get]
|
||||
func (c *ApiController) GetPermissions() {
|
||||
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.GetPermissions(owner)
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetPermissionCount(owner, field, value)))
|
||||
permissions := object.GetPaginationPermissions(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
c.ResponseOk(permissions, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// @Title GetPermission
|
||||
// @Tag Permission API
|
||||
// @Description get permission
|
||||
// @Param id query string true "The id of the permission"
|
||||
// @Success 200 {object} object.Permission The Response object
|
||||
// @router /get-permission [get]
|
||||
func (c *ApiController) GetPermission() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
c.Data["json"] = object.GetPermission(id)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title UpdatePermission
|
||||
// @Tag Permission API
|
||||
// @Description update permission
|
||||
// @Param id query string true "The id of the permission"
|
||||
// @Param body body object.Permission true "The details of the permission"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-permission [post]
|
||||
func (c *ApiController) UpdatePermission() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
var permission object.Permission
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &permission)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdatePermission(id, &permission))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title AddPermission
|
||||
// @Tag Permission API
|
||||
// @Description add permission
|
||||
// @Param body body object.Permission true "The details of the permission"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-permission [post]
|
||||
func (c *ApiController) AddPermission() {
|
||||
var permission object.Permission
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &permission)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddPermission(&permission))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title DeletePermission
|
||||
// @Tag Permission API
|
||||
// @Description delete permission
|
||||
// @Param body body object.Permission true "The details of the permission"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-permission [post]
|
||||
func (c *ApiController) DeletePermission() {
|
||||
var permission object.Permission
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &permission)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeletePermission(&permission))
|
||||
c.ServeJSON()
|
||||
}
|
@@ -16,10 +16,9 @@ package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/astaxie/beego/utils/pagination"
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetProviders
|
||||
@@ -33,13 +32,17 @@ func (c *ApiController) GetProviders() {
|
||||
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.GetProviders(owner)
|
||||
c.Data["json"] = object.GetMaskedProviders(object.GetProviders(owner))
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetProviderCount(owner)))
|
||||
providers := object.GetPaginationProviders(owner, paginator.Offset(), limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetProviderCount(owner, field, value)))
|
||||
providers := object.GetMaskedProviders(object.GetPaginationProviders(owner, paginator.Offset(), limit, field, value, sortField, sortOrder))
|
||||
c.ResponseOk(providers, paginator.Nums())
|
||||
}
|
||||
}
|
||||
@@ -53,7 +56,7 @@ func (c *ApiController) GetProviders() {
|
||||
func (c *ApiController) GetProvider() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
c.Data["json"] = object.GetProvider(id)
|
||||
c.Data["json"] = object.GetMaskedProvider(object.GetProvider(id))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
|
@@ -16,8 +16,8 @@ package controllers
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego/utils/pagination"
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetRecords
|
||||
@@ -31,13 +31,17 @@ import (
|
||||
func (c *ApiController) GetRecords() {
|
||||
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.GetRecords()
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetRecordCount()))
|
||||
records := object.GetPaginationRecords(paginator.Offset(), limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetRecordCount(field, value)))
|
||||
records := object.GetPaginationRecords(paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
c.ResponseOk(records, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
@@ -23,8 +23,8 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/astaxie/beego/utils/pagination"
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// @router /get-resources [get]
|
||||
@@ -35,13 +35,17 @@ func (c *ApiController) GetResources() {
|
||||
user := c.Input().Get("user")
|
||||
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.GetResources(owner, user)
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetResourceCount(owner, user)))
|
||||
resources := object.GetPaginationResources(owner, user, paginator.Offset(), limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetResourceCount(owner, user, field, value)))
|
||||
resources := object.GetPaginationResources(owner, user, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
c.ResponseOk(resources, paginator.Nums())
|
||||
}
|
||||
}
|
||||
@@ -198,7 +202,7 @@ func (c *ApiController) UploadResource() {
|
||||
}
|
||||
|
||||
user.Avatar = fileUrl
|
||||
object.UpdateUser(user.GetId(), user, []string{"avatar"})
|
||||
object.UpdateUser(user.GetId(), user, []string{"avatar"}, false)
|
||||
case "termsOfUse":
|
||||
applicationId := fmt.Sprintf("admin/%s", parent)
|
||||
app := object.GetApplication(applicationId)
|
||||
|
116
controllers/role.go
Normal file
116
controllers/role.go
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright 2021 The casbin 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"
|
||||
)
|
||||
|
||||
// GetRoles
|
||||
// @Title GetRoles
|
||||
// @Tag Role API
|
||||
// @Description get roles
|
||||
// @Param owner query string true "The owner of roles"
|
||||
// @Success 200 {array} object.Role The Response object
|
||||
// @router /get-roles [get]
|
||||
func (c *ApiController) GetRoles() {
|
||||
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.GetRoles(owner)
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetRoleCount(owner, field, value)))
|
||||
roles := object.GetPaginationRoles(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
c.ResponseOk(roles, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// @Title GetRole
|
||||
// @Tag Role API
|
||||
// @Description get role
|
||||
// @Param id query string true "The id of the role"
|
||||
// @Success 200 {object} object.Role The Response object
|
||||
// @router /get-role [get]
|
||||
func (c *ApiController) GetRole() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
c.Data["json"] = object.GetRole(id)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title UpdateRole
|
||||
// @Tag Role API
|
||||
// @Description update role
|
||||
// @Param id query string true "The id of the role"
|
||||
// @Param body body object.Role true "The details of the role"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-role [post]
|
||||
func (c *ApiController) UpdateRole() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
var role object.Role
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &role)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateRole(id, &role))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title AddRole
|
||||
// @Tag Role API
|
||||
// @Description add role
|
||||
// @Param body body object.Role true "The details of the role"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-role [post]
|
||||
func (c *ApiController) AddRole() {
|
||||
var role object.Role
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &role)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddRole(&role))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title DeleteRole
|
||||
// @Tag Role API
|
||||
// @Description delete role
|
||||
// @Param body body object.Role true "The details of the role"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-role [post]
|
||||
func (c *ApiController) DeleteRole() {
|
||||
var role object.Role
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &role)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteRole(&role))
|
||||
c.ServeJSON()
|
||||
}
|
@@ -21,8 +21,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// SendEmail
|
||||
|
@@ -18,8 +18,8 @@ import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/astaxie/beego/utils/pagination"
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetSyncers
|
||||
@@ -33,13 +33,17 @@ func (c *ApiController) GetSyncers() {
|
||||
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.GetSyncers(owner)
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetSyncerCount(owner)))
|
||||
syncers := object.GetPaginationSyncers(owner, paginator.Offset(), limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetSyncerCount(owner, field, value)))
|
||||
syncers := object.GetPaginationSyncers(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
c.ResponseOk(syncers, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
@@ -18,8 +18,8 @@ import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/astaxie/beego/utils/pagination"
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetTokens
|
||||
@@ -35,13 +35,17 @@ func (c *ApiController) GetTokens() {
|
||||
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.GetTokens(owner)
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetTokenCount(owner)))
|
||||
tokens := object.GetPaginationTokens(owner, paginator.Offset(), limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetTokenCount(owner, field, value)))
|
||||
tokens := object.GetPaginationTokens(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
c.ResponseOk(tokens, paginator.Nums())
|
||||
}
|
||||
}
|
||||
@@ -138,7 +142,15 @@ func (c *ApiController) GetOAuthCode() {
|
||||
state := c.Input().Get("state")
|
||||
nonce := c.Input().Get("nonce")
|
||||
|
||||
c.Data["json"] = object.GetOAuthCode(userId, clientId, responseType, redirectUri, scope, state, nonce)
|
||||
challengeMethod := c.Input().Get("code_challenge_method")
|
||||
codeChallenge := c.Input().Get("code_challenge")
|
||||
|
||||
if challengeMethod != "S256" && challengeMethod != "null" {
|
||||
c.ResponseError("Challenge method should be S256")
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = object.GetOAuthCode(userId, clientId, responseType, redirectUri, scope, state, nonce, codeChallenge)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
@@ -157,12 +169,13 @@ func (c *ApiController) GetOAuthToken() {
|
||||
clientId := c.Input().Get("client_id")
|
||||
clientSecret := c.Input().Get("client_secret")
|
||||
code := c.Input().Get("code")
|
||||
verifier := c.Input().Get("code_verifier")
|
||||
|
||||
if clientId == "" && clientSecret == "" {
|
||||
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
|
||||
}
|
||||
|
||||
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code)
|
||||
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
|
@@ -20,8 +20,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego/utils/pagination"
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetGlobalUsers
|
||||
@@ -33,13 +33,17 @@ import (
|
||||
func (c *ApiController) GetGlobalUsers() {
|
||||
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.GetMaskedUsers(object.GetGlobalUsers())
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetGlobalUserCount()))
|
||||
users := object.GetPaginationGlobalUsers(paginator.Offset(), limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetGlobalUserCount(field, value)))
|
||||
users := object.GetPaginationGlobalUsers(paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
c.ResponseOk(users, paginator.Nums())
|
||||
}
|
||||
}
|
||||
@@ -55,13 +59,17 @@ func (c *ApiController) GetUsers() {
|
||||
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.GetMaskedUsers(object.GetUsers(owner))
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetUserCount(owner)))
|
||||
users := object.GetPaginationUsers(owner, paginator.Offset(), limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetUserCount(owner, field, value)))
|
||||
users := object.GetPaginationUsers(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
c.ResponseOk(users, paginator.Nums())
|
||||
}
|
||||
}
|
||||
@@ -117,7 +125,8 @@ func (c *ApiController) UpdateUser() {
|
||||
columns = strings.Split(columnsStr, ",")
|
||||
}
|
||||
|
||||
affected := object.UpdateUser(id, &user, columns)
|
||||
isGlobalAdmin := c.IsGlobalAdmin()
|
||||
affected := object.UpdateUser(id, &user, columns, isGlobalAdmin)
|
||||
if affected {
|
||||
object.UpdateUserToOriginalDatabase(&user)
|
||||
}
|
||||
@@ -218,11 +227,6 @@ func (c *ApiController) SetPassword() {
|
||||
c.ResponseError("Please login first.")
|
||||
return
|
||||
}
|
||||
requestUser := object.GetUser(requestUserId)
|
||||
if requestUser == nil {
|
||||
c.ResponseError("Session outdated. Please login again.")
|
||||
return
|
||||
}
|
||||
|
||||
userId := fmt.Sprintf("%s/%s", userOwner, userName)
|
||||
targetUser := object.GetUser(userId)
|
||||
@@ -232,15 +236,22 @@ func (c *ApiController) SetPassword() {
|
||||
}
|
||||
|
||||
hasPermission := false
|
||||
|
||||
if requestUser.IsGlobalAdmin {
|
||||
hasPermission = true
|
||||
} else if requestUserId == userId {
|
||||
hasPermission = true
|
||||
} else if targetUser.Owner == requestUser.Owner && requestUser.IsAdmin {
|
||||
if strings.HasPrefix(requestUserId, "app/") {
|
||||
hasPermission = true
|
||||
} else {
|
||||
requestUser := object.GetUser(requestUserId)
|
||||
if requestUser == nil {
|
||||
c.ResponseError("Session outdated. Please login again.")
|
||||
return
|
||||
}
|
||||
if requestUser.IsGlobalAdmin {
|
||||
hasPermission = true
|
||||
} else if requestUserId == userId {
|
||||
hasPermission = true
|
||||
} else if targetUser.Owner == requestUser.Owner && requestUser.IsAdmin {
|
||||
hasPermission = true
|
||||
}
|
||||
}
|
||||
|
||||
if !hasPermission {
|
||||
c.ResponseError("You don't have the permission to do this.")
|
||||
return
|
||||
@@ -264,8 +275,6 @@ func (c *ApiController) SetPassword() {
|
||||
return
|
||||
}
|
||||
|
||||
c.SetSessionUsername("")
|
||||
|
||||
targetUser.Password = newPassword
|
||||
object.SetUserField(targetUser, "password", targetUser.Password)
|
||||
c.Data["json"] = Response{Status: "ok"}
|
||||
@@ -322,7 +331,7 @@ func (c *ApiController) GetUserCount() {
|
||||
|
||||
count := 0
|
||||
if isOnline == "" {
|
||||
count = object.GetUserCount(owner)
|
||||
count = object.GetUserCount(owner, "", "")
|
||||
} else {
|
||||
count = object.GetOnlineUserCount(owner, util.ParseInt(isOnline))
|
||||
}
|
||||
|
60
controllers/user_upload.go
Normal file
60
controllers/user_upload.go
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"os"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func saveFile(path string, file *multipart.File) {
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = io.Copy(f, *file)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ApiController) UploadUsers() {
|
||||
userId := c.GetSessionUsername()
|
||||
owner, user := util.GetOwnerAndNameFromId(userId)
|
||||
|
||||
file, header, err := c.Ctx.Request.FormFile("file")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fileId := fmt.Sprintf("%s_%s_%s", owner, user, util.RemoveExt(header.Filename))
|
||||
|
||||
path := util.GetUploadXlsxPath(fileId)
|
||||
util.EnsureFileFolderExists(path)
|
||||
saveFile(path, &file)
|
||||
|
||||
affected := object.UploadUsers(owner, fileId)
|
||||
if affected {
|
||||
c.ResponseOk()
|
||||
} else {
|
||||
c.ResponseError("Failed to import users")
|
||||
}
|
||||
}
|
@@ -19,8 +19,8 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// ResponseOk ...
|
||||
|
@@ -19,8 +19,8 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func (c *ApiController) getCurrentUser() *object.User {
|
||||
|
@@ -18,8 +18,8 @@ import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/astaxie/beego/utils/pagination"
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetWebhooks
|
||||
@@ -33,13 +33,17 @@ func (c *ApiController) GetWebhooks() {
|
||||
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.GetWebhooks(owner)
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetWebhookCount(owner)))
|
||||
webhooks := object.GetPaginationWebhooks(owner, paginator.Offset(), limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetWebhookCount(owner, field, value)))
|
||||
webhooks := object.GetPaginationWebhooks(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
c.ResponseOk(webhooks, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
23
cred/bcrypt.go
Normal file
23
cred/bcrypt.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package cred
|
||||
|
||||
import "golang.org/x/crypto/bcrypt"
|
||||
|
||||
type BcryptCredManager struct{}
|
||||
|
||||
func NewBcryptCredManager() *BcryptCredManager {
|
||||
cm := &BcryptCredManager{}
|
||||
return cm
|
||||
}
|
||||
|
||||
func (cm *BcryptCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(bytes)
|
||||
}
|
||||
|
||||
func (cm *BcryptCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hashedPwd), []byte(plainPwd))
|
||||
return err == nil
|
||||
}
|
@@ -15,7 +15,8 @@
|
||||
package cred
|
||||
|
||||
type CredManager interface {
|
||||
GetSealedPassword(password string, userSalt string, organizationSalt string) string
|
||||
GetHashedPassword(password string, userSalt string, organizationSalt string) string
|
||||
IsPasswordCorrect(password string, passwordHash string, userSalt string, organizationSalt string) bool
|
||||
}
|
||||
|
||||
func GetCredManager(passwordType string) CredManager {
|
||||
@@ -25,7 +26,8 @@ func GetCredManager(passwordType string) CredManager {
|
||||
return NewSha256SaltCredManager()
|
||||
} else if passwordType == "md5-salt" {
|
||||
return NewMd5UserSaltCredManager()
|
||||
} else if passwordType == "bcrypt" {
|
||||
return NewBcryptCredManager()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@@ -37,8 +37,12 @@ func NewMd5UserSaltCredManager() *Sha256SaltCredManager {
|
||||
return cm
|
||||
}
|
||||
|
||||
func (cm *Md5UserSaltCredManager) GetSealedPassword(password string, userSalt string, organizationSalt string) string {
|
||||
func (cm *Md5UserSaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
||||
hash := getMd5HexDigest(password)
|
||||
res := getMd5HexDigest(hash + userSalt)
|
||||
return res
|
||||
}
|
||||
|
||||
func (cm *Md5UserSaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
|
||||
return hashedPwd == cm.GetHashedPassword(plainPwd, userSalt, organizationSalt)
|
||||
}
|
||||
|
@@ -21,6 +21,10 @@ func NewPlainCredManager() *PlainCredManager {
|
||||
return cm
|
||||
}
|
||||
|
||||
func (cm *PlainCredManager) GetSealedPassword(password string, userSalt string, organizationSalt string) string {
|
||||
func (cm *PlainCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
||||
return password
|
||||
}
|
||||
|
||||
func (cm *PlainCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
|
||||
return hashedPwd == plainPwd
|
||||
}
|
||||
|
@@ -37,8 +37,12 @@ func NewSha256SaltCredManager() *Sha256SaltCredManager {
|
||||
return cm
|
||||
}
|
||||
|
||||
func (cm *Sha256SaltCredManager) GetSealedPassword(password string, userSalt string, organizationSalt string) string {
|
||||
func (cm *Sha256SaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
||||
hash := getSha256HexDigest(password)
|
||||
res := getSha256HexDigest(hash + organizationSalt)
|
||||
return res
|
||||
}
|
||||
|
||||
func (cm *Sha256SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
|
||||
return hashedPwd == cm.GetHashedPassword(plainPwd, userSalt, organizationSalt)
|
||||
}
|
||||
|
@@ -23,5 +23,5 @@ func TestGetSaltedPassword(t *testing.T) {
|
||||
password := "123456"
|
||||
salt := "123"
|
||||
cm := NewSha256SaltCredManager()
|
||||
fmt.Printf("%s -> %s\n", password, cm.GetSealedPassword(password, "", salt))
|
||||
fmt.Printf("%s -> %s\n", password, cm.GetHashedPassword(password, "", salt))
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
version: '3.1'
|
||||
services:
|
||||
restart: always
|
||||
casdoor:
|
||||
build:
|
||||
context: ./
|
||||
|
10
go.mod
10
go.mod
@@ -1,4 +1,4 @@
|
||||
module github.com/casbin/casdoor
|
||||
module github.com/casdoor/casdoor
|
||||
|
||||
go 1.16
|
||||
|
||||
@@ -8,7 +8,7 @@ require (
|
||||
github.com/aws/aws-sdk-go v1.37.30
|
||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
|
||||
github.com/casbin/casbin/v2 v2.30.1
|
||||
github.com/casbin/xorm-adapter/v2 v2.3.1
|
||||
github.com/casbin/xorm-adapter/v2 v2.5.1
|
||||
github.com/casdoor/go-sms-sender v0.0.5
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
|
||||
@@ -18,15 +18,17 @@ require (
|
||||
github.com/google/uuid v1.2.0
|
||||
github.com/jinzhu/configor v1.2.1 // indirect
|
||||
github.com/markbates/goth v1.68.1-0.20211006204042-9dc8905b41c8
|
||||
github.com/mileusna/crontab v1.0.1
|
||||
github.com/qiangmzsx/string-adapter/v2 v2.1.0
|
||||
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/russellhaering/gosaml2 v0.6.0
|
||||
github.com/russellhaering/goxmldsig v1.1.1
|
||||
github.com/satori/go.uuid v1.2.0 // indirect
|
||||
github.com/smartystreets/goconvey v1.6.4 // indirect
|
||||
github.com/tealeg/xlsx v1.0.5
|
||||
github.com/thanhpk/randstr v1.0.4
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
|
||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
|
27
go.sum
27
go.sum
@@ -74,11 +74,11 @@ github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6
|
||||
github.com/casbin/casbin v1.7.0 h1:PuzlE8w0JBg/DhIqnkF1Dewf3z+qmUZMVN07PonvVUQ=
|
||||
github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
|
||||
github.com/casbin/casbin/v2 v2.1.0/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
||||
github.com/casbin/casbin/v2 v2.25.5/go.mod h1:wUgota0cQbTXE6Vd+KWpg41726jFRi7upxio0sR+Xd0=
|
||||
github.com/casbin/casbin/v2 v2.28.3/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
|
||||
github.com/casbin/casbin/v2 v2.30.1 h1:P5HWadDL7olwUXNdcuKUBk+x75Y2eitFxYTcLNKeKF0=
|
||||
github.com/casbin/casbin/v2 v2.30.1/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
|
||||
github.com/casbin/xorm-adapter/v2 v2.3.1 h1:RVGsM6KYFP9s4OQJXrP/gv56Wmt5P40mzvcyXgv5xeg=
|
||||
github.com/casbin/xorm-adapter/v2 v2.3.1/go.mod h1:GZ+nlIdasVFunQ71SlvkL/HcQQBvFncphDf+2Yl167c=
|
||||
github.com/casbin/xorm-adapter/v2 v2.5.1 h1:BkpIxRHKa0s3bSMx173PpuU7oTs+Zw7XmD0BIta0HGM=
|
||||
github.com/casbin/xorm-adapter/v2 v2.5.1/go.mod h1:AeH4dBKHC9/zYxzdPVHhPDzF8LYLqjDdb767CWJoV54=
|
||||
github.com/casdoor/go-sms-sender v0.0.5 h1:9qhlMM+UoSOvvY7puUULqSHBBA7fbe02Px/tzchQboo=
|
||||
github.com/casdoor/go-sms-sender v0.0.5/go.mod h1:TMM/BsZQAa+7JVDXl2KqgxnzZgCjmHEX5MBN662mM5M=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
@@ -264,8 +264,6 @@ github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJK
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mileusna/crontab v1.0.1 h1:YrDLc7l3xOiznmXq2FtAgg+1YQ3yC6pfFVPe+ywXNtg=
|
||||
github.com/mileusna/crontab v1.0.1/go.mod h1:dbns64w/u3tUnGZGf8pAa76ZqOfeBX4olW4U1ZwExmc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -313,6 +311,8 @@ github.com/qiangmzsx/string-adapter/v2 v2.1.0 h1:q0y8TPa/sTwtriJPRe8gWL++PuZ+XbO
|
||||
github.com/qiangmzsx/string-adapter/v2 v2.1.0/go.mod h1:PElPB7b7HnGKTsuADAffFpOQXHqjEGJz1+U1a6yR5wA=
|
||||
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76 h1:J2Xj92efYLxPl3BiibgEDEUiMsCBzwTurE/8JjD8CG4=
|
||||
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76/go.mod h1:JhtPzUhP5KGtCB2yksmxuYAD4hEWw4qGQJpucjsm3U0=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
@@ -349,6 +349,8 @@ github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2K
|
||||
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
|
||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||
github.com/tealeg/xlsx v1.0.5 h1:+f8oFmvY8Gw1iUXzPk+kz+4GpbDZPK1FhPiQRd+ypgE=
|
||||
github.com/tealeg/xlsx v1.0.5/go.mod h1:btRS8dz54TDnvKNosuAqxrM1QgN1udgk9O34bDCnORM=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go v1.0.154 h1:THBgwGwUQtsw6L53cSSA2wwL3sLrm+HJ3Dk+ye/lMCI=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go v1.0.154/go.mod h1:asUz5BPXxgoPGaRgZaVm1iGcUAuHyYUo1nXqKa83cvI=
|
||||
github.com/thanhpk/randstr v1.0.4 h1:IN78qu/bR+My+gHCvMEXhR/i5oriVHcTB/BJJIRTsNo=
|
||||
@@ -375,8 +377,9 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -440,8 +443,8 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R
|
||||
golang.org/x/net v0.0.0-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -493,15 +496,17 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44 h1:Bli41pIlzTzf3KEY06n+xnzK/BESIg2ze4Pgfh/aI8c=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
@@ -21,7 +21,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
type I18nData map[string]map[string]string
|
||||
|
@@ -16,8 +16,9 @@ package i18n
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func getI18nFilePath(language string) string {
|
||||
@@ -37,6 +38,7 @@ func readI18nFile(language string) *I18nData {
|
||||
|
||||
func writeI18nFile(language string, data *I18nData) {
|
||||
s := util.StructToJsonFormatted(data)
|
||||
s = strings.ReplaceAll(s, "\\u0026", "&")
|
||||
println(s)
|
||||
|
||||
util.WriteStringToPath(s, getI18nFilePath(language))
|
||||
|
116
idp/baidu.go
Normal file
116
idp/baidu.go
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package idp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type BaiduIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
}
|
||||
|
||||
func NewBaiduIdProvider(clientId string, clientSecret string, redirectUrl string) *BaiduIdProvider {
|
||||
idp := &BaiduIdProvider{}
|
||||
|
||||
config := idp.getConfig()
|
||||
config.ClientID = clientId
|
||||
config.ClientSecret = clientSecret
|
||||
config.RedirectURL = redirectUrl
|
||||
idp.Config = config
|
||||
|
||||
return idp
|
||||
}
|
||||
|
||||
func (idp *BaiduIdProvider) SetHttpClient(client *http.Client) {
|
||||
idp.Client = client
|
||||
}
|
||||
|
||||
func (idp *BaiduIdProvider) getConfig() *oauth2.Config {
|
||||
var endpoint = oauth2.Endpoint{
|
||||
AuthURL: "https://openapi.baidu.com/oauth/2.0/authorize",
|
||||
TokenURL: "https://openapi.baidu.com/oauth/2.0/token",
|
||||
}
|
||||
|
||||
var config = &oauth2.Config{
|
||||
Scopes: []string{"email"},
|
||||
Endpoint: endpoint,
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func (idp *BaiduIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, idp.Client)
|
||||
return idp.Config.Exchange(ctx, code)
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
"userid":"2097322476",
|
||||
"username":"wl19871011",
|
||||
"realname":"阳光",
|
||||
"userdetail":"喜欢自由",
|
||||
"birthday":"1987-01-01",
|
||||
"marriage":"恋爱",
|
||||
"sex":"男",
|
||||
"blood":"O",
|
||||
"constellation":"射手",
|
||||
"figure":"小巧",
|
||||
"education":"大学/专科",
|
||||
"trade":"计算机/电子产品",
|
||||
"job":"未知",
|
||||
"birthday_year":"1987",
|
||||
"birthday_month":"01",
|
||||
"birthday_day":"01",
|
||||
}
|
||||
*/
|
||||
|
||||
type BaiduUserInfo struct {
|
||||
OpenId string `json:"openid"`
|
||||
Username string `json:"username"`
|
||||
Portrait string `json:"portrait"`
|
||||
}
|
||||
|
||||
func (idp *BaiduIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
resp, err := idp.Client.Get(fmt.Sprintf("https://openapi.baidu.com/rest/2.0/passport/users/getInfo?access_token=%s", token.AccessToken))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
baiduUser := BaiduUserInfo{}
|
||||
if err = json.Unmarshal(data, &baiduUser); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userInfo := UserInfo{
|
||||
Id: baiduUser.OpenId,
|
||||
Username: baiduUser.Username,
|
||||
DisplayName: baiduUser.Username,
|
||||
AvatarUrl: fmt.Sprintf("https://himg.bdimg.com/sys/portrait/item/%s", baiduUser.Portrait),
|
||||
}
|
||||
return &userInfo, nil
|
||||
}
|
23
idp/goth.go
23
idp/goth.go
@@ -21,6 +21,7 @@ import (
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/markbates/goth"
|
||||
"github.com/markbates/goth/providers/amazon"
|
||||
"github.com/markbates/goth/providers/apple"
|
||||
@@ -244,11 +245,27 @@ func getUser(gothUser goth.User) *UserInfo {
|
||||
//Some idp return an empty Name
|
||||
//so construct the Name with firstname and lastname or nickname
|
||||
if user.Username == "" {
|
||||
user.Username = fmt.Sprintf("%v%v", gothUser.FirstName, gothUser.LastName)
|
||||
if gothUser.FirstName != "" && gothUser.LastName != "" {
|
||||
user.Username = getName(gothUser.FirstName, gothUser.LastName)
|
||||
} else {
|
||||
user.Username = gothUser.NickName
|
||||
}
|
||||
}
|
||||
if user.Username == "" {
|
||||
user.Username = gothUser.NickName
|
||||
if user.DisplayName == "" {
|
||||
if gothUser.FirstName != "" && gothUser.LastName != "" {
|
||||
user.DisplayName = getName(gothUser.FirstName, gothUser.LastName)
|
||||
} else {
|
||||
user.DisplayName = user.Username
|
||||
}
|
||||
}
|
||||
|
||||
return &user
|
||||
}
|
||||
|
||||
func getName(firstName, lastName string) string {
|
||||
if util.IsChinese(firstName) || util.IsChinese(lastName) {
|
||||
return fmt.Sprintf("%s%s", lastName, firstName)
|
||||
} else {
|
||||
return fmt.Sprintf("%s %s", firstName, lastName)
|
||||
}
|
||||
}
|
||||
|
@@ -60,6 +60,8 @@ func GetIdProvider(providerType string, clientId string, clientSecret string, re
|
||||
return NewLarkIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if providerType == "GitLab" {
|
||||
return NewGitlabIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if providerType == "Baidu" {
|
||||
return NewBaiduIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if isGothSupport(providerType) {
|
||||
return NewGothIdProvider(providerType, clientId, clientSecret, redirectUrl)
|
||||
}
|
||||
|
@@ -179,6 +179,7 @@ func (idp *WeChatIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
|
||||
|
||||
userInfo := UserInfo{
|
||||
Id: id,
|
||||
Username: wechatUserInfo.Nickname,
|
||||
DisplayName: wechatUserInfo.Nickname,
|
||||
AvatarUrl: wechatUserInfo.Headimgurl,
|
||||
}
|
||||
|
17
main.go
17
main.go
@@ -15,20 +15,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/astaxie/beego/plugins/cors"
|
||||
_ "github.com/astaxie/beego/session/redis"
|
||||
"github.com/casbin/casdoor/authz"
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/proxy"
|
||||
"github.com/casbin/casdoor/routers"
|
||||
|
||||
_ "github.com/casbin/casdoor/routers"
|
||||
"github.com/casdoor/casdoor/authz"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
"github.com/casdoor/casdoor/routers"
|
||||
_ "github.com/casdoor/casdoor/routers"
|
||||
)
|
||||
|
||||
func main() {
|
||||
object.InitAdapter()
|
||||
createDatabase := flag.Bool("createDatabase", false, "true if you need casdoor to create database")
|
||||
flag.Parse()
|
||||
object.InitAdapter(*createDatabase)
|
||||
object.InitDb()
|
||||
object.InitDefaultStorageProvider()
|
||||
object.InitLdapAutoSynchronizer()
|
||||
|
@@ -17,11 +17,14 @@ package object
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"xorm.io/core"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casbin/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
//_ "github.com/denisenkom/go-mssqldb" // db = mssql
|
||||
_ "github.com/go-sql-driver/mysql" // db = mysql
|
||||
//_ "github.com/lib/pq" // db = postgres
|
||||
//_ "github.com/lib/pq" // db = postgres
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
@@ -33,11 +36,15 @@ func InitConfig() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
InitAdapter()
|
||||
InitAdapter(true)
|
||||
}
|
||||
|
||||
func InitAdapter() {
|
||||
func InitAdapter(createDatabase bool) {
|
||||
|
||||
adapter = NewAdapter(beego.AppConfig.String("driverName"), conf.GetBeegoConfDataSourceName(), beego.AppConfig.String("dbName"))
|
||||
if createDatabase {
|
||||
adapter.CreateDatabase()
|
||||
}
|
||||
adapter.createTable()
|
||||
}
|
||||
|
||||
@@ -73,7 +80,7 @@ func NewAdapter(driverName string, dataSourceName string, dbName string) *Adapte
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *Adapter) createDatabase() error {
|
||||
func (a *Adapter) CreateDatabase() error {
|
||||
engine, err := xorm.NewEngine(a.driverName, a.dataSourceName)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -85,13 +92,12 @@ func (a *Adapter) createDatabase() error {
|
||||
}
|
||||
|
||||
func (a *Adapter) open() {
|
||||
if a.driverName != "postgres" {
|
||||
if err := a.createDatabase(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
dataSourceName := a.dataSourceName + a.dbName
|
||||
if a.driverName != "mysql" {
|
||||
dataSourceName = a.dataSourceName
|
||||
}
|
||||
|
||||
engine, err := xorm.NewEngine(a.driverName, a.dataSourceName+a.dbName)
|
||||
engine, err := xorm.NewEngine(a.driverName, dataSourceName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -105,6 +111,13 @@ func (a *Adapter) close() {
|
||||
}
|
||||
|
||||
func (a *Adapter) createTable() {
|
||||
showSql, _ := beego.AppConfig.Bool("showSql")
|
||||
a.Engine.ShowSQL(showSql)
|
||||
|
||||
tableNamePrefix := beego.AppConfig.String("tableNamePrefix")
|
||||
tbMapper := core.NewPrefixMapper(core.SnakeMapper{}, tableNamePrefix)
|
||||
a.Engine.SetTableMapper(tbMapper)
|
||||
|
||||
err := a.Engine.Sync2(new(Organization))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -115,6 +128,16 @@ func (a *Adapter) createTable() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Role))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Permission))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Provider))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -155,8 +178,32 @@ func (a *Adapter) createTable() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Cert))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Ldap))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func GetSession(owner string, offset, limit int, field, value, sortField, sortOrder string) *xorm.Session {
|
||||
session := adapter.Engine.Limit(limit, offset).Where("1=1")
|
||||
if owner != "" {
|
||||
session = session.And("owner=?", owner)
|
||||
}
|
||||
if field != "" && value != "" {
|
||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
||||
}
|
||||
if sortField == "" || sortOrder == "" {
|
||||
sortField = "created_time"
|
||||
}
|
||||
if sortOrder == "ascend" {
|
||||
session = session.Asc(util.SnakeString(sortField))
|
||||
} else {
|
||||
session = session.Desc(util.SnakeString(sortField))
|
||||
}
|
||||
return session
|
||||
}
|
||||
|
@@ -15,7 +15,9 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"github.com/casbin/casdoor/util"
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
@@ -24,17 +26,19 @@ type Application struct {
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
Logo string `xorm:"varchar(100)" json:"logo"`
|
||||
HomepageUrl string `xorm:"varchar(100)" json:"homepageUrl"`
|
||||
Description string `xorm:"varchar(100)" json:"description"`
|
||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||
EnablePassword bool `json:"enablePassword"`
|
||||
EnableSignUp bool `json:"enableSignUp"`
|
||||
EnableCodeSignin bool `json:"enableCodeSignin"`
|
||||
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
|
||||
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
|
||||
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
Logo string `xorm:"varchar(100)" json:"logo"`
|
||||
HomepageUrl string `xorm:"varchar(100)" json:"homepageUrl"`
|
||||
Description string `xorm:"varchar(100)" json:"description"`
|
||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||
Cert string `xorm:"varchar(100)" json:"cert"`
|
||||
EnablePassword bool `json:"enablePassword"`
|
||||
EnableSignUp bool `json:"enableSignUp"`
|
||||
EnableSigninSession bool `json:"enableSigninSession"`
|
||||
EnableCodeSignin bool `json:"enableCodeSignin"`
|
||||
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
|
||||
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
|
||||
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
|
||||
|
||||
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
||||
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
||||
@@ -51,8 +55,12 @@ type Application struct {
|
||||
SigninHtml string `xorm:"mediumtext" json:"signinHtml"`
|
||||
}
|
||||
|
||||
func GetApplicationCount(owner string) int {
|
||||
count, err := adapter.Engine.Count(&Application{Owner: owner})
|
||||
func GetApplicationCount(owner, field, value string) int {
|
||||
session := adapter.Engine.Where("owner=?", owner)
|
||||
if field != "" && value != "" {
|
||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
||||
}
|
||||
count, err := session.Count(&Application{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -70,9 +78,20 @@ func GetApplications(owner string) []*Application {
|
||||
return applications
|
||||
}
|
||||
|
||||
func GetPaginationApplications(owner string, offset, limit int) []*Application {
|
||||
func GetPaginationApplications(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Application {
|
||||
applications := []*Application{}
|
||||
err := adapter.Engine.Desc("created_time").Limit(limit, offset).Find(&applications, &Application{Owner: owner})
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&applications)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return applications
|
||||
}
|
||||
|
||||
func GetApplicationsByOrganizationName(owner string, organization string) []*Application {
|
||||
applications := []*Application{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&applications, &Application{Owner: owner, Organization: organization})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -88,7 +107,7 @@ func getProviderMap(owner string) map[string]*Provider {
|
||||
// continue
|
||||
//}
|
||||
|
||||
m[provider.Name] = getMaskedProvider(provider)
|
||||
m[provider.Name] = GetMaskedProvider(provider)
|
||||
}
|
||||
return m
|
||||
}
|
||||
@@ -182,30 +201,47 @@ func GetApplicationByClientId(clientId string) *Application {
|
||||
}
|
||||
}
|
||||
|
||||
func GetApplicationByClientIdAndSecret(clientId, clientSecret string) *Application {
|
||||
if util.IsStrsEmpty(clientId, clientSecret) {
|
||||
return nil
|
||||
}
|
||||
|
||||
app := GetApplicationByClientId(clientId)
|
||||
if app == nil || app.ClientSecret != clientSecret {
|
||||
return nil
|
||||
}
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
func GetApplication(id string) *Application {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
return getApplication(owner, name)
|
||||
}
|
||||
|
||||
func GetMaskedApplication(application *Application, userId string) *Application {
|
||||
if isUserIdGlobalAdmin(userId) {
|
||||
return application
|
||||
}
|
||||
|
||||
if application == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if application.ClientSecret != "" {
|
||||
application.ClientSecret = "***"
|
||||
}
|
||||
return application
|
||||
}
|
||||
|
||||
func GetMaskedApplications(applications []*Application, userId string) []*Application {
|
||||
if isUserIdGlobalAdmin(userId) {
|
||||
return applications
|
||||
}
|
||||
|
||||
for _, application := range applications {
|
||||
application = GetMaskedApplication(application, userId)
|
||||
}
|
||||
return applications
|
||||
}
|
||||
|
||||
func UpdateApplication(id string, application *Application) bool {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
if getApplication(owner, name) == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if name == "app-built-in" {
|
||||
application.Name = name
|
||||
}
|
||||
|
||||
for _, providerItem := range application.Providers {
|
||||
providerItem.Provider = nil
|
||||
}
|
||||
@@ -234,6 +270,10 @@ func AddApplication(application *Application) bool {
|
||||
}
|
||||
|
||||
func DeleteApplication(application *Application) bool {
|
||||
if application.Name == "app-built-in" {
|
||||
return false
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{application.Owner, application.Name}).Delete(&Application{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -241,3 +281,7 @@ func DeleteApplication(application *Application) bool {
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func (application *Application) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", application.Owner, application.Name)
|
||||
}
|
||||
|
@@ -20,7 +20,7 @@ import (
|
||||
"io"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casbin/casdoor/proxy"
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
)
|
||||
|
||||
var defaultStorageProvider *Provider = nil
|
||||
|
@@ -18,7 +18,7 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/casbin/casdoor/proxy"
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
)
|
||||
|
||||
func TestSyncPermanentAvatars(t *testing.T) {
|
||||
|
164
object/cert.go
Normal file
164
object/cert.go
Normal file
@@ -0,0 +1,164 @@
|
||||
// Copyright 2021 The casbin 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 Cert 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"`
|
||||
Scope string `xorm:"varchar(100)" json:"scope"`
|
||||
Type string `xorm:"varchar(100)" json:"type"`
|
||||
CryptoAlgorithm string `xorm:"varchar(100)" json:"cryptoAlgorithm"`
|
||||
BitSize int `json:"bitSize"`
|
||||
ExpireInYears int `json:"expireInYears"`
|
||||
|
||||
PublicKey string `xorm:"mediumtext" json:"publicKey"`
|
||||
PrivateKey string `xorm:"mediumtext" json:"privateKey"`
|
||||
}
|
||||
|
||||
func GetMaskedCert(cert *Cert) *Cert {
|
||||
if cert == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return cert
|
||||
}
|
||||
|
||||
func GetMaskedCerts(certs []*Cert) []*Cert {
|
||||
for _, cert := range certs {
|
||||
cert = GetMaskedCert(cert)
|
||||
}
|
||||
return certs
|
||||
}
|
||||
|
||||
func GetCertCount(owner, field, value string) int {
|
||||
session := adapter.Engine.Where("owner=?", owner)
|
||||
if field != "" && value != "" {
|
||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
||||
}
|
||||
count, err := session.Count(&Cert{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return int(count)
|
||||
}
|
||||
|
||||
func GetCerts(owner string) []*Cert {
|
||||
certs := []*Cert{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&certs, &Cert{Owner: owner})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return certs
|
||||
}
|
||||
|
||||
func GetPaginationCerts(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Cert {
|
||||
certs := []*Cert{}
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&certs)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return certs
|
||||
}
|
||||
|
||||
func getCert(owner string, name string) *Cert {
|
||||
if owner == "" || name == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
cert := Cert{Owner: owner, Name: name}
|
||||
existed, err := adapter.Engine.Get(&cert)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &cert
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetCert(id string) *Cert {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
return getCert(owner, name)
|
||||
}
|
||||
|
||||
func UpdateCert(id string, cert *Cert) bool {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
if getCert(owner, name) == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(cert)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func AddCert(cert *Cert) bool {
|
||||
if cert.PublicKey == "" || cert.PrivateKey == "" {
|
||||
publicKey, privateKey := generateRsaKeys(cert.BitSize, cert.ExpireInYears, cert.Name, cert.Owner)
|
||||
cert.PublicKey = publicKey
|
||||
cert.PrivateKey = privateKey
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.Insert(cert)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func DeleteCert(cert *Cert) bool {
|
||||
affected, err := adapter.Engine.ID(core.PK{cert.Owner, cert.Name}).Delete(&Cert{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func (p *Cert) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
|
||||
}
|
||||
|
||||
func getCertByApplication(application *Application) *Cert {
|
||||
if application.Cert != "" {
|
||||
return getCert("admin", application.Cert)
|
||||
} else {
|
||||
return GetDefaultCert()
|
||||
}
|
||||
}
|
||||
|
||||
func GetDefaultCert() *Cert {
|
||||
return getCert("admin", "cert-built-in")
|
||||
}
|
@@ -18,8 +18,8 @@ import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/casbin/casdoor/cred"
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/cred"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
goldap "github.com/go-ldap/ldap/v3"
|
||||
)
|
||||
|
||||
@@ -107,12 +107,13 @@ func CheckPassword(user *User, password string) string {
|
||||
|
||||
credManager := cred.GetCredManager(organization.PasswordType)
|
||||
if credManager != nil {
|
||||
if organization.MasterPassword != "" && organization.MasterPassword == password {
|
||||
return ""
|
||||
if organization.MasterPassword != "" {
|
||||
if credManager.IsPasswordCorrect(password, organization.MasterPassword, "", organization.PasswordSalt) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
sealedPassword := credManager.GetSealedPassword(password, user.PasswordSalt, organization.PasswordSalt)
|
||||
if user.Password == sealedPassword {
|
||||
if credManager.IsPasswordCorrect(password, user.Password, user.PasswordSalt, organization.PasswordSalt) {
|
||||
return ""
|
||||
}
|
||||
return "password incorrect"
|
||||
|
@@ -14,12 +14,23 @@
|
||||
|
||||
package object
|
||||
|
||||
import "github.com/casbin/casdoor/util"
|
||||
import (
|
||||
_ "embed"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
//go:embed token_jwt_key.pem
|
||||
var tokenJwtPublicKey string
|
||||
|
||||
//go:embed token_jwt_key.key
|
||||
var tokenJwtPrivateKey string
|
||||
|
||||
func InitDb() {
|
||||
initBuiltInOrganization()
|
||||
initBuiltInUser()
|
||||
initBuiltInApplication()
|
||||
initBuiltInCert()
|
||||
initBuiltInLdap()
|
||||
}
|
||||
|
||||
@@ -50,26 +61,28 @@ func initBuiltInUser() {
|
||||
}
|
||||
|
||||
user = &User{
|
||||
Owner: "built-in",
|
||||
Name: "admin",
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
Id: util.GenerateId(),
|
||||
Type: "normal-user",
|
||||
Password: "123",
|
||||
DisplayName: "Admin",
|
||||
Avatar: "https://casbin.org/img/casbin.svg",
|
||||
Email: "admin@example.com",
|
||||
Phone: "12345678910",
|
||||
Address: []string{},
|
||||
Affiliation: "Example Inc.",
|
||||
Tag: "staff",
|
||||
Score: 2000,
|
||||
Ranking: 1,
|
||||
IsAdmin: true,
|
||||
IsGlobalAdmin: true,
|
||||
IsForbidden: false,
|
||||
IsDeleted: false,
|
||||
Properties: make(map[string]string),
|
||||
Owner: "built-in",
|
||||
Name: "admin",
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
Id: util.GenerateId(),
|
||||
Type: "normal-user",
|
||||
Password: "123",
|
||||
DisplayName: "Admin",
|
||||
Avatar: "https://casbin.org/img/casbin.svg",
|
||||
Email: "admin@example.com",
|
||||
Phone: "12345678910",
|
||||
Address: []string{},
|
||||
Affiliation: "Example Inc.",
|
||||
Tag: "staff",
|
||||
Score: 2000,
|
||||
Ranking: 1,
|
||||
IsAdmin: true,
|
||||
IsGlobalAdmin: true,
|
||||
IsForbidden: false,
|
||||
IsDeleted: false,
|
||||
SignupApplication: "built-in-app",
|
||||
CreatedIp: "127.0.0.1",
|
||||
Properties: make(map[string]string),
|
||||
}
|
||||
AddUser(user)
|
||||
}
|
||||
@@ -88,6 +101,7 @@ func initBuiltInApplication() {
|
||||
Logo: "https://cdn.casbin.com/logo/logo_1024x256.png",
|
||||
HomepageUrl: "https://casdoor.org",
|
||||
Organization: "built-in",
|
||||
Cert: "cert-built-in",
|
||||
EnablePassword: true,
|
||||
EnableSignUp: true,
|
||||
Providers: []*ProviderItem{},
|
||||
@@ -107,6 +121,28 @@ func initBuiltInApplication() {
|
||||
AddApplication(application)
|
||||
}
|
||||
|
||||
func initBuiltInCert() {
|
||||
cert := getCert("admin", "cert-built-in")
|
||||
if cert != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cert = &Cert{
|
||||
Owner: "admin",
|
||||
Name: "cert-built-in",
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
DisplayName: "Built-in Cert",
|
||||
Scope: "JWT",
|
||||
Type: "x509",
|
||||
CryptoAlgorithm: "RSA",
|
||||
BitSize: 4096,
|
||||
ExpireInYears: 20,
|
||||
PublicKey: tokenJwtPublicKey,
|
||||
PrivateKey: tokenJwtPrivateKey,
|
||||
}
|
||||
AddCert(cert)
|
||||
}
|
||||
|
||||
func initBuiltInLdap() {
|
||||
ldap := GetLdap("ldap-built-in")
|
||||
if ldap != nil {
|
||||
|
@@ -19,7 +19,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
goldap "github.com/go-ldap/ldap/v3"
|
||||
"github.com/thanhpk/randstr"
|
||||
)
|
||||
|
@@ -72,17 +72,20 @@ func GetOidcDiscovery() OidcDiscovery {
|
||||
return oidcDiscovery
|
||||
}
|
||||
|
||||
func GetJSONWebKeySet() (jose.JSONWebKeySet, error) {
|
||||
func GetJsonWebKeySet() (jose.JSONWebKeySet, error) {
|
||||
cert := GetDefaultCert()
|
||||
|
||||
//follows the protocol rfc 7517(draft)
|
||||
//link here: https://self-issued.info/docs/draft-ietf-jose-json-web-key.html
|
||||
//or https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key
|
||||
certPEMBlock := []byte(tokenJwtPublicKey)
|
||||
certDERBlock, _ := pem.Decode(certPEMBlock)
|
||||
x509Cert, _ := x509.ParseCertificate(certDERBlock.Bytes)
|
||||
certPemBlock := []byte(cert.PublicKey)
|
||||
certDerBlock, _ := pem.Decode(certPemBlock)
|
||||
x509Cert, _ := x509.ParseCertificate(certDerBlock.Bytes)
|
||||
|
||||
var jwk jose.JSONWebKey
|
||||
jwk.Key = x509Cert.PublicKey
|
||||
jwk.Certificates = []*x509.Certificate{x509Cert}
|
||||
jwk.KeyID = cert.Name
|
||||
|
||||
var jwks jose.JSONWebKeySet
|
||||
jwks.Keys = []jose.JSONWebKey{jwk}
|
||||
|
@@ -15,7 +15,10 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"github.com/casbin/casdoor/util"
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/cred"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
@@ -35,8 +38,12 @@ type Organization struct {
|
||||
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
||||
}
|
||||
|
||||
func GetOrganizationCount(owner string) int {
|
||||
count, err := adapter.Engine.Count(&Organization{Owner: owner})
|
||||
func GetOrganizationCount(owner, field, value string) int {
|
||||
session := adapter.Engine.Where("owner=?", owner)
|
||||
if field != "" && value != "" {
|
||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
||||
}
|
||||
count, err := session.Count(&Organization{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -54,9 +61,10 @@ func GetOrganizations(owner string) []*Organization {
|
||||
return organizations
|
||||
}
|
||||
|
||||
func GetPaginationOrganizations(owner string, offset, limit int) []*Organization {
|
||||
func GetPaginationOrganizations(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Organization {
|
||||
organizations := []*Organization{}
|
||||
err := adapter.Engine.Desc("created_time").Limit(limit, offset).Find(&organizations, &Provider{Owner: owner})
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&organizations)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -111,6 +119,26 @@ func UpdateOrganization(id string, organization *Organization) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if name == "built-in" {
|
||||
organization.Name = name
|
||||
}
|
||||
|
||||
if name != organization.Name {
|
||||
applications := GetApplicationsByOrganizationName("admin", name)
|
||||
for _, application := range applications {
|
||||
application.Organization = organization.Name
|
||||
UpdateApplication(application.GetId(), application)
|
||||
}
|
||||
}
|
||||
|
||||
if organization.MasterPassword != "" {
|
||||
credManager := cred.GetCredManager(organization.PasswordType)
|
||||
if credManager != nil {
|
||||
hashedPassword := credManager.GetHashedPassword(organization.MasterPassword, "", organization.PasswordSalt)
|
||||
organization.MasterPassword = hashedPassword
|
||||
}
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(organization)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -129,6 +157,10 @@ func AddOrganization(organization *Organization) bool {
|
||||
}
|
||||
|
||||
func DeleteOrganization(organization *Organization) bool {
|
||||
if organization.Name == "built-in" {
|
||||
return false
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{organization.Owner, organization.Name}).Delete(&Organization{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
132
object/permission.go
Normal file
132
object/permission.go
Normal file
@@ -0,0 +1,132 @@
|
||||
// Copyright 2021 The casbin 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 Permission 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"`
|
||||
|
||||
Users []string `xorm:"mediumtext" json:"users"`
|
||||
Roles []string `xorm:"mediumtext" json:"roles"`
|
||||
|
||||
ResourceType string `xorm:"varchar(100)" json:"resourceType"`
|
||||
Resources []string `xorm:"mediumtext" json:"resources"`
|
||||
Actions []string `xorm:"mediumtext" json:"actions"`
|
||||
Effect string `xorm:"varchar(100)" json:"effect"`
|
||||
|
||||
IsEnabled bool `json:"isEnabled"`
|
||||
}
|
||||
|
||||
func GetPermissionCount(owner, field, value string) int {
|
||||
session := adapter.Engine.Where("owner=?", owner)
|
||||
if field != "" && value != "" {
|
||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
||||
}
|
||||
count, err := session.Count(&Permission{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return int(count)
|
||||
}
|
||||
|
||||
func GetPermissions(owner string) []*Permission {
|
||||
permissions := []*Permission{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&permissions, &Permission{Owner: owner})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return permissions
|
||||
}
|
||||
|
||||
func GetPaginationPermissions(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Permission {
|
||||
permissions := []*Permission{}
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&permissions)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return permissions
|
||||
}
|
||||
|
||||
func getPermission(owner string, name string) *Permission {
|
||||
if owner == "" || name == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
permission := Permission{Owner: owner, Name: name}
|
||||
existed, err := adapter.Engine.Get(&permission)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &permission
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetPermission(id string) *Permission {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
return getPermission(owner, name)
|
||||
}
|
||||
|
||||
func UpdatePermission(id string, permission *Permission) bool {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
if getPermission(owner, name) == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(permission)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func AddPermission(permission *Permission) bool {
|
||||
affected, err := adapter.Engine.Insert(permission)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func DeletePermission(permission *Permission) bool {
|
||||
affected, err := adapter.Engine.ID(core.PK{permission.Owner, permission.Name}).Delete(&Permission{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func (permission *Permission) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", permission.Owner, permission.Name)
|
||||
}
|
@@ -17,7 +17,7 @@ package object
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
@@ -26,12 +26,14 @@ type Provider struct {
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
Category string `xorm:"varchar(100)" json:"category"`
|
||||
Type string `xorm:"varchar(100)" json:"type"`
|
||||
Method string `xorm:"varchar(100)" json:"method"`
|
||||
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
||||
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
Category string `xorm:"varchar(100)" json:"category"`
|
||||
Type string `xorm:"varchar(100)" json:"type"`
|
||||
Method string `xorm:"varchar(100)" json:"method"`
|
||||
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
||||
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
||||
ClientId2 string `xorm:"varchar(100)" json:"clientId2"`
|
||||
ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"`
|
||||
|
||||
Host string `xorm:"varchar(100)" json:"host"`
|
||||
Port int `json:"port"`
|
||||
@@ -56,22 +58,34 @@ type Provider struct {
|
||||
ProviderUrl string `xorm:"varchar(200)" json:"providerUrl"`
|
||||
}
|
||||
|
||||
func getMaskedProvider(provider *Provider) *Provider {
|
||||
p := &Provider{
|
||||
Owner: provider.Owner,
|
||||
Name: provider.Name,
|
||||
CreatedTime: provider.CreatedTime,
|
||||
DisplayName: provider.DisplayName,
|
||||
Category: provider.Category,
|
||||
Type: provider.Type,
|
||||
Method: provider.Method,
|
||||
ClientId: provider.ClientId,
|
||||
func GetMaskedProvider(provider *Provider) *Provider {
|
||||
if provider == nil {
|
||||
return nil
|
||||
}
|
||||
return p
|
||||
|
||||
if provider.ClientSecret != "" {
|
||||
provider.ClientSecret = "***"
|
||||
}
|
||||
if provider.ClientSecret2 != "" {
|
||||
provider.ClientSecret2 = "***"
|
||||
}
|
||||
|
||||
return provider
|
||||
}
|
||||
|
||||
func GetProviderCount(owner string) int {
|
||||
count, err := adapter.Engine.Count(&Provider{Owner: owner})
|
||||
func GetMaskedProviders(providers []*Provider) []*Provider {
|
||||
for _, provider := range providers {
|
||||
provider = GetMaskedProvider(provider)
|
||||
}
|
||||
return providers
|
||||
}
|
||||
|
||||
func GetProviderCount(owner, field, value string) int {
|
||||
session := adapter.Engine.Where("owner=?", owner)
|
||||
if field != "" && value != "" {
|
||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
||||
}
|
||||
count, err := session.Count(&Provider{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -89,9 +103,10 @@ func GetProviders(owner string) []*Provider {
|
||||
return providers
|
||||
}
|
||||
|
||||
func GetPaginationProviders(owner string, offset, limit int) []*Provider {
|
||||
func GetPaginationProviders(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Provider {
|
||||
providers := []*Provider{}
|
||||
err := adapter.Engine.Desc("created_time").Limit(limit, offset).Find(&providers, &Provider{Owner: owner})
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&providers)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@@ -20,7 +20,7 @@ import (
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/astaxie/beego/context"
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
var logPostOnly bool
|
||||
@@ -47,6 +47,8 @@ type Record struct {
|
||||
RequestUri string `xorm:"varchar(1000)" json:"requestUri"`
|
||||
Action string `xorm:"varchar(1000)" json:"action"`
|
||||
|
||||
ExtendedUser *User `xorm:"-" json:"extendedUser"`
|
||||
|
||||
IsTriggered bool `json:"isTriggered"`
|
||||
}
|
||||
|
||||
@@ -99,8 +101,12 @@ func AddRecord(record *Record) bool {
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func GetRecordCount() int {
|
||||
count, err := adapter.Engine.Count(&Record{})
|
||||
func GetRecordCount(field, value string) int {
|
||||
session := adapter.Engine.Where("1=1")
|
||||
if field != "" && value != "" {
|
||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
||||
}
|
||||
count, err := session.Count(&Record{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -118,9 +124,10 @@ func GetRecords() []*Record {
|
||||
return records
|
||||
}
|
||||
|
||||
func GetPaginationRecords(offset, limit int) []*Record {
|
||||
func GetPaginationRecords(offset, limit int, field, value, sortField, sortOrder string) []*Record {
|
||||
records := []*Record{}
|
||||
err := adapter.Engine.Desc("id").Limit(limit, offset).Find(&records)
|
||||
session := GetSession("", offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&records)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -141,6 +148,10 @@ func GetRecordsByField(record *Record) []*Record {
|
||||
func SendWebhooks(record *Record) error {
|
||||
webhooks := getWebhooksByOrganization(record.Organization)
|
||||
for _, webhook := range webhooks {
|
||||
if !webhook.IsEnabled {
|
||||
continue
|
||||
}
|
||||
|
||||
matched := false
|
||||
for _, event := range webhook.Events {
|
||||
if record.Action == event {
|
||||
@@ -150,6 +161,11 @@ func SendWebhooks(record *Record) error {
|
||||
}
|
||||
|
||||
if matched {
|
||||
if webhook.IsUserExtended {
|
||||
user := getUser(record.Organization, record.User)
|
||||
record.ExtendedUser = user
|
||||
}
|
||||
|
||||
err := sendWebhook(webhook, record)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@@ -17,7 +17,7 @@ package object
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
@@ -39,8 +39,12 @@ type Resource struct {
|
||||
Description string `xorm:"varchar(1000)" json:"description"`
|
||||
}
|
||||
|
||||
func GetResourceCount(owner string, user string) int {
|
||||
count, err := adapter.Engine.Count(&Resource{Owner: owner, User: user})
|
||||
func GetResourceCount(owner, user, field, value string) int {
|
||||
session := adapter.Engine.Where("owner=? and user=?", owner, user)
|
||||
if field != "" && value != "" {
|
||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
||||
}
|
||||
count, err := session.Count(&Resource{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -63,14 +67,15 @@ func GetResources(owner string, user string) []*Resource {
|
||||
return resources
|
||||
}
|
||||
|
||||
func GetPaginationResources(owner, user string, offset, limit int) []*Resource {
|
||||
func GetPaginationResources(owner, user string, offset, limit int, field, value, sortField, sortOrder string) []*Resource {
|
||||
if owner == "built-in" {
|
||||
owner = ""
|
||||
user = ""
|
||||
}
|
||||
|
||||
resources := []*Resource{}
|
||||
err := adapter.Engine.Desc("created_time").Limit(limit, offset).Find(&resources, &Resource{Owner: owner, User: user})
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&resources, &Resource{User: user})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
126
object/role.go
Normal file
126
object/role.go
Normal file
@@ -0,0 +1,126 @@
|
||||
// Copyright 2021 The casbin 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 Role 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"`
|
||||
|
||||
Users []string `xorm:"mediumtext" json:"users"`
|
||||
Roles []string `xorm:"mediumtext" json:"roles"`
|
||||
IsEnabled bool `json:"isEnabled"`
|
||||
}
|
||||
|
||||
func GetRoleCount(owner, field, value string) int {
|
||||
session := adapter.Engine.Where("owner=?", owner)
|
||||
if field != "" && value != "" {
|
||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
||||
}
|
||||
count, err := session.Count(&Role{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return int(count)
|
||||
}
|
||||
|
||||
func GetRoles(owner string) []*Role {
|
||||
roles := []*Role{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&roles, &Role{Owner: owner})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return roles
|
||||
}
|
||||
|
||||
func GetPaginationRoles(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Role {
|
||||
roles := []*Role{}
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&roles)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return roles
|
||||
}
|
||||
|
||||
func getRole(owner string, name string) *Role {
|
||||
if owner == "" || name == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
role := Role{Owner: owner, Name: name}
|
||||
existed, err := adapter.Engine.Get(&role)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &role
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetRole(id string) *Role {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
return getRole(owner, name)
|
||||
}
|
||||
|
||||
func UpdateRole(id string, role *Role) bool {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
if getRole(owner, name) == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(role)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func AddRole(role *Role) bool {
|
||||
affected, err := adapter.Engine.Insert(role)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func DeleteRole(role *Role) bool {
|
||||
affected, err := adapter.Engine.ID(core.PK{role.Owner, role.Name}).Delete(&Role{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func (role *Role) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", role.Owner, role.Name)
|
||||
}
|
@@ -20,8 +20,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casbin/casdoor/storage"
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/storage"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
var isCloudIntranet bool
|
||||
|
@@ -17,10 +17,18 @@ package object
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
type TableColumn struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
CasdoorName string `json:"casdoorName"`
|
||||
IsHashed bool `json:"isHashed"`
|
||||
Values []string `json:"values"`
|
||||
}
|
||||
|
||||
type Syncer struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
@@ -29,22 +37,30 @@ type Syncer struct {
|
||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||
Type string `xorm:"varchar(100)" json:"type"`
|
||||
|
||||
Host string `xorm:"varchar(100)" json:"host"`
|
||||
Port int `json:"port"`
|
||||
User string `xorm:"varchar(100)" json:"user"`
|
||||
Password string `xorm:"varchar(100)" json:"password"`
|
||||
Database string `xorm:"varchar(100)" json:"database"`
|
||||
Table string `xorm:"varchar(100)" json:"table"`
|
||||
AffiliationTable string `xorm:"varchar(100)" json:"affiliationTable"`
|
||||
AvatarBaseUrl string `xorm:"varchar(100)" json:"avatarBaseUrl"`
|
||||
SyncInterval int `json:"syncInterval"`
|
||||
IsEnabled bool `json:"isEnabled"`
|
||||
Host string `xorm:"varchar(100)" json:"host"`
|
||||
Port int `json:"port"`
|
||||
User string `xorm:"varchar(100)" json:"user"`
|
||||
Password string `xorm:"varchar(100)" json:"password"`
|
||||
DatabaseType string `xorm:"varchar(100)" json:"databaseType"`
|
||||
Database string `xorm:"varchar(100)" json:"database"`
|
||||
Table string `xorm:"varchar(100)" json:"table"`
|
||||
TablePrimaryKey string `xorm:"varchar(100)" json:"tablePrimaryKey"`
|
||||
TableColumns []*TableColumn `xorm:"mediumtext" json:"tableColumns"`
|
||||
AffiliationTable string `xorm:"varchar(100)" json:"affiliationTable"`
|
||||
AvatarBaseUrl string `xorm:"varchar(100)" json:"avatarBaseUrl"`
|
||||
ErrorText string `xorm:"mediumtext" json:"errorText"`
|
||||
SyncInterval int `json:"syncInterval"`
|
||||
IsEnabled bool `json:"isEnabled"`
|
||||
|
||||
Adapter *Adapter `xorm:"-" json:"-"`
|
||||
}
|
||||
|
||||
func GetSyncerCount(owner string) int {
|
||||
count, err := adapter.Engine.Count(&Syncer{Owner: owner})
|
||||
func GetSyncerCount(owner, field, value string) int {
|
||||
session := adapter.Engine.Where("owner=?", owner)
|
||||
if field != "" && value != "" {
|
||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
||||
}
|
||||
count, err := session.Count(&Syncer{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -62,9 +78,10 @@ func GetSyncers(owner string) []*Syncer {
|
||||
return syncers
|
||||
}
|
||||
|
||||
func GetPaginationSyncers(owner string, offset, limit int) []*Syncer {
|
||||
func GetPaginationSyncers(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Syncer {
|
||||
syncers := []*Syncer{}
|
||||
err := adapter.Engine.Desc("created_time").Limit(limit, offset).Find(&syncers, &Syncer{Owner: owner})
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&syncers)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -124,6 +141,26 @@ func UpdateSyncer(id string, syncer *Syncer) bool {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if affected == 1 {
|
||||
addSyncerJob(syncer)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func updateSyncerErrorText(syncer *Syncer, line string) bool {
|
||||
s := getSyncer(syncer.Owner, syncer.Name)
|
||||
if s == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
s.ErrorText = s.ErrorText + line
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{s.Owner, s.Name}).Cols("error_text").Update(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
@@ -133,6 +170,10 @@ func AddSyncer(syncer *Syncer) bool {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if affected == 1 {
|
||||
addSyncerJob(syncer)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
@@ -142,9 +183,29 @@ func DeleteSyncer(syncer *Syncer) bool {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if affected == 1 {
|
||||
deleteSyncerJob(syncer)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func (syncer *Syncer) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", syncer.Owner, syncer.Name)
|
||||
}
|
||||
|
||||
func (syncer *Syncer) getTableColumnsTypeMap() map[string]string {
|
||||
m := map[string]string{}
|
||||
for _, tableColumn := range syncer.TableColumns {
|
||||
m[tableColumn.Name] = tableColumn.Type
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (syncer *Syncer) getTable() string {
|
||||
if syncer.DatabaseType == "mssql" {
|
||||
return fmt.Sprintf("[%s]", syncer.Table)
|
||||
} else {
|
||||
return syncer.Table
|
||||
}
|
||||
}
|
||||
|
@@ -14,27 +14,56 @@
|
||||
|
||||
package object
|
||||
|
||||
import "github.com/mileusna/crontab"
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
var cronMap map[string]*crontab.Crontab
|
||||
"github.com/robfig/cron/v3"
|
||||
)
|
||||
|
||||
var cronMap map[string]*cron.Cron
|
||||
|
||||
func init() {
|
||||
cronMap = map[string]*crontab.Crontab{}
|
||||
cronMap = map[string]*cron.Cron{}
|
||||
}
|
||||
|
||||
func getCrontab(name string) *crontab.Crontab {
|
||||
ctab, ok := cronMap[name]
|
||||
func getCronMap(name string) *cron.Cron {
|
||||
m, ok := cronMap[name]
|
||||
if !ok {
|
||||
ctab = crontab.New()
|
||||
cronMap[name] = ctab
|
||||
m = cron.New()
|
||||
cronMap[name] = m
|
||||
}
|
||||
return ctab
|
||||
return m
|
||||
}
|
||||
|
||||
func clearCrontab(name string) {
|
||||
ctab, ok := cronMap[name]
|
||||
func clearCron(name string) {
|
||||
cron, ok := cronMap[name]
|
||||
if ok {
|
||||
ctab.Clear()
|
||||
cron.Stop()
|
||||
delete(cronMap, name)
|
||||
}
|
||||
}
|
||||
|
||||
func addSyncerJob(syncer *Syncer) {
|
||||
deleteSyncerJob(syncer)
|
||||
|
||||
if !syncer.IsEnabled {
|
||||
return
|
||||
}
|
||||
|
||||
syncer.initAdapter()
|
||||
|
||||
syncer.syncUsers()
|
||||
|
||||
schedule := fmt.Sprintf("@every %ds", syncer.SyncInterval)
|
||||
cron := getCronMap(syncer.Name)
|
||||
_, err := cron.AddFunc(schedule, syncer.syncUsers)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cron.Start()
|
||||
}
|
||||
|
||||
func deleteSyncerJob(syncer *Syncer) {
|
||||
clearCron(syncer.Name)
|
||||
}
|
||||
|
@@ -16,6 +16,16 @@ package object
|
||||
|
||||
import "fmt"
|
||||
|
||||
func getDbSyncerForUser(user *User) *Syncer {
|
||||
syncers := GetSyncers("admin")
|
||||
for _, syncer := range syncers {
|
||||
if syncer.Organization == user.Owner && syncer.IsEnabled && syncer.Type == "Database" {
|
||||
return syncer
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getEnabledSyncerForOrganization(organization string) *Syncer {
|
||||
syncers := GetSyncers("admin")
|
||||
for _, syncer := range syncers {
|
||||
|
@@ -16,21 +16,33 @@ package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (syncer *Syncer) syncUsers() {
|
||||
fmt.Printf("Running syncUsers()..\n")
|
||||
|
||||
users, userMap := syncer.getUserMap()
|
||||
oUsers, oUserMap := syncer.getUserMapOriginal()
|
||||
oUsers, oUserMap, err := syncer.getOriginalUserMap()
|
||||
if err != nil {
|
||||
fmt.Printf(err.Error())
|
||||
|
||||
timestamp := time.Now().Format("2006-01-02 15:04:05")
|
||||
line := fmt.Sprintf("[%s] %s\n", timestamp, err.Error())
|
||||
updateSyncerErrorText(syncer, line)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Users: %d, oUsers: %d\n", len(users), len(oUsers))
|
||||
|
||||
_, affiliationMap := syncer.getAffiliationMap()
|
||||
var affiliationMap map[int]string
|
||||
if syncer.AffiliationTable != "" {
|
||||
_, affiliationMap = syncer.getAffiliationMap()
|
||||
}
|
||||
|
||||
newUsers := []*User{}
|
||||
for _, oUser := range oUsers {
|
||||
id := strconv.Itoa(oUser.Id)
|
||||
id := oUser.Id
|
||||
if _, ok := userMap[id]; !ok {
|
||||
newUser := syncer.createUserFromOriginalUser(oUser, affiliationMap)
|
||||
fmt.Printf("New user: %v\n", newUser)
|
||||
@@ -44,7 +56,7 @@ func (syncer *Syncer) syncUsers() {
|
||||
updatedUser := syncer.createUserFromOriginalUser(oUser, affiliationMap)
|
||||
updatedUser.Hash = oHash
|
||||
updatedUser.PreHash = oHash
|
||||
UpdateUserForOriginalFields(updatedUser)
|
||||
syncer.updateUserForOriginalFields(updatedUser)
|
||||
fmt.Printf("Update from oUser to user: %v\n", updatedUser)
|
||||
}
|
||||
} else {
|
||||
@@ -65,7 +77,7 @@ func (syncer *Syncer) syncUsers() {
|
||||
updatedUser := syncer.createUserFromOriginalUser(oUser, affiliationMap)
|
||||
updatedUser.Hash = oHash
|
||||
updatedUser.PreHash = oHash
|
||||
UpdateUserForOriginalFields(updatedUser)
|
||||
syncer.updateUserForOriginalFields(updatedUser)
|
||||
fmt.Printf("Update from oUser to user (2nd condition): %v\n", updatedUser)
|
||||
}
|
||||
}
|
||||
@@ -77,7 +89,9 @@ func (syncer *Syncer) syncUsers() {
|
||||
for _, user := range users {
|
||||
id := user.Id
|
||||
if _, ok := oUserMap[id]; !ok {
|
||||
panic(fmt.Sprintf("New original user: cannot create now, user = %v", user))
|
||||
newOUser := syncer.createOriginalUserFromUser(user)
|
||||
syncer.addUser(newOUser)
|
||||
fmt.Printf("New oUser: %v\n", newOUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -16,93 +16,152 @@ package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
type DbUser struct {
|
||||
Id int `xorm:"int notnull pk autoincr" json:"id"`
|
||||
Name string `xorm:"varchar(128)" json:"name"`
|
||||
Password string `xorm:"varchar(128)" json:"password"`
|
||||
Cellphone string `xorm:"varchar(128)" json:"cellphone"`
|
||||
SchoolId int `json:"schoolId"`
|
||||
Avatar string `xorm:"varchar(128)" json:"avatar"`
|
||||
Deleted int `xorm:"tinyint(1)" json:"deleted"`
|
||||
}
|
||||
type OriginalUser = User
|
||||
|
||||
func (syncer *Syncer) getUsersOriginal() []*DbUser {
|
||||
users := []*DbUser{}
|
||||
err := syncer.Adapter.Engine.Table(syncer.Table).Asc("id").Find(&users)
|
||||
func (syncer *Syncer) getOriginalUsers() ([]*OriginalUser, error) {
|
||||
sql := fmt.Sprintf("select * from %s", syncer.getTable())
|
||||
results, err := syncer.Adapter.Engine.QueryString(sql)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return users
|
||||
return syncer.getOriginalUsersFromMap(results), nil
|
||||
}
|
||||
|
||||
func (syncer *Syncer) getUserMapOriginal() ([]*DbUser, map[string]*DbUser) {
|
||||
users := syncer.getUsersOriginal()
|
||||
func (syncer *Syncer) getOriginalUserMap() ([]*OriginalUser, map[string]*OriginalUser, error) {
|
||||
users, err := syncer.getOriginalUsers()
|
||||
if err != nil {
|
||||
return users, nil, err
|
||||
}
|
||||
|
||||
m := map[string]*DbUser{}
|
||||
m := map[string]*OriginalUser{}
|
||||
for _, user := range users {
|
||||
m[strconv.Itoa(user.Id)] = user
|
||||
m[user.Id] = user
|
||||
}
|
||||
return users, m
|
||||
return users, m, nil
|
||||
}
|
||||
|
||||
func (syncer *Syncer) addUser(user *DbUser) bool {
|
||||
affected, err := syncer.Adapter.Engine.Table(syncer.Table).Insert(user)
|
||||
func (syncer *Syncer) addUser(user *OriginalUser) (bool, error) {
|
||||
m := syncer.getMapFromOriginalUser(user)
|
||||
keyString, valueString := syncer.getSqlKeyValueStringFromMap(m)
|
||||
|
||||
sql := fmt.Sprintf("insert into %s (%s) values (%s)", syncer.getTable(), keyString, valueString)
|
||||
res, err := syncer.Adapter.Engine.Exec(sql)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func (syncer *Syncer) updateUser(user *DbUser) bool {
|
||||
affected, err := syncer.Adapter.Engine.Table(syncer.Table).ID(user.Id).Cols("name", "password", "cellphone", "school_id", "avatar", "deleted").Update(user)
|
||||
affected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
func (syncer *Syncer) calculateHash(user *DbUser) string {
|
||||
s := strings.Join([]string{strconv.Itoa(user.Id), user.Password, user.Name, syncer.getFullAvatarUrl(user.Avatar), user.Cellphone, strconv.Itoa(user.SchoolId)}, "|")
|
||||
/*func (syncer *Syncer) getOriginalColumns() []string {
|
||||
res := []string{}
|
||||
for _, tableColumn := range syncer.TableColumns {
|
||||
if tableColumn.CasdoorName != "Id" {
|
||||
res = append(res, tableColumn.Name)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}*/
|
||||
|
||||
func (syncer *Syncer) getCasdoorColumns() []string {
|
||||
res := []string{}
|
||||
for _, tableColumn := range syncer.TableColumns {
|
||||
if tableColumn.CasdoorName != "Id" {
|
||||
v := util.CamelToSnakeCase(tableColumn.CasdoorName)
|
||||
res = append(res, v)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (syncer *Syncer) updateUser(user *OriginalUser) (bool, error) {
|
||||
m := syncer.getMapFromOriginalUser(user)
|
||||
pkValue := m[syncer.TablePrimaryKey]
|
||||
delete(m, syncer.TablePrimaryKey)
|
||||
setString := syncer.getSqlSetStringFromMap(m)
|
||||
|
||||
sql := fmt.Sprintf("update %s set %s where %s = %s", syncer.getTable(), setString, syncer.TablePrimaryKey, pkValue)
|
||||
res, err := syncer.Adapter.Engine.Exec(sql)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
affected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
func (syncer *Syncer) updateUserForOriginalFields(user *User) (bool, error) {
|
||||
owner, name := util.GetOwnerAndNameFromId(user.GetId())
|
||||
oldUser := getUserById(owner, name)
|
||||
if oldUser == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if user.Avatar != oldUser.Avatar && user.Avatar != "" {
|
||||
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
|
||||
}
|
||||
|
||||
columns := syncer.getCasdoorColumns()
|
||||
columns = append(columns, "affiliation", "hash", "pre_hash")
|
||||
affected, err := adapter.Engine.ID(core.PK{oldUser.Owner, oldUser.Name}).Cols(columns...).Update(user)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
func (syncer *Syncer) calculateHash(user *OriginalUser) string {
|
||||
values := []string{}
|
||||
m := syncer.getMapFromOriginalUser(user)
|
||||
for _, tableColumn := range syncer.TableColumns {
|
||||
if tableColumn.IsHashed {
|
||||
values = append(values, m[tableColumn.Name])
|
||||
}
|
||||
}
|
||||
|
||||
s := strings.Join(values, "|")
|
||||
return util.GetMd5Hash(s)
|
||||
}
|
||||
|
||||
func (syncer *Syncer) initAdapter() {
|
||||
if syncer.Adapter == nil {
|
||||
dataSourceName := fmt.Sprintf("%s:%s@tcp(%s:%d)/", syncer.User, syncer.Password, syncer.Host, syncer.Port)
|
||||
syncer.Adapter = NewAdapter(beego.AppConfig.String("driverName"), dataSourceName, syncer.Database)
|
||||
var dataSourceName string
|
||||
if syncer.DatabaseType == "mssql" {
|
||||
dataSourceName = fmt.Sprintf("sqlserver://%s:%s@%s:%d?database=%s", syncer.User, syncer.Password, syncer.Host, syncer.Port, syncer.Database)
|
||||
} else {
|
||||
dataSourceName = fmt.Sprintf("%s:%s@tcp(%s:%d)/", syncer.User, syncer.Password, syncer.Host, syncer.Port)
|
||||
}
|
||||
|
||||
if !isCloudIntranet {
|
||||
dataSourceName = strings.ReplaceAll(dataSourceName, "dbi.", "db.")
|
||||
}
|
||||
|
||||
syncer.Adapter = NewAdapter(syncer.DatabaseType, dataSourceName, syncer.Database)
|
||||
}
|
||||
}
|
||||
|
||||
func RunSyncUsersJob() {
|
||||
syncers := GetSyncers("admin")
|
||||
for _, syncer := range syncers {
|
||||
if !syncer.IsEnabled {
|
||||
continue
|
||||
}
|
||||
|
||||
syncer.initAdapter()
|
||||
|
||||
syncer.syncUsers()
|
||||
|
||||
// run at every minute
|
||||
//schedule := fmt.Sprintf("* * * * %d", syncer.SyncInterval)
|
||||
schedule := "* * * * *"
|
||||
ctab := getCrontab(syncer.Name)
|
||||
err := ctab.AddJob(schedule, syncer.syncUsers)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
addSyncerJob(syncer)
|
||||
}
|
||||
|
||||
time.Sleep(time.Duration(1<<63 - 1))
|
||||
|
@@ -25,7 +25,7 @@ func TestGetUsers(t *testing.T) {
|
||||
syncers := GetSyncers("admin")
|
||||
syncer := syncers[0]
|
||||
syncer.initAdapter()
|
||||
users := syncer.getUsersOriginal()
|
||||
users := syncer.getOriginalUsers()
|
||||
for _, user := range users {
|
||||
fmt.Printf("%v\n", user)
|
||||
}
|
||||
|
@@ -19,11 +19,15 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func (syncer *Syncer) getFullAvatarUrl(avatar string) string {
|
||||
if !strings.HasPrefix(avatar, "https://") {
|
||||
if syncer.AvatarBaseUrl == "" {
|
||||
return avatar
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(avatar, "http") {
|
||||
return fmt.Sprintf("%s%s", syncer.AvatarBaseUrl, avatar)
|
||||
}
|
||||
return avatar
|
||||
@@ -36,53 +40,211 @@ func (syncer *Syncer) getPartialAvatarUrl(avatar string) string {
|
||||
return avatar
|
||||
}
|
||||
|
||||
func (syncer *Syncer) createUserFromOriginalUser(originalUser *DbUser, affiliationMap map[int]string) *User {
|
||||
affiliation := ""
|
||||
if originalUser.SchoolId != 0 {
|
||||
var ok bool
|
||||
affiliation, ok = affiliationMap[originalUser.SchoolId]
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("SchoolId not found: %d", originalUser.SchoolId))
|
||||
func (syncer *Syncer) createUserFromOriginalUser(originalUser *OriginalUser, affiliationMap map[int]string) *User {
|
||||
user := *originalUser
|
||||
user.Owner = syncer.Organization
|
||||
|
||||
if user.Name == "" {
|
||||
user.Name = originalUser.Id
|
||||
}
|
||||
|
||||
if user.CreatedTime == "" {
|
||||
user.CreatedTime = util.GetCurrentTime()
|
||||
}
|
||||
|
||||
if user.Type == "" {
|
||||
user.Type = "normal-user"
|
||||
}
|
||||
|
||||
user.Avatar = syncer.getFullAvatarUrl(user.Avatar)
|
||||
|
||||
if affiliationMap != nil {
|
||||
if originalUser.Score != 0 {
|
||||
affiliation, ok := affiliationMap[originalUser.Score]
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("Affiliation not found: %d", originalUser.Score))
|
||||
}
|
||||
user.Affiliation = affiliation
|
||||
}
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Owner: syncer.Organization,
|
||||
Name: strconv.Itoa(originalUser.Id),
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
Id: strconv.Itoa(originalUser.Id),
|
||||
Type: "normal-user",
|
||||
Password: originalUser.Password,
|
||||
DisplayName: originalUser.Name,
|
||||
Avatar: syncer.getFullAvatarUrl(originalUser.Avatar),
|
||||
Email: "",
|
||||
Phone: originalUser.Cellphone,
|
||||
Address: []string{},
|
||||
Affiliation: affiliation,
|
||||
Score: originalUser.SchoolId,
|
||||
IsAdmin: false,
|
||||
IsGlobalAdmin: false,
|
||||
IsForbidden: originalUser.Deleted != 0,
|
||||
IsDeleted: false,
|
||||
Properties: map[string]string{},
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
func (syncer *Syncer) createOriginalUserFromUser(user *User) *DbUser {
|
||||
deleted := 0
|
||||
if user.IsForbidden {
|
||||
deleted = 1
|
||||
if user.Properties == nil {
|
||||
user.Properties = map[string]string{}
|
||||
}
|
||||
|
||||
originalUser := &DbUser{
|
||||
Id: util.ParseInt(user.Id),
|
||||
Name: user.DisplayName,
|
||||
Password: user.Password,
|
||||
Cellphone: user.Phone,
|
||||
SchoolId: user.Score,
|
||||
Avatar: syncer.getPartialAvatarUrl(user.Avatar),
|
||||
Deleted: deleted,
|
||||
}
|
||||
return originalUser
|
||||
return &user
|
||||
}
|
||||
|
||||
func (syncer *Syncer) createOriginalUserFromUser(user *User) *OriginalUser {
|
||||
originalUser := *user
|
||||
originalUser.Avatar = syncer.getPartialAvatarUrl(user.Avatar)
|
||||
return &originalUser
|
||||
}
|
||||
|
||||
func (syncer *Syncer) setUserByKeyValue(user *User, key string, value string) {
|
||||
switch key {
|
||||
case "Name":
|
||||
user.Name = value
|
||||
case "CreatedTime":
|
||||
user.CreatedTime = value
|
||||
case "UpdatedTime":
|
||||
user.UpdatedTime = value
|
||||
case "Id":
|
||||
user.Id = value
|
||||
case "Type":
|
||||
user.Type = value
|
||||
case "Password":
|
||||
user.Password = value
|
||||
case "PasswordSalt":
|
||||
user.PasswordSalt = value
|
||||
case "DisplayName":
|
||||
user.DisplayName = value
|
||||
case "Avatar":
|
||||
user.Avatar = syncer.getPartialAvatarUrl(value)
|
||||
case "PermanentAvatar":
|
||||
user.PermanentAvatar = value
|
||||
case "Email":
|
||||
user.Email = value
|
||||
case "Phone":
|
||||
user.Phone = value
|
||||
case "Location":
|
||||
user.Location = value
|
||||
case "Address":
|
||||
user.Address = []string{value}
|
||||
case "Affiliation":
|
||||
user.Affiliation = value
|
||||
case "Title":
|
||||
user.Title = value
|
||||
case "IdCardType":
|
||||
user.IdCardType = value
|
||||
case "IdCard":
|
||||
user.IdCard = value
|
||||
case "Homepage":
|
||||
user.Homepage = value
|
||||
case "Bio":
|
||||
user.Bio = value
|
||||
case "Tag":
|
||||
user.Tag = value
|
||||
case "Region":
|
||||
user.Region = value
|
||||
case "Language":
|
||||
user.Language = value
|
||||
case "Gender":
|
||||
user.Gender = value
|
||||
case "Birthday":
|
||||
user.Birthday = value
|
||||
case "Education":
|
||||
user.Education = value
|
||||
case "Score":
|
||||
user.Score = util.ParseInt(value)
|
||||
case "Ranking":
|
||||
user.Ranking = util.ParseInt(value)
|
||||
case "IsDefaultAvatar":
|
||||
user.IsDefaultAvatar = util.ParseBool(value)
|
||||
case "IsOnline":
|
||||
user.IsOnline = util.ParseBool(value)
|
||||
case "IsAdmin":
|
||||
user.IsAdmin = util.ParseBool(value)
|
||||
case "IsGlobalAdmin":
|
||||
user.IsGlobalAdmin = util.ParseBool(value)
|
||||
case "IsForbidden":
|
||||
user.IsForbidden = util.ParseBool(value)
|
||||
case "IsDeleted":
|
||||
user.IsDeleted = util.ParseBool(value)
|
||||
case "CreatedIp":
|
||||
user.CreatedIp = value
|
||||
}
|
||||
}
|
||||
|
||||
func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]string) []*OriginalUser {
|
||||
users := []*OriginalUser{}
|
||||
for _, result := range results {
|
||||
originalUser := &OriginalUser{
|
||||
Address: []string{},
|
||||
Properties: map[string]string{},
|
||||
}
|
||||
|
||||
for _, tableColumn := range syncer.TableColumns {
|
||||
syncer.setUserByKeyValue(originalUser, tableColumn.CasdoorName, result[tableColumn.Name])
|
||||
}
|
||||
users = append(users, originalUser)
|
||||
}
|
||||
return users
|
||||
}
|
||||
|
||||
func (syncer *Syncer) getMapFromOriginalUser(user *OriginalUser) map[string]string {
|
||||
m := map[string]string{}
|
||||
m["Name"] = user.Name
|
||||
m["CreatedTime"] = user.CreatedTime
|
||||
m["UpdatedTime"] = user.UpdatedTime
|
||||
m["Id"] = user.Id
|
||||
m["Type"] = user.Type
|
||||
m["Password"] = user.Password
|
||||
m["PasswordSalt"] = user.PasswordSalt
|
||||
m["DisplayName"] = user.DisplayName
|
||||
m["Avatar"] = syncer.getFullAvatarUrl(user.Avatar)
|
||||
m["PermanentAvatar"] = user.PermanentAvatar
|
||||
m["Email"] = user.Email
|
||||
m["Phone"] = user.Phone
|
||||
m["Location"] = user.Location
|
||||
m["Address"] = strings.Join(user.Address, "|")
|
||||
m["Affiliation"] = user.Affiliation
|
||||
m["Title"] = user.Title
|
||||
m["IdCardType"] = user.IdCardType
|
||||
m["IdCard"] = user.IdCard
|
||||
m["Homepage"] = user.Homepage
|
||||
m["Bio"] = user.Bio
|
||||
m["Tag"] = user.Tag
|
||||
m["Region"] = user.Region
|
||||
m["Language"] = user.Language
|
||||
m["Gender"] = user.Gender
|
||||
m["Birthday"] = user.Birthday
|
||||
m["Education"] = user.Education
|
||||
m["Score"] = strconv.Itoa(user.Score)
|
||||
m["Ranking"] = strconv.Itoa(user.Ranking)
|
||||
m["IsDefaultAvatar"] = util.BoolToString(user.IsDefaultAvatar)
|
||||
m["IsOnline"] = util.BoolToString(user.IsOnline)
|
||||
m["IsAdmin"] = util.BoolToString(user.IsAdmin)
|
||||
m["IsGlobalAdmin"] = util.BoolToString(user.IsGlobalAdmin)
|
||||
m["IsForbidden"] = util.BoolToString(user.IsForbidden)
|
||||
m["IsDeleted"] = util.BoolToString(user.IsDeleted)
|
||||
m["CreatedIp"] = user.CreatedIp
|
||||
|
||||
m2 := map[string]string{}
|
||||
for _, tableColumn := range syncer.TableColumns {
|
||||
m2[tableColumn.Name] = m[tableColumn.CasdoorName]
|
||||
}
|
||||
|
||||
return m2
|
||||
}
|
||||
|
||||
func (syncer *Syncer) getSqlSetStringFromMap(m map[string]string) string {
|
||||
typeMap := syncer.getTableColumnsTypeMap()
|
||||
|
||||
tokens := []string{}
|
||||
for k, v := range m {
|
||||
token := fmt.Sprintf("%s = %s", k, v)
|
||||
if typeMap[k] == "string" {
|
||||
token = fmt.Sprintf("%s = '%s'", k, v)
|
||||
}
|
||||
|
||||
tokens = append(tokens, token)
|
||||
}
|
||||
return strings.Join(tokens, ", ")
|
||||
}
|
||||
|
||||
func (syncer *Syncer) getSqlKeyValueStringFromMap(m map[string]string) (string, string) {
|
||||
typeMap := syncer.getTableColumnsTypeMap()
|
||||
|
||||
keys := []string{}
|
||||
values := []string{}
|
||||
for k, v := range m {
|
||||
if typeMap[k] == "string" {
|
||||
v = fmt.Sprintf("'%s'", v)
|
||||
}
|
||||
|
||||
keys = append(keys, k)
|
||||
values = append(values, v)
|
||||
}
|
||||
return strings.Join(keys, ", "), strings.Join(values, ", ")
|
||||
}
|
||||
|
@@ -24,7 +24,7 @@ func (syncer *Syncer) getUserMap() ([]*User, map[string]*User) {
|
||||
|
||||
m := map[string]*User{}
|
||||
for _, user := range users {
|
||||
m[user.Name] = user
|
||||
m[user.Id] = user
|
||||
}
|
||||
return users, m
|
||||
}
|
||||
|
181
object/token.go
181
object/token.go
@@ -15,11 +15,12 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
@@ -37,24 +38,30 @@ type Token struct {
|
||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||
User string `xorm:"varchar(100)" json:"user"`
|
||||
|
||||
Code string `xorm:"varchar(100)" json:"code"`
|
||||
AccessToken string `xorm:"mediumtext" json:"accessToken"`
|
||||
RefreshToken string `xorm:"mediumtext" json:"refreshToken"`
|
||||
ExpiresIn int `json:"expiresIn"`
|
||||
Scope string `xorm:"varchar(100)" json:"scope"`
|
||||
TokenType string `xorm:"varchar(100)" json:"tokenType"`
|
||||
Code string `xorm:"varchar(100)" json:"code"`
|
||||
AccessToken string `xorm:"mediumtext" json:"accessToken"`
|
||||
RefreshToken string `xorm:"mediumtext" json:"refreshToken"`
|
||||
ExpiresIn int `json:"expiresIn"`
|
||||
Scope string `xorm:"varchar(100)" json:"scope"`
|
||||
TokenType string `xorm:"varchar(100)" json:"tokenType"`
|
||||
CodeChallenge string `xorm:"varchar(100)" json:"codeChallenge"`
|
||||
}
|
||||
|
||||
type TokenWrapper struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
IdToken string `json:"id_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
Scope string `json:"scope"`
|
||||
AccessToken string `json:"access_token"`
|
||||
IdToken string `json:"id_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
Scope string `json:"scope"`
|
||||
}
|
||||
|
||||
func GetTokenCount(owner string) int {
|
||||
count, err := adapter.Engine.Count(&Token{Owner: owner})
|
||||
func GetTokenCount(owner, field, value string) int {
|
||||
session := adapter.Engine.Where("owner=?", owner)
|
||||
if field != "" && value != "" {
|
||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
||||
}
|
||||
count, err := session.Count(&Token{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -72,9 +79,10 @@ func GetTokens(owner string) []*Token {
|
||||
return tokens
|
||||
}
|
||||
|
||||
func GetPaginationTokens(owner string, offset, limit int) []*Token {
|
||||
func GetPaginationTokens(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Token {
|
||||
tokens := []*Token{}
|
||||
err := adapter.Engine.Desc("created_time").Limit(limit, offset).Find(&tokens, &Token{Owner: owner})
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&tokens)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -177,7 +185,7 @@ func CheckOAuthLogin(clientId string, responseType string, redirectUri string, s
|
||||
return "", application
|
||||
}
|
||||
|
||||
func GetOAuthCode(userId string, clientId string, responseType string, redirectUri string, scope string, state string, nonce string) *Code {
|
||||
func GetOAuthCode(userId string, clientId string, responseType string, redirectUri string, scope string, state string, nonce string, challenge string) *Code {
|
||||
user := GetUser(userId)
|
||||
if user == nil {
|
||||
return &Code{
|
||||
@@ -185,6 +193,12 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
|
||||
Code: "",
|
||||
}
|
||||
}
|
||||
if user.IsForbidden {
|
||||
return &Code{
|
||||
Message: "error: the user is forbidden to sign in, please contact the administrator",
|
||||
Code: "",
|
||||
}
|
||||
}
|
||||
|
||||
msg, application := CheckOAuthLogin(clientId, responseType, redirectUri, scope, state)
|
||||
if msg != "" {
|
||||
@@ -199,19 +213,24 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if challenge == "null"{
|
||||
challenge = ""
|
||||
}
|
||||
|
||||
token := &Token{
|
||||
Owner: application.Owner,
|
||||
Name: util.GenerateId(),
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
Application: application.Name,
|
||||
Organization: user.Owner,
|
||||
User: user.Name,
|
||||
Code: util.GenerateClientId(),
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: refreshToken,
|
||||
ExpiresIn: application.ExpireInHours * 60,
|
||||
Scope: scope,
|
||||
TokenType: "Bearer",
|
||||
Owner: application.Owner,
|
||||
Name: util.GenerateId(),
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
Application: application.Name,
|
||||
Organization: user.Owner,
|
||||
User: user.Name,
|
||||
Code: util.GenerateClientId(),
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: refreshToken,
|
||||
ExpiresIn: application.ExpireInHours * 60,
|
||||
Scope: scope,
|
||||
TokenType: "Bearer",
|
||||
CodeChallenge: challenge,
|
||||
}
|
||||
AddToken(token)
|
||||
|
||||
@@ -221,7 +240,7 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
|
||||
}
|
||||
}
|
||||
|
||||
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string) *TokenWrapper {
|
||||
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string) *TokenWrapper {
|
||||
application := GetApplicationByClientId(clientId)
|
||||
if application == nil {
|
||||
return &TokenWrapper{
|
||||
@@ -277,63 +296,86 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
|
||||
Scope: "",
|
||||
}
|
||||
}
|
||||
if token.CodeChallenge != "" && pkceChallenge(verifier) != token.CodeChallenge {
|
||||
return &TokenWrapper{
|
||||
AccessToken: "error: incorrect code_verifier",
|
||||
TokenType: "",
|
||||
ExpiresIn: 0,
|
||||
Scope: "",
|
||||
}
|
||||
}
|
||||
|
||||
tokenWrapper := &TokenWrapper{
|
||||
AccessToken: token.AccessToken,
|
||||
IdToken: token.AccessToken,
|
||||
TokenType: token.TokenType,
|
||||
ExpiresIn: token.ExpiresIn,
|
||||
Scope: token.Scope,
|
||||
AccessToken: token.AccessToken,
|
||||
IdToken: token.AccessToken,
|
||||
RefreshToken: token.RefreshToken,
|
||||
TokenType: token.TokenType,
|
||||
ExpiresIn: token.ExpiresIn,
|
||||
Scope: token.Scope,
|
||||
}
|
||||
|
||||
return tokenWrapper
|
||||
}
|
||||
|
||||
func RefreshToken(grantType string, refreshToken string, scope string, clientId string, clientSecret string) *Code {
|
||||
func RefreshToken(grantType string, refreshToken string, scope string, clientId string, clientSecret string) *TokenWrapper {
|
||||
// check parameters
|
||||
if grantType != "refresh_token" {
|
||||
return &Code{
|
||||
Message: "error: grant_type should be \"refresh_token\"",
|
||||
Code: "",
|
||||
return &TokenWrapper{
|
||||
AccessToken: "error: grant_type should be \"refresh_token\"",
|
||||
TokenType: "",
|
||||
ExpiresIn: 0,
|
||||
Scope: "",
|
||||
}
|
||||
}
|
||||
application := GetApplicationByClientId(clientId)
|
||||
if application == nil {
|
||||
return &Code{
|
||||
Message: "error: invalid client_id",
|
||||
Code: "",
|
||||
return &TokenWrapper{
|
||||
AccessToken: "error: invalid client_id",
|
||||
TokenType: "",
|
||||
ExpiresIn: 0,
|
||||
Scope: "",
|
||||
}
|
||||
}
|
||||
if application.ClientSecret != clientSecret {
|
||||
return &Code{
|
||||
Message: "error: invalid client_secret",
|
||||
Code: "",
|
||||
return &TokenWrapper{
|
||||
AccessToken: "error: invalid client_secret",
|
||||
TokenType: "",
|
||||
ExpiresIn: 0,
|
||||
Scope: "",
|
||||
}
|
||||
}
|
||||
// check whether the refresh token is valid, and has not expired.
|
||||
token := Token{RefreshToken: refreshToken}
|
||||
existed, err := adapter.Engine.Get(&token)
|
||||
if err != nil || !existed {
|
||||
return &Code{
|
||||
Message: "error: invalid refresh_token",
|
||||
Code: "",
|
||||
return &TokenWrapper{
|
||||
AccessToken: "error: invalid refresh_token",
|
||||
TokenType: "",
|
||||
ExpiresIn: 0,
|
||||
Scope: "",
|
||||
}
|
||||
}
|
||||
claims, err := ParseJwtToken(refreshToken)
|
||||
|
||||
cert := getCertByApplication(application)
|
||||
_, err = ParseJwtToken(refreshToken, cert)
|
||||
if err != nil {
|
||||
return &Code{
|
||||
Message: "error: invalid refresh_token",
|
||||
Code: "",
|
||||
}
|
||||
}
|
||||
if time.Now().Unix() > claims.ExpiresAt.Unix() {
|
||||
return &Code{
|
||||
Message: "error: expired refresh_token",
|
||||
Code: "",
|
||||
return &TokenWrapper{
|
||||
AccessToken: fmt.Sprintf("error: %s", err.Error()),
|
||||
TokenType: "",
|
||||
ExpiresIn: 0,
|
||||
Scope: "",
|
||||
}
|
||||
}
|
||||
// generate a new token
|
||||
user := getUser(application.Owner, token.User)
|
||||
user := getUser(application.Organization, token.User)
|
||||
if user.IsForbidden {
|
||||
return &TokenWrapper{
|
||||
AccessToken: "error: the user is forbidden to sign in, please contact the administrator",
|
||||
TokenType: "",
|
||||
ExpiresIn: 0,
|
||||
Scope: "",
|
||||
}
|
||||
}
|
||||
newAccessToken, newRefreshToken, err := generateJwtToken(application, user, "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -355,8 +397,21 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
||||
}
|
||||
AddToken(newToken)
|
||||
|
||||
return &Code{
|
||||
Message: "",
|
||||
Code: token.Code,
|
||||
tokenWrapper := &TokenWrapper{
|
||||
AccessToken: token.AccessToken,
|
||||
IdToken: token.AccessToken,
|
||||
RefreshToken: token.RefreshToken,
|
||||
TokenType: token.TokenType,
|
||||
ExpiresIn: token.ExpiresIn,
|
||||
Scope: token.Scope,
|
||||
}
|
||||
|
||||
return tokenWrapper
|
||||
}
|
||||
|
||||
// PkceChallenge: base64-URL-encoded SHA256 hash of verifier, per rfc 7636
|
||||
func pkceChallenge(verifier string) string {
|
||||
sum := sha256.Sum256([]byte(verifier))
|
||||
challenge := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(sum[:])
|
||||
return challenge
|
||||
}
|
||||
|
@@ -23,20 +23,41 @@ import (
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
)
|
||||
|
||||
//go:embed token_jwt_key.pem
|
||||
var tokenJwtPublicKey string
|
||||
|
||||
//go:embed token_jwt_key.key
|
||||
var tokenJwtPrivateKey string
|
||||
|
||||
type Claims struct {
|
||||
*User
|
||||
Name string `json:"name,omitempty"`
|
||||
Owner string `json:"owner,omitempty"`
|
||||
Nonce string `json:"nonce,omitempty"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
type UserShort struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
}
|
||||
|
||||
type ClaimsShort struct {
|
||||
*UserShort
|
||||
Nonce string `json:"nonce,omitempty"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
func getShortUser(user *User) *UserShort {
|
||||
res := &UserShort{
|
||||
Owner: user.Owner,
|
||||
Name: user.Name,
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func getShortClaims(claims Claims) ClaimsShort {
|
||||
res := ClaimsShort{
|
||||
UserShort: getShortUser(claims.User),
|
||||
Nonce: claims.Nonce,
|
||||
RegisteredClaims: claims.RegisteredClaims,
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func generateJwtToken(application *Application, user *User, nonce string) (string, string, error) {
|
||||
nowTime := time.Now()
|
||||
expireTime := nowTime.Add(time.Duration(application.ExpireInHours) * time.Hour)
|
||||
@@ -47,6 +68,8 @@ func generateJwtToken(application *Application, user *User, nonce string) (strin
|
||||
claims := Claims{
|
||||
User: user,
|
||||
Nonce: nonce,
|
||||
// FIXME: A workaround for custom claim by reusing `tag` in user info
|
||||
Tag: user.Tag,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
Issuer: beego.AppConfig.String("origin"),
|
||||
Subject: user.Id,
|
||||
@@ -57,24 +80,32 @@ func generateJwtToken(application *Application, user *User, nonce string) (strin
|
||||
ID: "",
|
||||
},
|
||||
}
|
||||
//all fields of the User struct are not added in "JWT-Empty" format
|
||||
|
||||
var token *jwt.Token
|
||||
var refreshToken *jwt.Token
|
||||
|
||||
// the JWT token length in "JWT-Empty" mode will be very short, as User object only has two properties: owner and name
|
||||
if application.TokenFormat == "JWT-Empty" {
|
||||
claims.User = nil
|
||||
claimsShort := getShortClaims(claims)
|
||||
|
||||
token = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsShort)
|
||||
claimsShort.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
|
||||
refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsShort)
|
||||
} else {
|
||||
token = jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
|
||||
claims.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
|
||||
refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
|
||||
}
|
||||
claims.Name = user.Name
|
||||
claims.Owner = user.Owner
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
|
||||
claims.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
|
||||
refreshToken := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
|
||||
cert := getCertByApplication(application)
|
||||
|
||||
// Use "token_jwt_key.key" as RSA private key
|
||||
privateKey := tokenJwtPrivateKey
|
||||
key, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(privateKey))
|
||||
// RSA private key
|
||||
key, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(cert.PrivateKey))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
token.Header["kid"] = cert.Name
|
||||
tokenString, err := token.SignedString(key)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
@@ -84,14 +115,14 @@ func generateJwtToken(application *Application, user *User, nonce string) (strin
|
||||
return tokenString, refreshTokenString, err
|
||||
}
|
||||
|
||||
func ParseJwtToken(token string) (*Claims, error) {
|
||||
func ParseJwtToken(token string, cert *Cert) (*Claims, error) {
|
||||
t, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
|
||||
// Use "token_jwt_key.pem" as RSA public key
|
||||
publicKey, err := jwt.ParseRSAPublicKeyFromPEM([]byte(tokenJwtPublicKey))
|
||||
// RSA public key
|
||||
publicKey, err := jwt.ParseRSAPublicKeyFromPEM([]byte(cert.PublicKey))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -20,19 +20,14 @@ import (
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/casbin/casdoor/util"
|
||||
)
|
||||
|
||||
func generateRsaKeys(fileId string) {
|
||||
func generateRsaKeys(bitSize int, expireInYears int, commonName string, organization string) (string, string) {
|
||||
// https://stackoverflow.com/questions/64104586/use-golang-to-get-rsa-key-the-same-way-openssl-genrsa
|
||||
// https://stackoverflow.com/questions/43822945/golang-can-i-create-x509keypair-using-rsa-key
|
||||
|
||||
bitSize := 4096
|
||||
|
||||
// Generate RSA key.
|
||||
key, err := rsa.GenerateKey(rand.Reader, bitSize)
|
||||
if err != nil {
|
||||
@@ -50,12 +45,12 @@ func generateRsaKeys(fileId string) {
|
||||
tml := x509.Certificate{
|
||||
// you can add any attr that you need
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(20, 0, 0),
|
||||
NotAfter: time.Now().AddDate(expireInYears, 0, 0),
|
||||
// you have to generate a different serial number each execution
|
||||
SerialNumber: big.NewInt(123456),
|
||||
Subject: pkix.Name{
|
||||
CommonName: "Casdoor Cert",
|
||||
Organization: []string{"Casdoor Organization"},
|
||||
CommonName: commonName,
|
||||
Organization: []string{organization},
|
||||
},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
@@ -70,9 +65,5 @@ func generateRsaKeys(fileId string) {
|
||||
Bytes: cert,
|
||||
})
|
||||
|
||||
// Write private key to file.
|
||||
util.WriteBytesToPath(privateKeyPem, fmt.Sprintf("%s.key", fileId))
|
||||
|
||||
// Write certificate (aka public key) to file.
|
||||
util.WriteBytesToPath(certPem, fmt.Sprintf("%s.pem", fileId))
|
||||
return string(certPem), string(privateKeyPem)
|
||||
}
|
||||
|
@@ -14,9 +14,20 @@
|
||||
|
||||
package object
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func TestGenerateRsaKeys(t *testing.T) {
|
||||
fileId := "token_jwt_key"
|
||||
generateRsaKeys(fileId)
|
||||
publicKey, privateKey := generateRsaKeys(4096, 20, "Casdoor Cert", "Casdoor Organization")
|
||||
|
||||
// Write certificate (aka public key) to file.
|
||||
util.WriteStringToPath(publicKey, fmt.Sprintf("%s.pem", fileId))
|
||||
|
||||
// Write private key to file.
|
||||
util.WriteStringToPath(privateKey, fmt.Sprintf("%s.key", fileId))
|
||||
}
|
||||
|
@@ -16,8 +16,9 @@ package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
@@ -27,21 +28,21 @@ type User struct {
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
|
||||
|
||||
Id string `xorm:"varchar(100)" json:"id"`
|
||||
Id string `xorm:"varchar(100) index" json:"id"`
|
||||
Type string `xorm:"varchar(100)" json:"type"`
|
||||
Password string `xorm:"varchar(100)" json:"password"`
|
||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
Avatar string `xorm:"varchar(255)" json:"avatar"`
|
||||
PermanentAvatar string `xorm:"varchar(255)" json:"permanentAvatar"`
|
||||
Email string `xorm:"varchar(100)" json:"email"`
|
||||
Phone string `xorm:"varchar(100)" json:"phone"`
|
||||
Avatar string `xorm:"varchar(500)" json:"avatar"`
|
||||
PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"`
|
||||
Email string `xorm:"varchar(100) index" json:"email"`
|
||||
Phone string `xorm:"varchar(100) index" json:"phone"`
|
||||
Location string `xorm:"varchar(100)" json:"location"`
|
||||
Address []string `json:"address"`
|
||||
Affiliation string `xorm:"varchar(100)" json:"affiliation"`
|
||||
Title string `xorm:"varchar(100)" json:"title"`
|
||||
IdCardType string `xorm:"varchar(100)" json:"idCardType"`
|
||||
IdCard string `xorm:"varchar(100)" json:"idCard"`
|
||||
IdCard string `xorm:"varchar(100) index" json:"idCard"`
|
||||
Homepage string `xorm:"varchar(100)" json:"homepage"`
|
||||
Bio string `xorm:"varchar(100)" json:"bio"`
|
||||
Tag string `xorm:"varchar(100)" json:"tag"`
|
||||
@@ -78,6 +79,7 @@ type User struct {
|
||||
Wecom string `xorm:"wecom varchar(100)" json:"wecom"`
|
||||
Lark string `xorm:"lark varchar(100)" json:"lark"`
|
||||
Gitlab string `xorm:"gitlab varchar(100)" json:"gitlab"`
|
||||
Baidu string `xorm:"baidu varchar(100)" json:"baidu"`
|
||||
Apple string `xorm:"apple varchar(100)" json:"apple"`
|
||||
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"`
|
||||
Slack string `xorm:"slack varchar(100)" json:"slack"`
|
||||
@@ -86,8 +88,12 @@ type User struct {
|
||||
Properties map[string]string `json:"properties"`
|
||||
}
|
||||
|
||||
func GetGlobalUserCount() int {
|
||||
count, err := adapter.Engine.Count(&User{})
|
||||
func GetGlobalUserCount(field, value string) int {
|
||||
session := adapter.Engine.Where("1=1")
|
||||
if field != "" && value != "" {
|
||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
||||
}
|
||||
count, err := session.Count(&User{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -105,9 +111,10 @@ func GetGlobalUsers() []*User {
|
||||
return users
|
||||
}
|
||||
|
||||
func GetPaginationGlobalUsers(offset, limit int) []*User {
|
||||
func GetPaginationGlobalUsers(offset, limit int, field, value, sortField, sortOrder string) []*User {
|
||||
users := []*User{}
|
||||
err := adapter.Engine.Desc("created_time").Limit(limit, offset).Find(&users)
|
||||
session := GetSession("", offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&users)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -115,8 +122,12 @@ func GetPaginationGlobalUsers(offset, limit int) []*User {
|
||||
return users
|
||||
}
|
||||
|
||||
func GetUserCount(owner string) int {
|
||||
count, err := adapter.Engine.Count(&User{Owner: owner})
|
||||
func GetUserCount(owner, field, value string) int {
|
||||
session := adapter.Engine.Where("owner=?", owner)
|
||||
if field != "" && value != "" {
|
||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
||||
}
|
||||
count, err := session.Count(&User{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -153,9 +164,10 @@ func GetSortedUsers(owner string, sorter string, limit int) []*User {
|
||||
return users
|
||||
}
|
||||
|
||||
func GetPaginationUsers(owner string, offset, limit int) []*User {
|
||||
func GetPaginationUsers(owner string, offset, limit int, field, value, sortField, sortOrder string) []*User {
|
||||
users := []*User{}
|
||||
err := adapter.Engine.Desc("created_time").Limit(limit, offset).Find(&users, &User{Owner: owner})
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&users)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -181,6 +193,24 @@ func getUser(owner string, name string) *User {
|
||||
}
|
||||
}
|
||||
|
||||
func getUserById(owner string, id string) *User {
|
||||
if owner == "" || id == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
user := User{Owner: owner, Id: id}
|
||||
existed, err := adapter.Engine.Get(&user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &user
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetUserByEmail(owner string, email string) *User {
|
||||
if owner == "" || email == "" {
|
||||
return nil
|
||||
@@ -241,7 +271,7 @@ func GetLastUser(owner string) *User {
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpdateUser(id string, user *User, columns []string) bool {
|
||||
func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) bool {
|
||||
owner, name := util.GetOwnerAndNameFromIdNoCheck(id)
|
||||
oldUser := getUser(owner, name)
|
||||
if oldUser == nil {
|
||||
@@ -256,9 +286,12 @@ func UpdateUser(id string, user *User, columns []string) bool {
|
||||
|
||||
if len(columns) == 0 {
|
||||
columns = []string{"owner", "display_name", "avatar",
|
||||
"location", "address", "region", "language", "affiliation", "title", "homepage", "bio", "score", "tag",
|
||||
"location", "address", "region", "language", "affiliation", "title", "homepage", "bio", "score", "tag", "signup_application",
|
||||
"is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties"}
|
||||
}
|
||||
if isGlobalAdmin {
|
||||
columns = append(columns, "name")
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).Cols(columns...).Update(user)
|
||||
if err != nil {
|
||||
@@ -289,25 +322,6 @@ func UpdateUserForAllFields(id string, user *User) bool {
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func UpdateUserForOriginalFields(user *User) bool {
|
||||
owner, name := util.GetOwnerAndNameFromId(user.GetId())
|
||||
oldUser := getUser(owner, name)
|
||||
if oldUser == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if user.Avatar != oldUser.Avatar && user.Avatar != "" {
|
||||
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{user.Owner, user.Name}).Cols("display_name", "password", "phone", "avatar", "affiliation", "score", "is_forbidden", "hash", "pre_hash").Update(user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func AddUser(user *User) bool {
|
||||
if user.Id == "" {
|
||||
user.Id = util.GenerateId()
|
||||
@@ -338,9 +352,10 @@ func AddUsers(users []*User) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
organization := GetOrganizationByUser(users[0])
|
||||
//organization := GetOrganizationByUser(users[0])
|
||||
for _, user := range users {
|
||||
user.UpdateUserPassword(organization)
|
||||
// this function is only used for syncer or batch upload, so no need to encrypt the password
|
||||
//user.UpdateUserPassword(organization)
|
||||
|
||||
user.UpdateUserHash()
|
||||
user.PreHash = user.Hash
|
||||
@@ -398,3 +413,7 @@ func LinkUserAccount(user *User, field string, value string) bool {
|
||||
func (user *User) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", user.Owner, user.Name)
|
||||
}
|
||||
|
||||
func isUserIdGlobalAdmin(userId string) bool {
|
||||
return strings.HasPrefix(userId, "built-in/")
|
||||
}
|
||||
|
@@ -14,17 +14,15 @@
|
||||
|
||||
package object
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/casbin/casdoor/cred"
|
||||
"github.com/casbin/casdoor/util"
|
||||
)
|
||||
import "github.com/casdoor/casdoor/cred"
|
||||
|
||||
func calculateHash(user *User) string {
|
||||
s := strings.Join([]string{user.Id, user.Password, user.DisplayName, user.Avatar, user.Phone, strconv.Itoa(user.Score)}, "|")
|
||||
return util.GetMd5Hash(s)
|
||||
syncer := getDbSyncerForUser(user)
|
||||
if syncer == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return syncer.calculateHash(user)
|
||||
}
|
||||
|
||||
func (user *User) UpdateUserHash() {
|
||||
@@ -35,7 +33,7 @@ func (user *User) UpdateUserHash() {
|
||||
func (user *User) UpdateUserPassword(organization *Organization) {
|
||||
credManager := cred.GetCredManager(organization.PasswordType)
|
||||
if credManager != nil {
|
||||
sealedPassword := credManager.GetSealedPassword(user.Password, user.PasswordSalt, organization.PasswordSalt)
|
||||
user.Password = sealedPassword
|
||||
hashedPassword := credManager.GetHashedPassword(user.Password, user.PasswordSalt, organization.PasswordSalt)
|
||||
user.Password = hashedPassword
|
||||
}
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
|
114
object/user_upload.go
Normal file
114
object/user_upload.go
Normal file
@@ -0,0 +1,114 @@
|
||||
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package object
|
||||
|
||||
import (
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/casdoor/casdoor/xlsx"
|
||||
)
|
||||
|
||||
func getUserMap(owner string) map[string]*User {
|
||||
m := map[string]*User{}
|
||||
|
||||
users := GetUsers(owner)
|
||||
for _, user := range users {
|
||||
m[user.GetId()] = user
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func parseLineItem(line *[]string, i int) string {
|
||||
if i >= len(*line) {
|
||||
return ""
|
||||
} else {
|
||||
return (*line)[i]
|
||||
}
|
||||
}
|
||||
|
||||
func parseLineItemInt(line *[]string, i int) int {
|
||||
s := parseLineItem(line, i)
|
||||
return util.ParseInt(s)
|
||||
}
|
||||
|
||||
func parseLineItemBool(line *[]string, i int) bool {
|
||||
return parseLineItemInt(line, i) != 0
|
||||
}
|
||||
|
||||
func UploadUsers(owner string, fileId string) bool {
|
||||
table := xlsx.ReadXlsxFile(fileId)
|
||||
|
||||
oldUserMap := getUserMap(owner)
|
||||
newUsers := []*User{}
|
||||
for _, line := range table {
|
||||
if parseLineItem(&line, 0) == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Owner: parseLineItem(&line, 0),
|
||||
Name: parseLineItem(&line, 1),
|
||||
CreatedTime: parseLineItem(&line, 2),
|
||||
UpdatedTime: parseLineItem(&line, 3),
|
||||
Id: parseLineItem(&line, 4),
|
||||
Type: parseLineItem(&line, 5),
|
||||
Password: parseLineItem(&line, 6),
|
||||
PasswordSalt: parseLineItem(&line, 7),
|
||||
DisplayName: parseLineItem(&line, 8),
|
||||
Avatar: parseLineItem(&line, 9),
|
||||
PermanentAvatar: "",
|
||||
Email: parseLineItem(&line, 10),
|
||||
Phone: parseLineItem(&line, 11),
|
||||
Location: parseLineItem(&line, 12),
|
||||
Address: []string{parseLineItem(&line, 13)},
|
||||
Affiliation: parseLineItem(&line, 14),
|
||||
Title: parseLineItem(&line, 15),
|
||||
IdCardType: parseLineItem(&line, 16),
|
||||
IdCard: parseLineItem(&line, 17),
|
||||
Homepage: parseLineItem(&line, 18),
|
||||
Bio: parseLineItem(&line, 19),
|
||||
Tag: parseLineItem(&line, 20),
|
||||
Region: parseLineItem(&line, 21),
|
||||
Language: parseLineItem(&line, 22),
|
||||
Gender: parseLineItem(&line, 23),
|
||||
Birthday: parseLineItem(&line, 24),
|
||||
Education: parseLineItem(&line, 25),
|
||||
Score: parseLineItemInt(&line, 26),
|
||||
Ranking: parseLineItemInt(&line, 27),
|
||||
IsDefaultAvatar: false,
|
||||
IsOnline: parseLineItemBool(&line, 28),
|
||||
IsAdmin: parseLineItemBool(&line, 29),
|
||||
IsGlobalAdmin: parseLineItemBool(&line, 30),
|
||||
IsForbidden: parseLineItemBool(&line, 31),
|
||||
IsDeleted: parseLineItemBool(&line, 32),
|
||||
SignupApplication: parseLineItem(&line, 33),
|
||||
Hash: "",
|
||||
PreHash: "",
|
||||
CreatedIp: parseLineItem(&line, 34),
|
||||
LastSigninTime: parseLineItem(&line, 35),
|
||||
LastSigninIp: parseLineItem(&line, 36),
|
||||
Properties: map[string]string{},
|
||||
}
|
||||
|
||||
if _, ok := oldUserMap[user.GetId()]; !ok {
|
||||
newUsers = append(newUsers, user)
|
||||
}
|
||||
}
|
||||
|
||||
if len(newUsers) == 0 {
|
||||
return false
|
||||
}
|
||||
return AddUsersInBatch(newUsers)
|
||||
}
|
@@ -19,7 +19,7 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/casbin/casdoor/idp"
|
||||
"github.com/casdoor/casdoor/idp"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
@@ -64,6 +64,12 @@ func GetUserByFields(organization string, field string) *User {
|
||||
return user
|
||||
}
|
||||
|
||||
// check ID card
|
||||
user = GetUserByField(organization, "id_card", field)
|
||||
if user != nil {
|
||||
return user
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@@ -21,7 +21,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
|
@@ -17,24 +17,37 @@ package object
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
type Header struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type Webhook 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"`
|
||||
|
||||
Url string `xorm:"varchar(100)" json:"url"`
|
||||
ContentType string `xorm:"varchar(100)" json:"contentType"`
|
||||
Events []string `xorm:"varchar(100)" json:"events"`
|
||||
|
||||
Organization string `xorm:"varchar(100) index" json:"organization"`
|
||||
|
||||
Url string `xorm:"varchar(100)" json:"url"`
|
||||
Method string `xorm:"varchar(100)" json:"method"`
|
||||
ContentType string `xorm:"varchar(100)" json:"contentType"`
|
||||
Headers []*Header `xorm:"mediumtext" json:"headers"`
|
||||
Events []string `xorm:"varchar(100)" json:"events"`
|
||||
IsUserExtended bool `json:"isUserExtended"`
|
||||
IsEnabled bool `json:"isEnabled"`
|
||||
}
|
||||
|
||||
func GetWebhookCount(owner string) int {
|
||||
count, err := adapter.Engine.Count(&Webhook{Owner: owner})
|
||||
func GetWebhookCount(owner, field, value string) int {
|
||||
session := adapter.Engine.Where("owner=?", owner)
|
||||
if field != "" && value != "" {
|
||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
||||
}
|
||||
count, err := session.Count(&Webhook{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -52,9 +65,10 @@ func GetWebhooks(owner string) []*Webhook {
|
||||
return webhooks
|
||||
}
|
||||
|
||||
func GetPaginationWebhooks(owner string, offset, limit int) []*Webhook {
|
||||
func GetPaginationWebhooks(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Webhook {
|
||||
webhooks := []*Webhook{}
|
||||
err := adapter.Engine.Desc("created_time").Limit(limit, offset).Find(&webhooks, &Webhook{Owner: owner})
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&webhooks)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@@ -18,7 +18,7 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func sendWebhook(webhook *Webhook, record *Record) error {
|
||||
@@ -26,13 +26,17 @@ func sendWebhook(webhook *Webhook, record *Record) error {
|
||||
|
||||
body := strings.NewReader(util.StructToJson(record))
|
||||
|
||||
req, err := http.NewRequest("POST", webhook.Url, body)
|
||||
req, err := http.NewRequest(webhook.Method, webhook.Url, body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", webhook.ContentType)
|
||||
|
||||
for _, header := range webhook.Headers {
|
||||
req.Header.Set(header.Name, header.Value)
|
||||
}
|
||||
|
||||
_, err = client.Do(req)
|
||||
return err
|
||||
}
|
||||
|
@@ -20,8 +20,8 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/astaxie/beego/context"
|
||||
"github.com/casbin/casdoor/authz"
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/authz"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
type Object struct {
|
||||
|
@@ -19,8 +19,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/context"
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func AutoSigninFilter(ctx *context.Context) {
|
||||
@@ -31,7 +31,8 @@ func AutoSigninFilter(ctx *context.Context) {
|
||||
// "/page?access_token=123"
|
||||
accessToken := ctx.Input.Query("accessToken")
|
||||
if accessToken != "" {
|
||||
claims, err := object.ParseJwtToken(accessToken)
|
||||
cert := object.GetDefaultCert()
|
||||
claims, err := object.ParseJwtToken(accessToken, cert)
|
||||
if err != nil {
|
||||
responseError(ctx, "invalid JWT token")
|
||||
return
|
||||
@@ -71,7 +72,8 @@ func AutoSigninFilter(ctx *context.Context) {
|
||||
// Authorization: Bearer bearerToken
|
||||
bearerToken := parseBearerToken(ctx)
|
||||
if bearerToken != "" {
|
||||
claims, err := object.ParseJwtToken(bearerToken)
|
||||
cert := object.GetDefaultCert()
|
||||
claims, err := object.ParseJwtToken(bearerToken, cert)
|
||||
if err != nil {
|
||||
responseError(ctx, err.Error())
|
||||
return
|
||||
|
@@ -19,8 +19,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego/context"
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
|
@@ -18,8 +18,8 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/astaxie/beego/context"
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func getUser(ctx *context.Context) (username string) {
|
||||
|
@@ -22,7 +22,7 @@ package routers
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
|
||||
"github.com/casbin/casdoor/controllers"
|
||||
"github.com/casdoor/casdoor/controllers"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -68,6 +68,19 @@ func initAPI() {
|
||||
beego.Router("/api/update-user", &controllers.ApiController{}, "POST:UpdateUser")
|
||||
beego.Router("/api/add-user", &controllers.ApiController{}, "POST:AddUser")
|
||||
beego.Router("/api/delete-user", &controllers.ApiController{}, "POST:DeleteUser")
|
||||
beego.Router("/api/upload-users", &controllers.ApiController{}, "POST:UploadUsers")
|
||||
|
||||
beego.Router("/api/get-roles", &controllers.ApiController{}, "GET:GetRoles")
|
||||
beego.Router("/api/get-role", &controllers.ApiController{}, "GET:GetRole")
|
||||
beego.Router("/api/update-role", &controllers.ApiController{}, "POST:UpdateRole")
|
||||
beego.Router("/api/add-role", &controllers.ApiController{}, "POST:AddRole")
|
||||
beego.Router("/api/delete-role", &controllers.ApiController{}, "POST:DeleteRole")
|
||||
|
||||
beego.Router("/api/get-permissions", &controllers.ApiController{}, "GET:GetPermissions")
|
||||
beego.Router("/api/get-permission", &controllers.ApiController{}, "GET:GetPermission")
|
||||
beego.Router("/api/update-permission", &controllers.ApiController{}, "POST:UpdatePermission")
|
||||
beego.Router("/api/add-permission", &controllers.ApiController{}, "POST:AddPermission")
|
||||
beego.Router("/api/delete-permission", &controllers.ApiController{}, "POST:DeletePermission")
|
||||
|
||||
beego.Router("/api/set-password", &controllers.ApiController{}, "POST:SetPassword")
|
||||
beego.Router("/api/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword")
|
||||
@@ -112,6 +125,7 @@ func initAPI() {
|
||||
beego.Router("/api/delete-token", &controllers.ApiController{}, "POST:DeleteToken")
|
||||
beego.Router("/api/login/oauth/code", &controllers.ApiController{}, "POST:GetOAuthCode")
|
||||
beego.Router("/api/login/oauth/access_token", &controllers.ApiController{}, "POST:GetOAuthToken")
|
||||
beego.Router("/api/login/oauth/refresh_token", &controllers.ApiController{}, "POST:RefreshToken")
|
||||
|
||||
beego.Router("/api/get-records", &controllers.ApiController{}, "GET:GetRecords")
|
||||
beego.Router("/api/get-records-filter", &controllers.ApiController{}, "POST:GetRecordsByFilter")
|
||||
@@ -128,6 +142,12 @@ func initAPI() {
|
||||
beego.Router("/api/add-syncer", &controllers.ApiController{}, "POST:AddSyncer")
|
||||
beego.Router("/api/delete-syncer", &controllers.ApiController{}, "POST:DeleteSyncer")
|
||||
|
||||
beego.Router("/api/get-certs", &controllers.ApiController{}, "GET:GetCerts")
|
||||
beego.Router("/api/get-cert", &controllers.ApiController{}, "GET:GetCert")
|
||||
beego.Router("/api/update-cert", &controllers.ApiController{}, "POST:UpdateCert")
|
||||
beego.Router("/api/add-cert", &controllers.ApiController{}, "POST:AddCert")
|
||||
beego.Router("/api/delete-cert", &controllers.ApiController{}, "POST:DeleteCert")
|
||||
|
||||
beego.Router("/api/send-email", &controllers.ApiController{}, "POST:SendEmail")
|
||||
beego.Router("/api/send-sms", &controllers.ApiController{}, "POST:SendSms")
|
||||
|
||||
|
@@ -19,7 +19,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego/context"
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func StaticFilter(ctx *context.Context) {
|
||||
|
19
util/path.go
19
util/path.go
@@ -18,6 +18,7 @@ import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -28,6 +29,24 @@ func FileExist(path string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func GetPath(path string) string {
|
||||
return filepath.Dir(path)
|
||||
}
|
||||
|
||||
func EnsureFileFolderExists(path string) {
|
||||
p := GetPath(path)
|
||||
if !FileExist(p) {
|
||||
err := os.MkdirAll(p, os.ModePerm)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func RemoveExt(filename string) string {
|
||||
return filename[:len(filename)-len(filepath.Ext(filename))]
|
||||
}
|
||||
|
||||
func UrlJoin(base string, path string) string {
|
||||
res := fmt.Sprintf("%s/%s", strings.TrimRight(base, "/"), strings.TrimLeft(path, "/"))
|
||||
return res
|
||||
|
21
util/setting.go
Normal file
21
util/setting.go
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package util
|
||||
|
||||
import "fmt"
|
||||
|
||||
func GetUploadXlsxPath(fileId string) string {
|
||||
return fmt.Sprintf("tmpFiles/%s.xlsx", fileId)
|
||||
}
|
@@ -15,6 +15,7 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
@@ -22,11 +23,16 @@ import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func ParseInt(s string) int {
|
||||
if s == "" {
|
||||
return 0
|
||||
}
|
||||
|
||||
i, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -35,6 +41,35 @@ func ParseInt(s string) int {
|
||||
return i
|
||||
}
|
||||
|
||||
func ParseBool(s string) bool {
|
||||
i := ParseInt(s)
|
||||
return i != 0
|
||||
}
|
||||
|
||||
func BoolToString(b bool) string {
|
||||
if b {
|
||||
return "1"
|
||||
} else {
|
||||
return "0"
|
||||
}
|
||||
}
|
||||
|
||||
func CamelToSnakeCase(camel string) string {
|
||||
var buf bytes.Buffer
|
||||
for _, c := range camel {
|
||||
if 'A' <= c && c <= 'Z' {
|
||||
// just convert [A-Z] to _[a-z]
|
||||
if buf.Len() > 0 {
|
||||
buf.WriteRune('_')
|
||||
}
|
||||
buf.WriteRune(c - 'A' + 'a')
|
||||
} else {
|
||||
buf.WriteRune(c)
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func GetOwnerAndNameFromId(id string) (string, string) {
|
||||
tokens := strings.Split(id, "/")
|
||||
if len(tokens) != 2 {
|
||||
@@ -128,3 +163,32 @@ func WriteBytesToPath(b []byte, path string) {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// SnakeString XxYy to xx_yy
|
||||
func SnakeString(s string) string {
|
||||
data := make([]byte, 0, len(s)*2)
|
||||
j := false
|
||||
num := len(s)
|
||||
for i := 0; i < num; i++ {
|
||||
d := s[i]
|
||||
if i > 0 && d >= 'A' && d <= 'Z' && j {
|
||||
data = append(data, '_')
|
||||
}
|
||||
if d != '_' {
|
||||
j = true
|
||||
}
|
||||
data = append(data, d)
|
||||
}
|
||||
return strings.ToLower(string(data[:]))
|
||||
}
|
||||
|
||||
func IsChinese(str string) bool {
|
||||
var flag bool
|
||||
for _, v := range str {
|
||||
if unicode.Is(unicode.Han, v) {
|
||||
flag = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return flag
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
project_id: '463556'
|
||||
project_id: '491513'
|
||||
api_token_env: 'CROWDIN_PERSONAL_TOKEN'
|
||||
preserve_hierarchy: true
|
||||
files: [
|
||||
|
@@ -13,9 +13,11 @@
|
||||
"codemirror": "^5.61.1",
|
||||
"copy-to-clipboard": "^3.3.1",
|
||||
"craco-less": "^1.17.1",
|
||||
"file-saver": "^2.0.5",
|
||||
"i18n-iso-countries": "^7.0.0",
|
||||
"i18next": "^19.8.9",
|
||||
"moment": "^2.29.1",
|
||||
"qs": "^6.10.2",
|
||||
"react": "^17.0.2",
|
||||
"react-codemirror2": "^7.2.1",
|
||||
"react-cropper": "^2.1.7",
|
||||
@@ -23,6 +25,7 @@
|
||||
"react-dom": "^17.0.2",
|
||||
"react-github-corner": "^2.5.0",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-highlight-words": "^0.17.0",
|
||||
"react-i18next": "^11.8.7",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "4.0.3",
|
||||
|
@@ -23,12 +23,15 @@ import OrganizationListPage from "./OrganizationListPage";
|
||||
import OrganizationEditPage from "./OrganizationEditPage";
|
||||
import UserListPage from "./UserListPage";
|
||||
import UserEditPage from "./UserEditPage";
|
||||
import RoleListPage from "./RoleListPage";
|
||||
import RoleEditPage from "./RoleEditPage";
|
||||
import PermissionListPage from "./PermissionListPage";
|
||||
import PermissionEditPage from "./PermissionEditPage";
|
||||
import ProviderListPage from "./ProviderListPage";
|
||||
import ProviderEditPage from "./ProviderEditPage";
|
||||
import ApplicationListPage from "./ApplicationListPage";
|
||||
import ApplicationEditPage from "./ApplicationEditPage";
|
||||
import ResourceListPage from "./ResourceListPage";
|
||||
// import ResourceEditPage from "./ResourceEditPage";
|
||||
import LdapEditPage from "./LdapEditPage";
|
||||
import LdapSyncPage from "./LdapSyncPage";
|
||||
import TokenListPage from "./TokenListPage";
|
||||
@@ -38,6 +41,8 @@ import WebhookListPage from "./WebhookListPage";
|
||||
import WebhookEditPage from "./WebhookEditPage";
|
||||
import SyncerListPage from "./SyncerListPage";
|
||||
import SyncerEditPage from "./SyncerEditPage";
|
||||
import CertListPage from "./CertListPage";
|
||||
import CertEditPage from "./CertEditPage";
|
||||
import AccountPage from "./account/AccountPage";
|
||||
import HomePage from "./basic/HomePage";
|
||||
import CustomGithubCorner from "./CustomGithubCorner";
|
||||
@@ -101,6 +106,10 @@ class App extends Component {
|
||||
this.setState({ selectedMenuKey: '/organizations' });
|
||||
} else if (uri.includes('/users')) {
|
||||
this.setState({ selectedMenuKey: '/users' });
|
||||
} else if (uri.includes('/roles')) {
|
||||
this.setState({ selectedMenuKey: '/roles' });
|
||||
} else if (uri.includes('/permissions')) {
|
||||
this.setState({ selectedMenuKey: '/permissions' });
|
||||
} else if (uri.includes('/providers')) {
|
||||
this.setState({ selectedMenuKey: '/providers' });
|
||||
} else if (uri.includes('/applications')) {
|
||||
@@ -115,6 +124,8 @@ class App extends Component {
|
||||
this.setState({ selectedMenuKey: '/webhooks' });
|
||||
} else if (uri.includes('/syncers')) {
|
||||
this.setState({ selectedMenuKey: '/syncers' });
|
||||
} else if (uri.includes('/certs')) {
|
||||
this.setState({ selectedMenuKey: '/certs' });
|
||||
} else if (uri.includes('/signup')) {
|
||||
this.setState({ selectedMenuKey: '/signup' });
|
||||
} else if (uri.includes('/login')) {
|
||||
@@ -324,6 +335,20 @@ class App extends Component {
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
res.push(
|
||||
<Menu.Item key="/roles">
|
||||
<Link to="/roles">
|
||||
{i18next.t("general:Roles")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
res.push(
|
||||
<Menu.Item key="/permissions">
|
||||
<Link to="/permissions">
|
||||
{i18next.t("general:Permissions")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
res.push(
|
||||
<Menu.Item key="/providers">
|
||||
<Link to="/providers">
|
||||
@@ -376,6 +401,13 @@ class App extends Component {
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
res.push(
|
||||
<Menu.Item key="/certs">
|
||||
<Link to="/certs">
|
||||
{i18next.t("general:Certs")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
res.push(
|
||||
<Menu.Item key="/swagger">
|
||||
<a target="_blank" rel="noreferrer" href={Setting.isLocalhost() ? `${Setting.ServerUrl}/swagger` : "/swagger"}>
|
||||
@@ -426,6 +458,10 @@ class App extends Component {
|
||||
<Route exact path="/organizations/:organizationName/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/users/:organizationName/:userName" render={(props) => <UserEditPage account={this.state.account} {...props} />}/>
|
||||
<Route exact path="/roles" render={(props) => this.renderLoginIfNotLoggedIn(<RoleListPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/roles/:organizationName/:roleName" render={(props) => this.renderLoginIfNotLoggedIn(<RoleEditPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/permissions" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionListPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/permissions/:organizationName/:permissionName" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionEditPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/providers/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)}/>
|
||||
@@ -440,6 +476,8 @@ class App extends Component {
|
||||
<Route exact path="/webhooks/:webhookName" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookEditPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/syncers" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerListPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/certs" render={(props) => this.renderLoginIfNotLoggedIn(<CertListPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/certs/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />}/>
|
||||
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
|
||||
|
@@ -82,7 +82,7 @@ class ApplicationEditPage extends React.Component {
|
||||
}
|
||||
|
||||
parseApplicationField(key, value) {
|
||||
if (["expireInHours"].includes(key) || ["refreshExpireInHours"].includes(key)) {
|
||||
if (["expireInHours", "refreshExpireInHours"].includes(key)) {
|
||||
value = Setting.myParseInt(value);
|
||||
}
|
||||
return value;
|
||||
@@ -132,7 +132,7 @@ class ApplicationEditPage extends React.Component {
|
||||
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.application.name} onChange={e => {
|
||||
<Input value={this.state.application.name} disabled={this.state.application.name === "app-built-in"} onChange={e => {
|
||||
this.updateApplicationField('name', e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
@@ -240,7 +240,7 @@ class ApplicationEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:Token format"), i18next.t("general:Token format - Tooltip"))} :
|
||||
{Setting.getLabel(i18next.t("application:Token format"), i18next.t("application:Token format - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: '100%'}} value={this.state.application.tokenFormat} onChange={(value => {this.updateApplicationField('tokenFormat', value);})}>
|
||||
@@ -253,7 +253,7 @@ class ApplicationEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Token expire"), i18next.t("general:Token expire - Tooltip"))} :
|
||||
{Setting.getLabel(i18next.t("application:Token expire"), i18next.t("application:Token expire - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input style={{width: "150px"}} value={this.state.application.expireInHours} suffix="Hours" onChange={e => {
|
||||
@@ -263,7 +263,7 @@ class ApplicationEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Refresh token expire"), i18next.t("general:Refresh token expire - Tooltip"))} :
|
||||
{Setting.getLabel(i18next.t("application:Refresh token expire"), i18next.t("application:Refresh token expire - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input style={{width: "150px"}} value={this.state.application.refreshExpireInHours} suffix="Hours" onChange={e => {
|
||||
@@ -293,7 +293,17 @@ class ApplicationEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:Enable Code Signin"), i18next.t("application:Enable Code Signin - Tooltip"))} :
|
||||
{Setting.getLabel(i18next.t("application:Signin session"), i18next.t("application:Enable signin session - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={1} >
|
||||
<Switch checked={this.state.application.enableSigninSession} onChange={checked => {
|
||||
this.updateApplicationField('enableSigninSession', checked);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:Enable code signin"), i18next.t("application:Enable code signin - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={1} >
|
||||
<Switch checked={this.state.application.enableCodeSignin} onChange={checked => {
|
||||
|
@@ -20,32 +20,9 @@ import moment from "moment";
|
||||
import * as Setting from "./Setting";
|
||||
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
||||
import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
|
||||
class ApplicationListPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
applications: null,
|
||||
total: 0,
|
||||
};
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this.getApplications(1, 10);
|
||||
}
|
||||
|
||||
getApplications(page, pageSize) {
|
||||
ApplicationBackend.getApplications("admin", page, pageSize)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState({
|
||||
applications: res.data,
|
||||
total: res.data2
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
class ApplicationListPage extends BaseListPage {
|
||||
|
||||
newApplication() {
|
||||
const randomName = Setting.getRandomName();
|
||||
@@ -57,7 +34,8 @@ class ApplicationListPage extends React.Component {
|
||||
logo: "https://cdn.casbin.com/logo/logo_1024x256.png",
|
||||
enablePassword: true,
|
||||
enableSignUp: true,
|
||||
EnableCodeSignin: false,
|
||||
enableSigninSession: true,
|
||||
enableCodeSignin: false,
|
||||
providers: [],
|
||||
signupItems: [
|
||||
{name: "ID", visible: false, required: true, rule: "Random"},
|
||||
@@ -80,10 +58,6 @@ class ApplicationListPage extends React.Component {
|
||||
ApplicationBackend.addApplication(newApplication)
|
||||
.then((res) => {
|
||||
Setting.showMessage("success", `Application added successfully`);
|
||||
this.setState({
|
||||
applications: Setting.prependRow(this.state.applications, newApplication),
|
||||
total: this.state.total + 1
|
||||
});
|
||||
this.props.history.push(`/applications/${newApplication.name}`);
|
||||
}
|
||||
)
|
||||
@@ -93,12 +67,12 @@ class ApplicationListPage extends React.Component {
|
||||
}
|
||||
|
||||
deleteApplication(i) {
|
||||
ApplicationBackend.deleteApplication(this.state.applications[i])
|
||||
ApplicationBackend.deleteApplication(this.state.data[i])
|
||||
.then((res) => {
|
||||
Setting.showMessage("success", `Application deleted successfully`);
|
||||
this.setState({
|
||||
applications: Setting.deleteRow(this.state.applications, i),
|
||||
total: this.state.total - 1
|
||||
data: Setting.deleteRow(this.state.data, i),
|
||||
pagination: {total: this.state.pagination.total - 1},
|
||||
});
|
||||
}
|
||||
)
|
||||
@@ -115,7 +89,8 @@ class ApplicationListPage extends React.Component {
|
||||
key: 'name',
|
||||
width: '150px',
|
||||
fixed: 'left',
|
||||
sorter: (a, b) => a.name.localeCompare(b.name),
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps('name'),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/applications/${text}`}>
|
||||
@@ -129,7 +104,7 @@ class ApplicationListPage extends React.Component {
|
||||
dataIndex: 'createdTime',
|
||||
key: 'createdTime',
|
||||
width: '160px',
|
||||
sorter: (a, b) => a.createdTime.localeCompare(b.createdTime),
|
||||
sorter: true,
|
||||
render: (text, record, index) => {
|
||||
return Setting.getFormattedDate(text);
|
||||
}
|
||||
@@ -139,7 +114,8 @@ class ApplicationListPage extends React.Component {
|
||||
dataIndex: 'displayName',
|
||||
key: 'displayName',
|
||||
// width: '100px',
|
||||
sorter: (a, b) => a.displayName.localeCompare(b.displayName),
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps('displayName'),
|
||||
},
|
||||
{
|
||||
title: 'Logo',
|
||||
@@ -159,7 +135,8 @@ class ApplicationListPage extends React.Component {
|
||||
dataIndex: 'organization',
|
||||
key: 'organization',
|
||||
width: '150px',
|
||||
sorter: (a, b) => a.organization.localeCompare(b.organization),
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps('organization'),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/organizations/${text}`}>
|
||||
@@ -172,6 +149,7 @@ class ApplicationListPage extends React.Component {
|
||||
title: i18next.t("general:Providers"),
|
||||
dataIndex: 'providers',
|
||||
key: 'providers',
|
||||
...this.getColumnSearchProps('providers'),
|
||||
// width: '600px',
|
||||
render: (text, record, index) => {
|
||||
const providers = text;
|
||||
@@ -236,8 +214,9 @@ class ApplicationListPage extends React.Component {
|
||||
<Popconfirm
|
||||
title={`Sure to delete application: ${record.name} ?`}
|
||||
onConfirm={() => this.deleteApplication(index)}
|
||||
disabled={record.name === "app-built-in"}
|
||||
>
|
||||
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button>
|
||||
<Button style={{marginBottom: '10px'}} disabled={record.name === "app-built-in"} type="danger">{i18next.t("general:Delete")}</Button>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
)
|
||||
@@ -246,12 +225,10 @@ class ApplicationListPage extends React.Component {
|
||||
];
|
||||
|
||||
const paginationProps = {
|
||||
total: this.state.total,
|
||||
total: this.state.pagination.total,
|
||||
showQuickJumper: true,
|
||||
showSizeChanger: true,
|
||||
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.total),
|
||||
onChange: (page, pageSize) => this.getApplications(page, pageSize),
|
||||
onShowSizeChange: (current, size) => this.getApplications(current, size),
|
||||
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -263,21 +240,33 @@ class ApplicationListPage extends React.Component {
|
||||
<Button type="primary" size="small" onClick={this.addApplication.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||
</div>
|
||||
)}
|
||||
loading={applications === null}
|
||||
loading={this.state.loading}
|
||||
onChange={this.handleTableChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
this.renderTable(this.state.applications)
|
||||
fetch = (params = {}) => {
|
||||
let field = params.searchedColumn, value = params.searchText;
|
||||
let sortField = params.sortField, sortOrder = params.sortOrder;
|
||||
this.setState({ loading: true });
|
||||
ApplicationBackend.getApplications("admin", 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,
|
||||
});
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default ApplicationListPage;
|
||||
|
138
web/src/BaseListPage.js
Normal file
138
web/src/BaseListPage.js
Normal file
@@ -0,0 +1,138 @@
|
||||
// Copyright 2021 The casbin 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, Input, Space} from "antd";
|
||||
import {SearchOutlined} from "@ant-design/icons";
|
||||
import Highlighter from "react-highlight-words";
|
||||
|
||||
class BaseListPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
data: [],
|
||||
pagination: {
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
},
|
||||
loading: false,
|
||||
searchText: '',
|
||||
searchedColumn: '',
|
||||
};
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
const { pagination } = this.state;
|
||||
this.fetch({ pagination });
|
||||
}
|
||||
|
||||
getColumnSearchProps = dataIndex => ({
|
||||
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => (
|
||||
<div style={{ padding: 8 }}>
|
||||
<Input
|
||||
ref={node => {
|
||||
this.searchInput = node;
|
||||
}}
|
||||
placeholder={`Search ${dataIndex}`}
|
||||
value={selectedKeys[0]}
|
||||
onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
|
||||
onPressEnter={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
|
||||
style={{ marginBottom: 8, display: 'block' }}
|
||||
/>
|
||||
<Space>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
|
||||
icon={<SearchOutlined />}
|
||||
size="small"
|
||||
style={{ width: 90 }}
|
||||
>
|
||||
Search
|
||||
</Button>
|
||||
<Button onClick={() => this.handleReset(clearFilters)} size="small" style={{ width: 90 }}>
|
||||
Reset
|
||||
</Button>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
confirm({ closeDropdown: false });
|
||||
this.setState({
|
||||
searchText: selectedKeys[0],
|
||||
searchedColumn: dataIndex,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Filter
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
),
|
||||
filterIcon: filtered => <SearchOutlined style={{ color: filtered ? '#1890ff' : undefined }} />,
|
||||
onFilter: (value, record) =>
|
||||
record[dataIndex]
|
||||
? record[dataIndex].toString().toLowerCase().includes(value.toLowerCase())
|
||||
: '',
|
||||
onFilterDropdownVisibleChange: visible => {
|
||||
if (visible) {
|
||||
setTimeout(() => this.searchInput.select(), 100);
|
||||
}
|
||||
},
|
||||
render: text =>
|
||||
this.state.searchedColumn === dataIndex ? (
|
||||
<Highlighter
|
||||
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
|
||||
searchWords={[this.state.searchText]}
|
||||
autoEscape
|
||||
textToHighlight={text ? text.toString() : ''}
|
||||
/>
|
||||
) : (
|
||||
text
|
||||
),
|
||||
});
|
||||
|
||||
handleSearch = (selectedKeys, confirm, dataIndex) => {
|
||||
this.fetch({searchText: selectedKeys[0], searchedColumn: dataIndex, pagination: this.state.pagination});
|
||||
};
|
||||
|
||||
handleReset = clearFilters => {
|
||||
clearFilters();
|
||||
const { pagination } = this.state;
|
||||
this.fetch({ pagination });
|
||||
};
|
||||
|
||||
handleTableChange = (pagination, filters, sorter) => {
|
||||
this.fetch({
|
||||
sortField: sorter.field,
|
||||
sortOrder: sorter.order,
|
||||
pagination,
|
||||
...filters,
|
||||
searchText: this.state.searchText,
|
||||
searchedColumn: this.state.searchedColumn,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
this.renderTable(this.state.data)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default BaseListPage;
|
254
web/src/CertEditPage.js
Normal file
254
web/src/CertEditPage.js
Normal file
@@ -0,0 +1,254 @@
|
||||
// Copyright 2021 The casbin 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, InputNumber, Row, Select} from 'antd';
|
||||
import * as CertBackend from "./backend/CertBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import i18next from "i18next";
|
||||
import copy from "copy-to-clipboard";
|
||||
import FileSaver from "file-saver";
|
||||
|
||||
const { Option } = Select;
|
||||
const { TextArea } = Input;
|
||||
|
||||
class CertEditPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
certName: props.match.params.certName,
|
||||
cert: null,
|
||||
};
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this.getCert();
|
||||
}
|
||||
|
||||
getCert() {
|
||||
CertBackend.getCert("admin", this.state.certName)
|
||||
.then((cert) => {
|
||||
this.setState({
|
||||
cert: cert,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
parseCertField(key, value) {
|
||||
if (["port"].includes(key)) {
|
||||
value = Setting.myParseInt(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
updateCertField(key, value) {
|
||||
value = this.parseCertField(key, value);
|
||||
|
||||
let cert = this.state.cert;
|
||||
cert[key] = value;
|
||||
this.setState({
|
||||
cert: cert,
|
||||
});
|
||||
}
|
||||
|
||||
renderCert() {
|
||||
return (
|
||||
<Card size="small" title={
|
||||
<div>
|
||||
{i18next.t("cert:Edit Cert")}
|
||||
<Button onClick={() => this.submitCertEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||
<Button style={{marginLeft: '20px'}} type="primary" onClick={() => this.submitCertEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||
</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:Name"), i18next.t("general:Name - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.cert.name} onChange={e => {
|
||||
this.updateCertField('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.cert.displayName} onChange={e => {
|
||||
this.updateCertField('displayName', e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("cert:Scope"), i18next.t("cert:Scope - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: '100%'}} value={this.state.cert.scope} onChange={(value => {
|
||||
this.updateCertField('scope', value);
|
||||
})}>
|
||||
{
|
||||
[
|
||||
{id: 'JWT', name: 'JWT'},
|
||||
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("cert:Type"), i18next.t("cert:Type - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: '100%'}} value={this.state.cert.type} onChange={(value => {
|
||||
this.updateCertField('type', value);
|
||||
})}>
|
||||
{
|
||||
[
|
||||
{id: 'x509', name: 'x509'},
|
||||
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("cert:Crypto algorithm"), i18next.t("cert:Crypto algorithm - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: '100%'}} value={this.state.cert.cryptoAlgorithm} onChange={(value => {
|
||||
this.updateCertField('cryptoAlgorithm', value);
|
||||
})}>
|
||||
{
|
||||
[
|
||||
{id: 'RSA', name: 'RSA'},
|
||||
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("cert:Bit size"), i18next.t("cert:Bit size - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<InputNumber value={this.state.cert.bitSize} onChange={value => {
|
||||
this.updateCertField('bitSize', value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("cert:Expire in years"), i18next.t("cert:Expire in years - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<InputNumber value={this.state.cert.expireInYears} onChange={value => {
|
||||
this.updateCertField('expireInYears', value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("cert:Public key"), i18next.t("cert:Public key - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={9} >
|
||||
<Button style={{marginRight: '10px', marginBottom: '10px'}} onClick={() => {
|
||||
copy(this.state.cert.publicKey);
|
||||
Setting.showMessage("success", i18next.t("cert:Public key copied to clipboard successfully"));
|
||||
}}
|
||||
>
|
||||
{i18next.t("cert:Copy public key")}
|
||||
</Button>
|
||||
<Button type="primary" onClick={() => {
|
||||
const blob = new Blob([this.state.cert.publicKey], {type: "text/plain;charset=utf-8"});
|
||||
FileSaver.saveAs(blob, "token_jwt_key.pem");
|
||||
}}
|
||||
>
|
||||
{i18next.t("cert:Download public key")}
|
||||
</Button>
|
||||
<TextArea autoSize={{minRows: 30, maxRows: 30}} value={this.state.cert.publicKey} onChange={e => {
|
||||
this.updateCertField('publicKey', e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
<Col span={1} />
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("cert:Private key"), i18next.t("cert:Private key - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={9} >
|
||||
<Button style={{marginRight: '10px', marginBottom: '10px'}} onClick={() => {
|
||||
copy(this.state.cert.privateKey);
|
||||
Setting.showMessage("success", i18next.t("cert:Private key copied to clipboard successfully"));
|
||||
}}
|
||||
>
|
||||
{i18next.t("cert:Copy private key")}
|
||||
</Button>
|
||||
<Button type="primary" onClick={() => {
|
||||
const blob = new Blob([this.state.cert.privateKey], {type: "text/plain;charset=utf-8"});
|
||||
FileSaver.saveAs(blob, "token_jwt_key.key");
|
||||
}}
|
||||
>
|
||||
{i18next.t("cert:Download private key")}
|
||||
</Button>
|
||||
<TextArea autoSize={{minRows: 30, maxRows: 30}} value={this.state.cert.privateKey} onChange={e => {
|
||||
this.updateCertField('privateKey', e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
submitCertEdit(willExist) {
|
||||
let cert = Setting.deepCopy(this.state.cert);
|
||||
CertBackend.updateCert(this.state.cert.owner, this.state.certName, cert)
|
||||
.then((res) => {
|
||||
if (res.msg === "") {
|
||||
Setting.showMessage("success", `Successfully saved`);
|
||||
this.setState({
|
||||
certName: this.state.cert.name,
|
||||
});
|
||||
|
||||
if (willExist) {
|
||||
this.props.history.push(`/certs`);
|
||||
} else {
|
||||
this.props.history.push(`/certs/${this.state.cert.name}`);
|
||||
}
|
||||
} else {
|
||||
Setting.showMessage("error", res.msg);
|
||||
this.updateCertField('name', this.state.certName);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
Setting.showMessage("error", `Failed to connect to server: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
this.state.cert !== null ? this.renderCert() : null
|
||||
}
|
||||
<div style={{marginTop: '20px', marginLeft: '40px'}}>
|
||||
<Button size="large" onClick={() => this.submitCertEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||
<Button style={{marginLeft: '20px'}} type="primary" size="large" onClick={() => this.submitCertEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default CertEditPage;
|
230
web/src/CertListPage.js
Normal file
230
web/src/CertListPage.js
Normal file
@@ -0,0 +1,230 @@
|
||||
// Copyright 2021 The casbin 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, Table} from 'antd';
|
||||
import moment from "moment";
|
||||
import * as Setting from "./Setting";
|
||||
import * as CertBackend from "./backend/CertBackend";
|
||||
import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
|
||||
class CertListPage extends BaseListPage {
|
||||
|
||||
newCert() {
|
||||
const randomName = Setting.getRandomName();
|
||||
return {
|
||||
owner: "admin", // this.props.account.certname,
|
||||
name: `cert_${randomName}`,
|
||||
createdTime: moment().format(),
|
||||
displayName: `New Cert - ${randomName}`,
|
||||
scope: "JWT",
|
||||
type: "x509",
|
||||
cryptoAlgorithm: "RSA",
|
||||
bitSize: 4096,
|
||||
expireInYears: 20,
|
||||
publicKey: "",
|
||||
privateKey: "",
|
||||
}
|
||||
}
|
||||
|
||||
addCert() {
|
||||
const newCert = this.newCert();
|
||||
CertBackend.addCert(newCert)
|
||||
.then((res) => {
|
||||
Setting.showMessage("success", `Cert added successfully`);
|
||||
this.props.history.push(`/certs/${newCert.name}`);
|
||||
}
|
||||
)
|
||||
.catch(error => {
|
||||
Setting.showMessage("error", `Cert failed to add: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
deleteCert(i) {
|
||||
CertBackend.deleteCert(this.state.data[i])
|
||||
.then((res) => {
|
||||
Setting.showMessage("success", `Cert deleted successfully`);
|
||||
this.setState({
|
||||
data: Setting.deleteRow(this.state.data, i),
|
||||
pagination: {total: this.state.pagination.total - 1},
|
||||
});
|
||||
}
|
||||
)
|
||||
.catch(error => {
|
||||
Setting.showMessage("error", `Cert failed to delete: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
renderTable(certs) {
|
||||
const columns = [
|
||||
{
|
||||
title: i18next.t("general:Name"),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: '120px',
|
||||
fixed: 'left',
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps('name'),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/certs/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Created time"),
|
||||
dataIndex: 'createdTime',
|
||||
key: 'createdTime',
|
||||
width: '180px',
|
||||
sorter: true,
|
||||
render: (text, record, index) => {
|
||||
return Setting.getFormattedDate(text);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Display name"),
|
||||
dataIndex: 'displayName',
|
||||
key: 'displayName',
|
||||
// width: '100px',
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps('displayName'),
|
||||
},
|
||||
{
|
||||
title: i18next.t("cert:Scope"),
|
||||
dataIndex: 'scope',
|
||||
key: 'scope',
|
||||
filterMultiple: false,
|
||||
filters: [
|
||||
{text: 'JWT', value: 'JWT'},
|
||||
],
|
||||
width: '110px',
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: i18next.t("cert:Type"),
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
filterMultiple: false,
|
||||
filters: [
|
||||
{text: 'x509', value: 'x509'},
|
||||
],
|
||||
width: '110px',
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: i18next.t("cert:Crypto algorithm"),
|
||||
dataIndex: 'cryptoAlgorithm',
|
||||
key: 'cryptoAlgorithm',
|
||||
filterMultiple: false,
|
||||
filters: [
|
||||
{text: 'RSA', value: 'RSA'},
|
||||
],
|
||||
width: '190px',
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: i18next.t("cert:Bit size"),
|
||||
dataIndex: 'bitSize',
|
||||
key: 'bitSize',
|
||||
width: '130px',
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps('bitSize'),
|
||||
},
|
||||
{
|
||||
title: i18next.t("cert:Expire in years"),
|
||||
dataIndex: 'expireInYears',
|
||||
key: 'expireInYears',
|
||||
width: '170px',
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps('expireInYears'),
|
||||
},
|
||||
{
|
||||
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(`/certs/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||
<Popconfirm
|
||||
title={`Sure to delete cert: ${record.name} ?`}
|
||||
onConfirm={() => this.deleteCert(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={certs} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||
title={() => (
|
||||
<div>
|
||||
{i18next.t("general:Certs")}
|
||||
<Button type="primary" size="small" onClick={this.addCert.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.category !== undefined && params.category !== null) {
|
||||
field = "category";
|
||||
value = params.category;
|
||||
} else if (params.type !== undefined && params.type !== null) {
|
||||
field = "type";
|
||||
value = params.type;
|
||||
}
|
||||
this.setState({ loading: true });
|
||||
CertBackend.getCerts("admin", 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 CertListPage;
|
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
export const ShowGithubCorner = false;
|
||||
export const GithubRepo = "https://github.com/casbin/casdoor";
|
||||
export const GithubRepo = "https://github.com/casdoor/casdoor";
|
||||
|
||||
export const ForceLanguage = "";
|
||||
export const DefaultLanguage = "en";
|
||||
|
@@ -94,7 +94,7 @@ class OrganizationEditPage extends React.Component {
|
||||
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.organization.name} onChange={e => {
|
||||
<Input value={this.state.organization.name} disabled={this.state.organization.name === "built-in"} onChange={e => {
|
||||
this.updateOrganizationField('name', e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
@@ -153,7 +153,7 @@ class OrganizationEditPage extends React.Component {
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: '100%'}} value={this.state.organization.passwordType} onChange={(value => {this.updateOrganizationField('passwordType', value);})}>
|
||||
{
|
||||
['plain', 'salt', 'md5-salt']
|
||||
['plain', 'salt', 'md5-salt', 'bcrypt']
|
||||
.map((item, index) => <Option key={index} value={item}>{item}</Option>)
|
||||
}
|
||||
</Select>
|
||||
|
@@ -19,32 +19,9 @@ import moment from "moment";
|
||||
import * as Setting from "./Setting";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
|
||||
class OrganizationListPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
organizations: null,
|
||||
total: 0
|
||||
};
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this.getOrganizations(1, 10);
|
||||
}
|
||||
|
||||
getOrganizations(page, pageSize) {
|
||||
OrganizationBackend.getOrganizations("admin", page, pageSize)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState({
|
||||
organizations: res.data,
|
||||
total: res.data2
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
class OrganizationListPage extends BaseListPage {
|
||||
|
||||
newOrganization() {
|
||||
const randomName = Setting.getRandomName();
|
||||
@@ -69,10 +46,6 @@ class OrganizationListPage extends React.Component {
|
||||
OrganizationBackend.addOrganization(newOrganization)
|
||||
.then((res) => {
|
||||
Setting.showMessage("success", `Organization added successfully`);
|
||||
this.setState({
|
||||
organizations: Setting.prependRow(this.state.organizations, newOrganization),
|
||||
total: this.state.total + 1
|
||||
});
|
||||
this.props.history.push(`/organizations/${newOrganization.name}`);
|
||||
}
|
||||
)
|
||||
@@ -82,12 +55,12 @@ class OrganizationListPage extends React.Component {
|
||||
}
|
||||
|
||||
deleteOrganization(i) {
|
||||
OrganizationBackend.deleteOrganization(this.state.organizations[i])
|
||||
OrganizationBackend.deleteOrganization(this.state.data[i])
|
||||
.then((res) => {
|
||||
Setting.showMessage("success", `Organization deleted successfully`);
|
||||
this.setState({
|
||||
organizations: Setting.deleteRow(this.state.organizations, i),
|
||||
total: this.state.total - 1
|
||||
data: Setting.deleteRow(this.state.data, i),
|
||||
pagination: {total: this.state.pagination.total - 1},
|
||||
});
|
||||
}
|
||||
)
|
||||
@@ -104,7 +77,8 @@ class OrganizationListPage extends React.Component {
|
||||
key: 'name',
|
||||
width: '120px',
|
||||
fixed: 'left',
|
||||
sorter: (a, b) => a.name.localeCompare(b.name),
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps('name'),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/organizations/${text}`}>
|
||||
@@ -118,7 +92,7 @@ class OrganizationListPage extends React.Component {
|
||||
dataIndex: 'createdTime',
|
||||
key: 'createdTime',
|
||||
width: '160px',
|
||||
sorter: (a, b) => a.createdTime.localeCompare(b.createdTime),
|
||||
sorter: true,
|
||||
render: (text, record, index) => {
|
||||
return Setting.getFormattedDate(text);
|
||||
}
|
||||
@@ -128,7 +102,8 @@ class OrganizationListPage extends React.Component {
|
||||
dataIndex: 'displayName',
|
||||
key: 'displayName',
|
||||
// width: '100px',
|
||||
sorter: (a, b) => a.displayName.localeCompare(b.displayName),
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps('displayName'),
|
||||
},
|
||||
{
|
||||
title: i18next.t("organization:Favicon"),
|
||||
@@ -148,7 +123,8 @@ class OrganizationListPage extends React.Component {
|
||||
dataIndex: 'websiteUrl',
|
||||
key: 'websiteUrl',
|
||||
width: '300px',
|
||||
sorter: (a, b) => a.websiteUrl.localeCompare(b.websiteUrl),
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps('websiteUrl'),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<a target="_blank" rel="noreferrer" href={text}>
|
||||
@@ -162,14 +138,21 @@ class OrganizationListPage extends React.Component {
|
||||
dataIndex: 'passwordType',
|
||||
key: 'passwordType',
|
||||
width: '150px',
|
||||
sorter: (a, b) => a.passwordType.localeCompare(b.passwordType),
|
||||
sorter: true,
|
||||
filterMultiple: false,
|
||||
filters: [
|
||||
{text: 'plain', value: 'plain'},
|
||||
{text: 'salt', value: 'salt'},
|
||||
{text: 'md5-salt', value: 'md5-salt'},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Password salt"),
|
||||
dataIndex: 'passwordSalt',
|
||||
key: 'passwordSalt',
|
||||
width: '150px',
|
||||
sorter: (a, b) => a.passwordSalt.localeCompare(b.passwordSalt),
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps('passwordSalt'),
|
||||
},
|
||||
{
|
||||
title: i18next.t("organization:Default avatar"),
|
||||
@@ -189,7 +172,7 @@ class OrganizationListPage extends React.Component {
|
||||
dataIndex: 'enableSoftDeletion',
|
||||
key: 'enableSoftDeletion',
|
||||
width: '140px',
|
||||
sorter: (a, b) => a.enableSoftDeletion - b.enableSoftDeletion,
|
||||
sorter: true,
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
|
||||
@@ -210,8 +193,9 @@ class OrganizationListPage extends React.Component {
|
||||
<Popconfirm
|
||||
title={`Sure to delete organization: ${record.name} ?`}
|
||||
onConfirm={() => this.deleteOrganization(index)}
|
||||
disabled={record.name === "built-in"}
|
||||
>
|
||||
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button>
|
||||
<Button style={{marginBottom: '10px'}} disabled={record.name === "built-in"} type="danger">{i18next.t("general:Delete")}</Button>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
)
|
||||
@@ -220,12 +204,10 @@ class OrganizationListPage extends React.Component {
|
||||
];
|
||||
|
||||
const paginationProps = {
|
||||
total: this.state.total,
|
||||
total: this.state.pagination.total,
|
||||
showQuickJumper: true,
|
||||
showSizeChanger: true,
|
||||
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.total),
|
||||
onChange: (page, pageSize) => this.getOrganizations(page, pageSize),
|
||||
onShowSizeChange: (current, size) => this.getOrganizations(current, size),
|
||||
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -237,21 +219,37 @@ class OrganizationListPage extends React.Component {
|
||||
<Button type="primary" size="small" onClick={this.addOrganization.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||
</div>
|
||||
)}
|
||||
loading={organizations === null}
|
||||
loading={this.state.loading}
|
||||
onChange={this.handleTableChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
this.renderTable(this.state.organizations)
|
||||
fetch = (params = {}) => {
|
||||
let field = params.searchedColumn, value = params.searchText;
|
||||
let sortField = params.sortField, sortOrder = params.sortOrder;
|
||||
if (params.passwordType !== undefined && params.passwordType !== null) {
|
||||
field = "passwordType";
|
||||
value = params.passwordType;
|
||||
}
|
||||
this.setState({ loading: true });
|
||||
OrganizationBackend.getOrganizations("admin", 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,
|
||||
});
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default OrganizationListPage;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user