mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-18 06:53:50 +08:00
Compare commits
53 Commits
Author | SHA1 | Date | |
---|---|---|---|
3c2f7b7fc8 | |||
fbc73de3bb | |||
479daf4fa4 | |||
d129202b95 | |||
c1f553440e | |||
7dcae2d183 | |||
5ec0c7a890 | |||
051752340d | |||
c87c001da3 | |||
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 |
2
.github/workflows/sync.yml
vendored
2
.github/workflows/sync.yml
vendored
@ -7,7 +7,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
synchronize-with-crowdin:
|
synchronize-with-crowdin:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.repository == 'casbin/casdoor' && github.event_name == 'push'
|
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push'
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
@ -20,7 +20,7 @@ COPY --from=FRONT /web/build /web/build
|
|||||||
CMD chmod 777 /tmp && service mariadb start&&\
|
CMD chmod 777 /tmp && service mariadb start&&\
|
||||||
if [ "${MYSQL_ROOT_PASSWORD}" = "" ] ;then MYSQL_ROOT_PASSWORD=123456 ; fi&&\
|
if [ "${MYSQL_ROOT_PASSWORD}" = "" ] ;then MYSQL_ROOT_PASSWORD=123456 ; fi&&\
|
||||||
mysqladmin -u root password ${MYSQL_ROOT_PASSWORD} &&\
|
mysqladmin -u root password ${MYSQL_ROOT_PASSWORD} &&\
|
||||||
./wait-for-it localhost:3306 -- ./server
|
./wait-for-it localhost:3306 -- ./server --createDatabase=true
|
||||||
|
|
||||||
|
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
@ -32,5 +32,5 @@ COPY --from=BACK /go/src/casdoor/ ./
|
|||||||
COPY --from=BACK /usr/bin/wait-for-it ./
|
COPY --from=BACK /usr/bin/wait-for-it ./
|
||||||
RUN mkdir -p web/build && apk add --no-cache bash coreutils
|
RUN mkdir -p web/build && apk add --no-cache bash coreutils
|
||||||
COPY --from=FRONT /web/build /web/build
|
COPY --from=FRONT /web/build /web/build
|
||||||
CMD ./wait-for-it db:3306 -- ./server
|
CMD ./server
|
||||||
|
|
||||||
|
23
README.md
23
README.md
@ -7,10 +7,10 @@
|
|||||||
<a href="https://hub.docker.com/r/casbin/casdoor">
|
<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">
|
<img alt="docker pull casbin/casdoor" src="https://img.shields.io/docker/pulls/casbin/casdoor.svg">
|
||||||
</a>
|
</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">
|
<img alt="GitHub Workflow Status (branch)" src="https://github.com/casbin/jcasbin/workflows/build/badge.svg?style=flat-square">
|
||||||
</a>
|
</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">
|
<img alt="GitHub Release" src="https://img.shields.io/github/v/release/casbin/casdoor.svg">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://hub.docker.com/repository/docker/casbin/casdoor">
|
<a href="https://hub.docker.com/repository/docker/casbin/casdoor">
|
||||||
@ -19,24 +19,27 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://goreportcard.com/report/github.com/casbin/casdoor">
|
<a href="https://goreportcard.com/report/github.com/casdoor/casdoor">
|
||||||
<img alt="Go Report Card" src="https://goreportcard.com/badge/github.com/casbin/casdoor?style=flat-square">
|
<img alt="Go Report Card" src="https://goreportcard.com/badge/github.com/casdoor/casdoor?style=flat-square">
|
||||||
</a>
|
</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">
|
<img src="https://img.shields.io/github/license/casbin/casdoor?style=flat-square" alt="license">
|
||||||
</a>
|
</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">
|
<img alt="GitHub issues" src="https://img.shields.io/github/issues/casbin/casdoor?style=flat-square">
|
||||||
</a>
|
</a>
|
||||||
<a href="#">
|
<a href="#">
|
||||||
<img alt="GitHub stars" src="https://img.shields.io/github/stars/casbin/casdoor?style=flat-square">
|
<img alt="GitHub stars" src="https://img.shields.io/github/stars/casbin/casdoor?style=flat-square">
|
||||||
</a>
|
</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">
|
<img alt="GitHub forks" src="https://img.shields.io/github/forks/casbin/casdoor?style=flat-square">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://crowdin.com/project/casdoor-site">
|
<a href="https://crowdin.com/project/casdoor-site">
|
||||||
<img alt="Crowdin" src="https://badges.crowdin.net/casdoor-site/localized.svg">
|
<img alt="Crowdin" src="https://badges.crowdin.net/casdoor-site/localized.svg">
|
||||||
</a>
|
</a>
|
||||||
|
<a href="https://gitter.im/casbin/casdoor">
|
||||||
|
<img alt="Gitter" src="https://badges.gitter.im/casbin/casdoor.svg">
|
||||||
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Online demo
|
## Online demo
|
||||||
@ -51,13 +54,13 @@ Run your own casdoor program in a few minutes.
|
|||||||
There are two methods, get code via go subcommand `get`:
|
There are two methods, get code via go subcommand `get`:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
go get github.com/casbin/casdoor
|
go get github.com/casdoor/casdoor
|
||||||
```
|
```
|
||||||
|
|
||||||
or `git`:
|
or `git`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/casbin/casdoor
|
git clone https://github.com/casdoor/casdoor
|
||||||
```
|
```
|
||||||
|
|
||||||
Finally, change directory:
|
Finally, change directory:
|
||||||
@ -179,5 +182,5 @@ If you are contributing to casdoor, please note that we use [Crowdin](https://cr
|
|||||||
|
|
||||||
## License
|
## 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/astaxie/beego"
|
||||||
"github.com/casbin/casbin/v2"
|
"github.com/casbin/casbin/v2"
|
||||||
"github.com/casbin/casbin/v2/model"
|
"github.com/casbin/casbin/v2/model"
|
||||||
"github.com/casbin/casdoor/conf"
|
|
||||||
xormadapter "github.com/casbin/xorm-adapter/v2"
|
xormadapter "github.com/casbin/xorm-adapter/v2"
|
||||||
|
"github.com/casdoor/casdoor/conf"
|
||||||
stringadapter "github.com/qiangmzsx/string-adapter/v2"
|
stringadapter "github.com/qiangmzsx/string-adapter/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,7 +28,8 @@ var Enforcer *casbin.Enforcer
|
|||||||
func InitAuthz() {
|
func InitAuthz() {
|
||||||
var err error
|
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 {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -79,7 +80,9 @@ p, *, *, POST, /api/login, *, *
|
|||||||
p, *, *, GET, /api/get-app-login, *, *
|
p, *, *, GET, /api/get-app-login, *, *
|
||||||
p, *, *, POST, /api/logout, *, *
|
p, *, *, POST, /api/logout, *, *
|
||||||
p, *, *, GET, /api/get-account, *, *
|
p, *, *, GET, /api/get-account, *, *
|
||||||
|
p, *, *, GET, /api/userinfo, *, *
|
||||||
p, *, *, POST, /api/login/oauth/access_token, *, *
|
p, *, *, POST, /api/login/oauth/access_token, *, *
|
||||||
|
p, *, *, POST, /api/login/oauth/refresh_token, *, *
|
||||||
p, *, *, GET, /api/get-application, *, *
|
p, *, *, GET, /api/get-application, *, *
|
||||||
p, *, *, GET, /api/get-users, *, *
|
p, *, *, GET, /api/get-users, *, *
|
||||||
p, *, *, GET, /api/get-user, *, *
|
p, *, *, GET, /api/get-user, *, *
|
||||||
|
@ -6,6 +6,8 @@ copyrequestbody = true
|
|||||||
driverName = mysql
|
driverName = mysql
|
||||||
dataSourceName = root:123456@tcp(localhost:3306)/
|
dataSourceName = root:123456@tcp(localhost:3306)/
|
||||||
dbName = casdoor
|
dbName = casdoor
|
||||||
|
tableNamePrefix =
|
||||||
|
showSql = false
|
||||||
redisEndpoint =
|
redisEndpoint =
|
||||||
defaultStorageProvider =
|
defaultStorageProvider =
|
||||||
isCloudIntranet = false
|
isCloudIntranet = false
|
||||||
|
@ -18,9 +18,11 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/casbin/casdoor/object"
|
"github.com/astaxie/beego"
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -38,6 +40,7 @@ type RequestForm struct {
|
|||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Phone string `json:"phone"`
|
Phone string `json:"phone"`
|
||||||
Affiliation string `json:"affiliation"`
|
Affiliation string `json:"affiliation"`
|
||||||
|
IdCard string `json:"idCard"`
|
||||||
Region string `json:"region"`
|
Region string `json:"region"`
|
||||||
|
|
||||||
Application string `json:"application"`
|
Application string `json:"application"`
|
||||||
@ -61,10 +64,23 @@ type Response struct {
|
|||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Msg string `json:"msg"`
|
Msg string `json:"msg"`
|
||||||
Sub string `json:"sub"`
|
Sub string `json:"sub"`
|
||||||
|
Name string `json:"name"`
|
||||||
Data interface{} `json:"data"`
|
Data interface{} `json:"data"`
|
||||||
Data2 interface{} `json:"data2"`
|
Data2 interface{} `json:"data2"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Userinfo struct {
|
||||||
|
Sub string `json:"sub"`
|
||||||
|
Iss string `json:"iss"`
|
||||||
|
Aud string `json:"aud"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
DisplayName string `json:"preferred_username,omitempty"`
|
||||||
|
Email string `json:"email,omitempty"`
|
||||||
|
Avatar string `json:"picture,omitempty"`
|
||||||
|
Address string `json:"address,omitempty"`
|
||||||
|
Phone string `json:"phone,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type HumanCheck struct {
|
type HumanCheck struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
AppKey string `json:"appKey"`
|
AppKey string `json:"appKey"`
|
||||||
@ -151,6 +167,7 @@ func (c *ApiController) Signup() {
|
|||||||
Phone: form.Phone,
|
Phone: form.Phone,
|
||||||
Address: []string{},
|
Address: []string{},
|
||||||
Affiliation: form.Affiliation,
|
Affiliation: form.Affiliation,
|
||||||
|
IdCard: form.IdCard,
|
||||||
Region: form.Region,
|
Region: form.Region,
|
||||||
Score: getInitScore(),
|
Score: getInitScore(),
|
||||||
IsAdmin: false,
|
IsAdmin: false,
|
||||||
@ -220,6 +237,7 @@ func (c *ApiController) GetAccount() {
|
|||||||
resp := Response{
|
resp := Response{
|
||||||
Status: "ok",
|
Status: "ok",
|
||||||
Sub: user.Id,
|
Sub: user.Id,
|
||||||
|
Name: user.Name,
|
||||||
Data: user,
|
Data: user,
|
||||||
Data2: organization,
|
Data2: organization,
|
||||||
}
|
}
|
||||||
@ -227,6 +245,47 @@ func (c *ApiController) GetAccount() {
|
|||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserInfo
|
||||||
|
// @Title UserInfo
|
||||||
|
// @Tag Account API
|
||||||
|
// @Description return user information according to OIDC standards
|
||||||
|
// @Success 200 {object} controllers.Userinfo The Response object
|
||||||
|
// @router /userinfo [get]
|
||||||
|
func (c *ApiController) GetUserinfo() {
|
||||||
|
userId, ok := c.RequireSignedIn()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user := object.GetUser(userId)
|
||||||
|
if user == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
scope, aud := c.GetSessionOidc()
|
||||||
|
iss := beego.AppConfig.String("origin")
|
||||||
|
resp := Userinfo{
|
||||||
|
Sub: user.Id,
|
||||||
|
Iss: iss,
|
||||||
|
Aud: aud,
|
||||||
|
}
|
||||||
|
if strings.Contains(scope, "profile") {
|
||||||
|
resp.Name = user.Name
|
||||||
|
resp.DisplayName = user.DisplayName
|
||||||
|
resp.Avatar = user.Avatar
|
||||||
|
}
|
||||||
|
if strings.Contains(scope, "email") {
|
||||||
|
resp.Email = user.Email
|
||||||
|
}
|
||||||
|
if strings.Contains(scope, "address") {
|
||||||
|
resp.Address = user.Location
|
||||||
|
}
|
||||||
|
if strings.Contains(scope, "phone") {
|
||||||
|
resp.Phone = user.Phone
|
||||||
|
}
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
// GetHumanCheck ...
|
// GetHumanCheck ...
|
||||||
// @Tag Login API
|
// @Tag Login API
|
||||||
// @Title GetHumancheck
|
// @Title GetHumancheck
|
||||||
|
@ -18,8 +18,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/astaxie/beego/utils/pagination"
|
"github.com/astaxie/beego/utils/pagination"
|
||||||
"github.com/casbin/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetApplications
|
// GetApplications
|
||||||
@ -30,6 +30,7 @@ import (
|
|||||||
// @Success 200 {array} object.Application The Response object
|
// @Success 200 {array} object.Application The Response object
|
||||||
// @router /get-applications [get]
|
// @router /get-applications [get]
|
||||||
func (c *ApiController) GetApplications() {
|
func (c *ApiController) GetApplications() {
|
||||||
|
userId := c.GetSessionUsername()
|
||||||
owner := c.Input().Get("owner")
|
owner := c.Input().Get("owner")
|
||||||
limit := c.Input().Get("pageSize")
|
limit := c.Input().Get("pageSize")
|
||||||
page := c.Input().Get("p")
|
page := c.Input().Get("p")
|
||||||
@ -37,13 +38,22 @@ func (c *ApiController) GetApplications() {
|
|||||||
value := c.Input().Get("value")
|
value := c.Input().Get("value")
|
||||||
sortField := c.Input().Get("sortField")
|
sortField := c.Input().Get("sortField")
|
||||||
sortOrder := c.Input().Get("sortOrder")
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
organization := c.Input().Get("organization")
|
||||||
|
|
||||||
if limit == "" || page == "" {
|
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()
|
c.ServeJSON()
|
||||||
} else {
|
} else {
|
||||||
limit := util.ParseInt(limit)
|
limit := util.ParseInt(limit)
|
||||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetApplicationCount(owner, field, value)))
|
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetApplicationCount(owner, field, value)))
|
||||||
applications := object.GetPaginationApplications(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
applications := object.GetMaskedApplications(object.GetPaginationApplications(owner, paginator.Offset(), limit, field, value, sortField, sortOrder), userId)
|
||||||
c.ResponseOk(applications, paginator.Nums())
|
c.ResponseOk(applications, paginator.Nums())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -56,9 +66,10 @@ func (c *ApiController) GetApplications() {
|
|||||||
// @Success 200 {object} object.Application The Response object
|
// @Success 200 {object} object.Application The Response object
|
||||||
// @router /get-application [get]
|
// @router /get-application [get]
|
||||||
func (c *ApiController) GetApplication() {
|
func (c *ApiController) GetApplication() {
|
||||||
|
userId := c.GetSessionUsername()
|
||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
c.Data["json"] = object.GetApplication(id)
|
c.Data["json"] = object.GetMaskedApplication(object.GetApplication(id), userId)
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,6 +81,7 @@ func (c *ApiController) GetApplication() {
|
|||||||
// @Success 200 {object} object.Application The Response object
|
// @Success 200 {object} object.Application The Response object
|
||||||
// @router /get-user-application [get]
|
// @router /get-user-application [get]
|
||||||
func (c *ApiController) GetUserApplication() {
|
func (c *ApiController) GetUserApplication() {
|
||||||
|
userId := c.GetSessionUsername()
|
||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
user := object.GetUser(id)
|
user := object.GetUser(id)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
@ -77,7 +89,7 @@ func (c *ApiController) GetUserApplication() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data["json"] = object.GetApplicationByUser(user)
|
c.Data["json"] = object.GetMaskedApplication(object.GetApplicationByUser(user), userId)
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,10 +24,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/astaxie/beego"
|
||||||
"github.com/casbin/casdoor/idp"
|
"github.com/casdoor/casdoor/idp"
|
||||||
"github.com/casbin/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casbin/casdoor/proxy"
|
"github.com/casdoor/casdoor/proxy"
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func codeToResponse(code *object.Code) *Response {
|
func codeToResponse(code *object.Code) *Response {
|
||||||
@ -52,7 +52,14 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
|||||||
scope := c.Input().Get("scope")
|
scope := c.Input().Get("scope")
|
||||||
state := c.Input().Get("state")
|
state := c.Input().Get("state")
|
||||||
nonce := c.Input().Get("nonce")
|
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)
|
resp = codeToResponse(code)
|
||||||
|
|
||||||
if application.EnableSigninSession || application.HasPromptPage() {
|
if application.EnableSigninSession || application.HasPromptPage() {
|
||||||
|
@ -15,10 +15,12 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
"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
|
// controller for handlers under /api uri
|
||||||
@ -35,6 +37,21 @@ type SessionData struct {
|
|||||||
ExpireTime int64
|
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 ...
|
// GetSessionUsername ...
|
||||||
func (c *ApiController) GetSessionUsername() string {
|
func (c *ApiController) GetSessionUsername() string {
|
||||||
// check if user session expired
|
// check if user session expired
|
||||||
@ -55,6 +72,28 @@ func (c *ApiController) GetSessionUsername() string {
|
|||||||
return user.(string)
|
return user.(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) GetSessionOidc() (string, string) {
|
||||||
|
sessionData := c.GetSessionData()
|
||||||
|
if sessionData != nil &&
|
||||||
|
sessionData.ExpireTime != 0 &&
|
||||||
|
sessionData.ExpireTime < time.Now().Unix() {
|
||||||
|
c.SetSessionUsername("")
|
||||||
|
c.SetSessionData(nil)
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
scopeValue := c.GetSession("scope")
|
||||||
|
audValue := c.GetSession("aud")
|
||||||
|
var scope, aud string
|
||||||
|
var ok bool
|
||||||
|
if scope, ok = scopeValue.(string); !ok {
|
||||||
|
scope = ""
|
||||||
|
}
|
||||||
|
if aud, ok = audValue.(string); !ok {
|
||||||
|
aud = ""
|
||||||
|
}
|
||||||
|
return scope, aud
|
||||||
|
}
|
||||||
|
|
||||||
// SetSessionUsername ...
|
// SetSessionUsername ...
|
||||||
func (c *ApiController) SetSessionUsername(user string) {
|
func (c *ApiController) SetSessionUsername(user string) {
|
||||||
c.SetSession("username", user)
|
c.SetSession("username", user)
|
||||||
|
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 (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/casbin/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LdapServer struct {
|
type LdapServer struct {
|
||||||
|
@ -17,7 +17,7 @@ package controllers
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/casbin/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LinkForm struct {
|
type LinkForm struct {
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import "github.com/casbin/casdoor/object"
|
import "github.com/casdoor/casdoor/object"
|
||||||
|
|
||||||
// @Title GetOidcDiscovery
|
// @Title GetOidcDiscovery
|
||||||
// @Tag OIDC API
|
// @Tag OIDC API
|
||||||
@ -28,7 +28,7 @@ func (c *RootController) GetOidcDiscovery() {
|
|||||||
// @Tag OIDC API
|
// @Tag OIDC API
|
||||||
// @router /api/certs [get]
|
// @router /api/certs [get]
|
||||||
func (c *RootController) GetOidcCert() {
|
func (c *RootController) GetOidcCert() {
|
||||||
jwks, err := object.GetJSONWebKeySet()
|
jwks, err := object.GetJsonWebKeySet()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
|
@ -18,8 +18,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/astaxie/beego/utils/pagination"
|
"github.com/astaxie/beego/utils/pagination"
|
||||||
"github.com/casbin/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetOrganizations ...
|
// GetOrganizations ...
|
||||||
|
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()
|
||||||
|
}
|
@ -17,8 +17,8 @@ package controllers
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/astaxie/beego/utils/pagination"
|
"github.com/astaxie/beego/utils/pagination"
|
||||||
"github.com/casbin/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetProviders
|
// GetProviders
|
||||||
|
@ -16,8 +16,8 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/astaxie/beego/utils/pagination"
|
"github.com/astaxie/beego/utils/pagination"
|
||||||
"github.com/casbin/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetRecords
|
// GetRecords
|
||||||
|
@ -23,8 +23,8 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/astaxie/beego/utils/pagination"
|
"github.com/astaxie/beego/utils/pagination"
|
||||||
"github.com/casbin/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// @router /get-resources [get]
|
// @router /get-resources [get]
|
||||||
@ -202,7 +202,7 @@ func (c *ApiController) UploadResource() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
user.Avatar = fileUrl
|
user.Avatar = fileUrl
|
||||||
object.UpdateUser(user.GetId(), user, []string{"avatar"})
|
object.UpdateUser(user.GetId(), user, []string{"avatar"}, false)
|
||||||
case "termsOfUse":
|
case "termsOfUse":
|
||||||
applicationId := fmt.Sprintf("admin/%s", parent)
|
applicationId := fmt.Sprintf("admin/%s", parent)
|
||||||
app := object.GetApplication(applicationId)
|
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"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/casbin/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SendEmail
|
// SendEmail
|
||||||
|
@ -18,8 +18,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/astaxie/beego/utils/pagination"
|
"github.com/astaxie/beego/utils/pagination"
|
||||||
"github.com/casbin/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetSyncers
|
// GetSyncers
|
||||||
|
@ -18,8 +18,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/astaxie/beego/utils/pagination"
|
"github.com/astaxie/beego/utils/pagination"
|
||||||
"github.com/casbin/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetTokens
|
// GetTokens
|
||||||
@ -142,7 +142,15 @@ func (c *ApiController) GetOAuthCode() {
|
|||||||
state := c.Input().Get("state")
|
state := c.Input().Get("state")
|
||||||
nonce := c.Input().Get("nonce")
|
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()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,12 +169,13 @@ func (c *ApiController) GetOAuthToken() {
|
|||||||
clientId := c.Input().Get("client_id")
|
clientId := c.Input().Get("client_id")
|
||||||
clientSecret := c.Input().Get("client_secret")
|
clientSecret := c.Input().Get("client_secret")
|
||||||
code := c.Input().Get("code")
|
code := c.Input().Get("code")
|
||||||
|
verifier := c.Input().Get("code_verifier")
|
||||||
|
|
||||||
if clientId == "" && clientSecret == "" {
|
if clientId == "" && clientSecret == "" {
|
||||||
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
|
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()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,8 +20,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/astaxie/beego/utils/pagination"
|
"github.com/astaxie/beego/utils/pagination"
|
||||||
"github.com/casbin/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetGlobalUsers
|
// GetGlobalUsers
|
||||||
@ -125,7 +125,8 @@ func (c *ApiController) UpdateUser() {
|
|||||||
columns = strings.Split(columnsStr, ",")
|
columns = strings.Split(columnsStr, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
affected := object.UpdateUser(id, &user, columns)
|
isGlobalAdmin := c.IsGlobalAdmin()
|
||||||
|
affected := object.UpdateUser(id, &user, columns, isGlobalAdmin)
|
||||||
if affected {
|
if affected {
|
||||||
object.UpdateUserToOriginalDatabase(&user)
|
object.UpdateUserToOriginalDatabase(&user)
|
||||||
}
|
}
|
||||||
@ -226,11 +227,6 @@ func (c *ApiController) SetPassword() {
|
|||||||
c.ResponseError("Please login first.")
|
c.ResponseError("Please login first.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
requestUser := object.GetUser(requestUserId)
|
|
||||||
if requestUser == nil {
|
|
||||||
c.ResponseError("Session outdated. Please login again.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
userId := fmt.Sprintf("%s/%s", userOwner, userName)
|
userId := fmt.Sprintf("%s/%s", userOwner, userName)
|
||||||
targetUser := object.GetUser(userId)
|
targetUser := object.GetUser(userId)
|
||||||
@ -240,7 +236,14 @@ func (c *ApiController) SetPassword() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hasPermission := false
|
hasPermission := false
|
||||||
|
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 {
|
if requestUser.IsGlobalAdmin {
|
||||||
hasPermission = true
|
hasPermission = true
|
||||||
} else if requestUserId == userId {
|
} else if requestUserId == userId {
|
||||||
@ -248,7 +251,7 @@ func (c *ApiController) SetPassword() {
|
|||||||
} else if targetUser.Owner == requestUser.Owner && requestUser.IsAdmin {
|
} else if targetUser.Owner == requestUser.Owner && requestUser.IsAdmin {
|
||||||
hasPermission = true
|
hasPermission = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if !hasPermission {
|
if !hasPermission {
|
||||||
c.ResponseError("You don't have the permission to do this.")
|
c.ResponseError("You don't have the permission to do this.")
|
||||||
return
|
return
|
||||||
@ -272,8 +275,6 @@ func (c *ApiController) SetPassword() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.SetSessionUsername("")
|
|
||||||
|
|
||||||
targetUser.Password = newPassword
|
targetUser.Password = newPassword
|
||||||
object.SetUserField(targetUser, "password", targetUser.Password)
|
object.SetUserField(targetUser, "password", targetUser.Password)
|
||||||
c.Data["json"] = Response{Status: "ok"}
|
c.Data["json"] = Response{Status: "ok"}
|
||||||
|
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"
|
"strconv"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/astaxie/beego"
|
||||||
"github.com/casbin/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ResponseOk ...
|
// ResponseOk ...
|
||||||
|
@ -19,8 +19,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/casbin/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *ApiController) getCurrentUser() *object.User {
|
func (c *ApiController) getCurrentUser() *object.User {
|
||||||
|
@ -18,8 +18,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/astaxie/beego/utils/pagination"
|
"github.com/astaxie/beego/utils/pagination"
|
||||||
"github.com/casbin/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetWebhooks
|
// GetWebhooks
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
version: '3.1'
|
version: '3.1'
|
||||||
services:
|
services:
|
||||||
|
restart: always
|
||||||
casdoor:
|
casdoor:
|
||||||
build:
|
build:
|
||||||
context: ./
|
context: ./
|
||||||
|
7
go.mod
7
go.mod
@ -1,4 +1,4 @@
|
|||||||
module github.com/casbin/casdoor
|
module github.com/casdoor/casdoor
|
||||||
|
|
||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
@ -8,7 +8,7 @@ require (
|
|||||||
github.com/aws/aws-sdk-go v1.37.30
|
github.com/aws/aws-sdk-go v1.37.30
|
||||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
|
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
|
||||||
github.com/casbin/casbin/v2 v2.30.1
|
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/casdoor/go-sms-sender v0.0.5
|
||||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||||
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
|
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
|
||||||
@ -18,13 +18,14 @@ require (
|
|||||||
github.com/google/uuid v1.2.0
|
github.com/google/uuid v1.2.0
|
||||||
github.com/jinzhu/configor v1.2.1 // indirect
|
github.com/jinzhu/configor v1.2.1 // indirect
|
||||||
github.com/markbates/goth v1.68.1-0.20211006204042-9dc8905b41c8
|
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/qiangmzsx/string-adapter/v2 v2.1.0
|
||||||
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76
|
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/gosaml2 v0.6.0
|
||||||
github.com/russellhaering/goxmldsig v1.1.1
|
github.com/russellhaering/goxmldsig v1.1.1
|
||||||
github.com/satori/go.uuid v1.2.0 // indirect
|
github.com/satori/go.uuid v1.2.0 // indirect
|
||||||
github.com/smartystreets/goconvey v1.6.4 // indirect
|
github.com/smartystreets/goconvey v1.6.4 // indirect
|
||||||
|
github.com/tealeg/xlsx v1.0.5
|
||||||
github.com/thanhpk/randstr v1.0.4
|
github.com/thanhpk/randstr v1.0.4
|
||||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
|
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
|
||||||
|
12
go.sum
12
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 h1:PuzlE8w0JBg/DhIqnkF1Dewf3z+qmUZMVN07PonvVUQ=
|
||||||
github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
|
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.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 h1:P5HWadDL7olwUXNdcuKUBk+x75Y2eitFxYTcLNKeKF0=
|
||||||
github.com/casbin/casbin/v2 v2.30.1/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
|
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.5.1 h1:BkpIxRHKa0s3bSMx173PpuU7oTs+Zw7XmD0BIta0HGM=
|
||||||
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/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 h1:9qhlMM+UoSOvvY7puUULqSHBBA7fbe02Px/tzchQboo=
|
||||||
github.com/casdoor/go-sms-sender v0.0.5/go.mod h1:TMM/BsZQAa+7JVDXl2KqgxnzZgCjmHEX5MBN662mM5M=
|
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=
|
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/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 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
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-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 h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
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/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 h1:J2Xj92efYLxPl3BiibgEDEUiMsCBzwTurE/8JjD8CG4=
|
||||||
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76/go.mod h1:JhtPzUhP5KGtCB2yksmxuYAD4hEWw4qGQJpucjsm3U0=
|
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.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.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
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 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 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
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 h1:THBgwGwUQtsw6L53cSSA2wwL3sLrm+HJ3Dk+ye/lMCI=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go v1.0.154/go.mod h1:asUz5BPXxgoPGaRgZaVm1iGcUAuHyYUo1nXqKa83cvI=
|
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=
|
github.com/thanhpk/randstr v1.0.4 h1:IN78qu/bR+My+gHCvMEXhR/i5oriVHcTB/BJJIRTsNo=
|
||||||
|
@ -21,7 +21,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type I18nData map[string]map[string]string
|
type I18nData map[string]map[string]string
|
||||||
|
@ -18,7 +18,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getI18nFilePath(language string) string {
|
func getI18nFilePath(language string) string {
|
||||||
|
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
|
||||||
|
}
|
@ -21,7 +21,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"github.com/markbates/goth"
|
"github.com/markbates/goth"
|
||||||
"github.com/markbates/goth/providers/amazon"
|
"github.com/markbates/goth/providers/amazon"
|
||||||
"github.com/markbates/goth/providers/apple"
|
"github.com/markbates/goth/providers/apple"
|
||||||
|
@ -60,6 +60,8 @@ func GetIdProvider(providerType string, clientId string, clientSecret string, re
|
|||||||
return NewLarkIdProvider(clientId, clientSecret, redirectUrl)
|
return NewLarkIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
} else if providerType == "GitLab" {
|
} else if providerType == "GitLab" {
|
||||||
return NewGitlabIdProvider(clientId, clientSecret, redirectUrl)
|
return NewGitlabIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
|
} else if providerType == "Baidu" {
|
||||||
|
return NewBaiduIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
} else if isGothSupport(providerType) {
|
} else if isGothSupport(providerType) {
|
||||||
return NewGothIdProvider(providerType, clientId, clientSecret, redirectUrl)
|
return NewGothIdProvider(providerType, clientId, clientSecret, redirectUrl)
|
||||||
}
|
}
|
||||||
|
167
idp/wecom_inter.go
Normal file
167
idp/wecom_inter.go
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
// 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 (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
//This idp is using wecom internal application api as idp
|
||||||
|
type WeComInternalIdProvider struct {
|
||||||
|
Client *http.Client
|
||||||
|
Config *oauth2.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWeComInternalIdProvider(clientId string, clientSecret string, redirectUrl string) *WeComInternalIdProvider {
|
||||||
|
idp := &WeComInternalIdProvider{}
|
||||||
|
|
||||||
|
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||||
|
idp.Config = config
|
||||||
|
|
||||||
|
return idp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *WeComInternalIdProvider) SetHttpClient(client *http.Client) {
|
||||||
|
idp.Client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *WeComInternalIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||||
|
var config = &oauth2.Config{
|
||||||
|
ClientID: clientId,
|
||||||
|
ClientSecret: clientSecret,
|
||||||
|
RedirectURL: redirectUrl,
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
type WecomInterToken struct {
|
||||||
|
Errcode int `json:"errcode"`
|
||||||
|
Errmsg string `json:"errmsg"`
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetToken use code get access_token (*operation of getting code ought to be done in front)
|
||||||
|
// get more detail via: https://developer.work.weixin.qq.com/document/path/91039
|
||||||
|
func (idp *WeComInternalIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||||
|
pTokenParams := &struct {
|
||||||
|
CorpId string `json:"corpid"`
|
||||||
|
Corpsecret string `json:"corpsecret"`
|
||||||
|
}{idp.Config.ClientID, idp.Config.ClientSecret}
|
||||||
|
resp, err := idp.Client.Get(fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s", pTokenParams.CorpId, pTokenParams.Corpsecret))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pToken := &WecomInterToken{}
|
||||||
|
err = json.Unmarshal(data, pToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if pToken.Errcode != 0 {
|
||||||
|
return nil, fmt.Errorf("pToken.Errcode = %d, pToken.Errmsg = %s", pToken.Errcode, pToken.Errmsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
token := &oauth2.Token{
|
||||||
|
AccessToken: pToken.AccessToken,
|
||||||
|
Expiry: time.Unix(time.Now().Unix()+int64(pToken.ExpiresIn), 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
raw := make(map[string]interface{})
|
||||||
|
raw["code"] = code
|
||||||
|
token = token.WithExtra(raw)
|
||||||
|
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type WecomInternalUserResp struct {
|
||||||
|
Errcode int `json:"errcode"`
|
||||||
|
Errmsg string `json:"errmsg"`
|
||||||
|
UserId string `json:"UserId"`
|
||||||
|
OpenId string `json:"OpenId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WecomInternalUserInfo struct {
|
||||||
|
Errcode int `json:"errcode"`
|
||||||
|
Errmsg string `json:"errmsg"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Avatar string `json:"avatar"`
|
||||||
|
OpenId string `json:"open_userid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *WeComInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||||
|
//Get userid first
|
||||||
|
accessToken := token.AccessToken
|
||||||
|
code := token.Extra("code").(string)
|
||||||
|
resp, err := idp.Client.Get(fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=%s&code=%s", accessToken, code))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
userResp := &WecomInternalUserResp{}
|
||||||
|
err = json.Unmarshal(data, userResp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if userResp.Errcode != 0 {
|
||||||
|
return nil, fmt.Errorf("userIdResp.Errcode = %d, userIdResp.Errmsg = %s", userResp.Errcode, userResp.Errmsg)
|
||||||
|
}
|
||||||
|
if userResp.OpenId != "" {
|
||||||
|
return nil, fmt.Errorf("not an internal user")
|
||||||
|
}
|
||||||
|
//Use userid and accesstoken to get user information
|
||||||
|
resp, err = idp.Client.Get(fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=%s&userid=%s", accessToken, userResp.UserId))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err = io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
infoResp := &WecomInternalUserInfo{}
|
||||||
|
err = json.Unmarshal(data, infoResp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if infoResp.Errcode != 0 {
|
||||||
|
return nil, fmt.Errorf("userInfoResp.errcode = %d, userInfoResp.errmsg = %s", infoResp.Errcode, infoResp.Errmsg)
|
||||||
|
}
|
||||||
|
userInfo := UserInfo{
|
||||||
|
Id: infoResp.OpenId,
|
||||||
|
Username: infoResp.Name,
|
||||||
|
DisplayName: infoResp.Name,
|
||||||
|
Email: infoResp.Email,
|
||||||
|
AvatarUrl: infoResp.Avatar,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &userInfo, nil
|
||||||
|
}
|
56
k8s.yaml
Normal file
56
k8s.yaml
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# this is only an EXAMPLE of deploying casddor in kubernetes
|
||||||
|
# please modify this file according to your requirements
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
#EDIT IT: if you don't want to run casdoor in default namespace, please modify this field
|
||||||
|
#namespace: casdoor
|
||||||
|
name: casdoor-svc
|
||||||
|
labels:
|
||||||
|
app: casdoor
|
||||||
|
spec:
|
||||||
|
#EDIT IT: if you don't want to run casdoor in default namespace, please modify this filed
|
||||||
|
type: NodePort
|
||||||
|
ports:
|
||||||
|
- port: 8000
|
||||||
|
selector:
|
||||||
|
app: casdoor
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
#EDIT IT: if you don't want to run casdoor in default namespace, please modify this field
|
||||||
|
#namespace: casdoor
|
||||||
|
name: casdoor-deployment
|
||||||
|
labels:
|
||||||
|
app: casdoor
|
||||||
|
spec:
|
||||||
|
#EDIT IT: if you don't use redis, casdoor should not have multiple replicas
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: casdoor
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: casdoor
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: casdoor-container
|
||||||
|
image: casbin/casdoor:latest
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- containerPort: 8000
|
||||||
|
volumeMounts:
|
||||||
|
# the mounted directory path in THE CONTAINER
|
||||||
|
- mountPath: /conf
|
||||||
|
name: conf
|
||||||
|
env:
|
||||||
|
- name: RUNNING_IN_DOCKER
|
||||||
|
value: "true"
|
||||||
|
#if you want to deploy this in real prod env, consider the config map
|
||||||
|
volumes:
|
||||||
|
- name: conf
|
||||||
|
hostPath:
|
||||||
|
#EDIT IT: the mounted directory path in THE HOST
|
||||||
|
path: /conf
|
17
main.go
17
main.go
@ -15,20 +15,23 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/astaxie/beego"
|
||||||
"github.com/astaxie/beego/logs"
|
"github.com/astaxie/beego/logs"
|
||||||
"github.com/astaxie/beego/plugins/cors"
|
"github.com/astaxie/beego/plugins/cors"
|
||||||
_ "github.com/astaxie/beego/session/redis"
|
_ "github.com/astaxie/beego/session/redis"
|
||||||
"github.com/casbin/casdoor/authz"
|
"github.com/casdoor/casdoor/authz"
|
||||||
"github.com/casbin/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casbin/casdoor/proxy"
|
"github.com/casdoor/casdoor/proxy"
|
||||||
"github.com/casbin/casdoor/routers"
|
"github.com/casdoor/casdoor/routers"
|
||||||
|
_ "github.com/casdoor/casdoor/routers"
|
||||||
_ "github.com/casbin/casdoor/routers"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
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.InitDb()
|
||||||
object.InitDefaultStorageProvider()
|
object.InitDefaultStorageProvider()
|
||||||
object.InitLdapAutoSynchronizer()
|
object.InitLdapAutoSynchronizer()
|
||||||
|
@ -17,10 +17,11 @@ package object
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"xorm.io/core"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/astaxie/beego"
|
||||||
"github.com/casbin/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
//_ "github.com/denisenkom/go-mssqldb" // db = mssql
|
//_ "github.com/denisenkom/go-mssqldb" // db = mssql
|
||||||
_ "github.com/go-sql-driver/mysql" // db = mysql
|
_ "github.com/go-sql-driver/mysql" // db = mysql
|
||||||
//_ "github.com/lib/pq" // db = postgres
|
//_ "github.com/lib/pq" // db = postgres
|
||||||
@ -35,11 +36,15 @@ func InitConfig() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
InitAdapter()
|
InitAdapter(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitAdapter() {
|
func InitAdapter(createDatabase bool) {
|
||||||
|
|
||||||
adapter = NewAdapter(beego.AppConfig.String("driverName"), conf.GetBeegoConfDataSourceName(), beego.AppConfig.String("dbName"))
|
adapter = NewAdapter(beego.AppConfig.String("driverName"), conf.GetBeegoConfDataSourceName(), beego.AppConfig.String("dbName"))
|
||||||
|
if createDatabase {
|
||||||
|
adapter.CreateDatabase()
|
||||||
|
}
|
||||||
adapter.createTable()
|
adapter.createTable()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,6 +80,17 @@ func NewAdapter(driverName string, dataSourceName string, dbName string) *Adapte
|
|||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) CreateDatabase() error {
|
||||||
|
engine, err := xorm.NewEngine(a.driverName, a.dataSourceName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer engine.Close()
|
||||||
|
|
||||||
|
_, err = engine.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s default charset utf8 COLLATE utf8_general_ci", a.dbName))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (a *Adapter) open() {
|
func (a *Adapter) open() {
|
||||||
dataSourceName := a.dataSourceName + a.dbName
|
dataSourceName := a.dataSourceName + a.dbName
|
||||||
if a.driverName != "mysql" {
|
if a.driverName != "mysql" {
|
||||||
@ -95,6 +111,13 @@ func (a *Adapter) close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Adapter) createTable() {
|
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))
|
err := a.Engine.Sync2(new(Organization))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -105,6 +128,16 @@ func (a *Adapter) createTable() {
|
|||||||
panic(err)
|
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))
|
err = a.Engine.Sync2(new(Provider))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -145,6 +178,11 @@ func (a *Adapter) createTable() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = a.Engine.Sync2(new(Cert))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
err = a.Engine.Sync2(new(Ldap))
|
err = a.Engine.Sync2(new(Ldap))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -152,13 +190,18 @@ func (a *Adapter) createTable() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetSession(owner string, offset, limit int, field, value, sortField, sortOrder string) *xorm.Session {
|
func GetSession(owner string, offset, limit int, field, value, sortField, sortOrder string) *xorm.Session {
|
||||||
session := adapter.Engine.Limit(limit, offset).Where("1=1")
|
session := adapter.Engine.Prepare()
|
||||||
|
if offset != -1 && limit != -1 {
|
||||||
|
session.Limit(limit, offset)
|
||||||
|
}
|
||||||
if owner != "" {
|
if owner != "" {
|
||||||
session = session.And("owner=?", owner)
|
session = session.And("owner=?", owner)
|
||||||
}
|
}
|
||||||
if field != "" && value != "" {
|
if field != "" && value != "" {
|
||||||
|
if filterField(field) {
|
||||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if sortField == "" || sortOrder == "" {
|
if sortField == "" || sortOrder == "" {
|
||||||
sortField = "created_time"
|
sortField = "created_time"
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ package object
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -31,6 +31,7 @@ type Application struct {
|
|||||||
HomepageUrl string `xorm:"varchar(100)" json:"homepageUrl"`
|
HomepageUrl string `xorm:"varchar(100)" json:"homepageUrl"`
|
||||||
Description string `xorm:"varchar(100)" json:"description"`
|
Description string `xorm:"varchar(100)" json:"description"`
|
||||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||||
|
Cert string `xorm:"varchar(100)" json:"cert"`
|
||||||
EnablePassword bool `json:"enablePassword"`
|
EnablePassword bool `json:"enablePassword"`
|
||||||
EnableSignUp bool `json:"enableSignUp"`
|
EnableSignUp bool `json:"enableSignUp"`
|
||||||
EnableSigninSession bool `json:"enableSigninSession"`
|
EnableSigninSession bool `json:"enableSigninSession"`
|
||||||
@ -55,10 +56,7 @@ type Application struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetApplicationCount(owner, field, value string) int {
|
func GetApplicationCount(owner, field, value string) int {
|
||||||
session := adapter.Engine.Where("owner=?", owner)
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
if field != "" && value != "" {
|
|
||||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
|
||||||
}
|
|
||||||
count, err := session.Count(&Application{})
|
count, err := session.Count(&Application{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -88,7 +86,7 @@ func GetPaginationApplications(owner string, offset, limit int, field, value, so
|
|||||||
return applications
|
return applications
|
||||||
}
|
}
|
||||||
|
|
||||||
func getApplicationsByOrganizationName(owner string, organization string) []*Application {
|
func GetApplicationsByOrganizationName(owner string, organization string) []*Application {
|
||||||
applications := []*Application{}
|
applications := []*Application{}
|
||||||
err := adapter.Engine.Desc("created_time").Find(&applications, &Application{Owner: owner, Organization: organization})
|
err := adapter.Engine.Desc("created_time").Find(&applications, &Application{Owner: owner, Organization: organization})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -200,24 +198,37 @@ 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 {
|
func GetApplication(id string) *Application {
|
||||||
owner, name := util.GetOwnerAndNameFromId(id)
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
return getApplication(owner, name)
|
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 {
|
func UpdateApplication(id string, application *Application) bool {
|
||||||
owner, name := util.GetOwnerAndNameFromId(id)
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
if getApplication(owner, name) == nil {
|
if getApplication(owner, name) == nil {
|
||||||
|
@ -20,7 +20,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/astaxie/beego"
|
||||||
"github.com/casbin/casdoor/proxy"
|
"github.com/casdoor/casdoor/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultStorageProvider *Provider = nil
|
var defaultStorageProvider *Provider = nil
|
||||||
|
@ -18,7 +18,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/casbin/casdoor/proxy"
|
"github.com/casdoor/casdoor/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSyncPermanentAvatars(t *testing.T) {
|
func TestSyncPermanentAvatars(t *testing.T) {
|
||||||
|
161
object/cert.go
Normal file
161
object/cert.go
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
// 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 := GetSession(owner, -1, -1, field, 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,15 +18,19 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"github.com/casbin/casdoor/cred"
|
"github.com/casdoor/casdoor/cred"
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
goldap "github.com/go-ldap/ldap/v3"
|
goldap "github.com/go-ldap/ldap/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var reWhiteSpace *regexp.Regexp
|
var (
|
||||||
|
reWhiteSpace *regexp.Regexp
|
||||||
|
reFieldWhiteList *regexp.Regexp
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
reWhiteSpace, _ = regexp.Compile(`\s`)
|
reWhiteSpace, _ = regexp.Compile(`\s`)
|
||||||
|
reFieldWhiteList, _ = regexp.Compile(`^[A-Za-z0-9]+$`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckUserSignup(application *Application, organization *Organization, username string, password string, displayName string, email string, phone string, affiliation string) string {
|
func CheckUserSignup(application *Application, organization *Organization, username string, password string, displayName string, email string, phone string, affiliation string) string {
|
||||||
@ -179,3 +183,7 @@ func CheckUserPassword(organization string, username string, password string) (*
|
|||||||
|
|
||||||
return user, ""
|
return user, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func filterField(field string) bool {
|
||||||
|
return reFieldWhiteList.MatchString(field)
|
||||||
|
}
|
@ -14,12 +14,23 @@
|
|||||||
|
|
||||||
package object
|
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() {
|
func InitDb() {
|
||||||
initBuiltInOrganization()
|
initBuiltInOrganization()
|
||||||
initBuiltInUser()
|
initBuiltInUser()
|
||||||
initBuiltInApplication()
|
initBuiltInApplication()
|
||||||
|
initBuiltInCert()
|
||||||
initBuiltInLdap()
|
initBuiltInLdap()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,6 +101,7 @@ func initBuiltInApplication() {
|
|||||||
Logo: "https://cdn.casbin.com/logo/logo_1024x256.png",
|
Logo: "https://cdn.casbin.com/logo/logo_1024x256.png",
|
||||||
HomepageUrl: "https://casdoor.org",
|
HomepageUrl: "https://casdoor.org",
|
||||||
Organization: "built-in",
|
Organization: "built-in",
|
||||||
|
Cert: "cert-built-in",
|
||||||
EnablePassword: true,
|
EnablePassword: true,
|
||||||
EnableSignUp: true,
|
EnableSignUp: true,
|
||||||
Providers: []*ProviderItem{},
|
Providers: []*ProviderItem{},
|
||||||
@ -109,6 +121,28 @@ func initBuiltInApplication() {
|
|||||||
AddApplication(application)
|
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() {
|
func initBuiltInLdap() {
|
||||||
ldap := GetLdap("ldap-built-in")
|
ldap := GetLdap("ldap-built-in")
|
||||||
if ldap != nil {
|
if ldap != nil {
|
||||||
|
@ -19,7 +19,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
goldap "github.com/go-ldap/ldap/v3"
|
goldap "github.com/go-ldap/ldap/v3"
|
||||||
"github.com/thanhpk/randstr"
|
"github.com/thanhpk/randstr"
|
||||||
)
|
)
|
||||||
|
@ -54,7 +54,7 @@ func init() {
|
|||||||
Issuer: origin,
|
Issuer: origin,
|
||||||
AuthorizationEndpoint: fmt.Sprintf("%s/login/oauth/authorize", origin),
|
AuthorizationEndpoint: fmt.Sprintf("%s/login/oauth/authorize", origin),
|
||||||
TokenEndpoint: fmt.Sprintf("%s/api/login/oauth/access_token", origin),
|
TokenEndpoint: fmt.Sprintf("%s/api/login/oauth/access_token", origin),
|
||||||
UserinfoEndpoint: fmt.Sprintf("%s/api/get-account", origin),
|
UserinfoEndpoint: fmt.Sprintf("%s/api/userinfo", origin),
|
||||||
JwksUri: fmt.Sprintf("%s/api/certs", origin),
|
JwksUri: fmt.Sprintf("%s/api/certs", origin),
|
||||||
ResponseTypesSupported: []string{"id_token"},
|
ResponseTypesSupported: []string{"id_token"},
|
||||||
ResponseModesSupported: []string{"login", "code", "link"},
|
ResponseModesSupported: []string{"login", "code", "link"},
|
||||||
@ -72,17 +72,20 @@ func GetOidcDiscovery() OidcDiscovery {
|
|||||||
return oidcDiscovery
|
return oidcDiscovery
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetJSONWebKeySet() (jose.JSONWebKeySet, error) {
|
func GetJsonWebKeySet() (jose.JSONWebKeySet, error) {
|
||||||
|
cert := GetDefaultCert()
|
||||||
|
|
||||||
//follows the protocol rfc 7517(draft)
|
//follows the protocol rfc 7517(draft)
|
||||||
//link here: https://self-issued.info/docs/draft-ietf-jose-json-web-key.html
|
//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
|
//or https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key
|
||||||
certPEMBlock := []byte(tokenJwtPublicKey)
|
certPemBlock := []byte(cert.PublicKey)
|
||||||
certDERBlock, _ := pem.Decode(certPEMBlock)
|
certDerBlock, _ := pem.Decode(certPemBlock)
|
||||||
x509Cert, _ := x509.ParseCertificate(certDERBlock.Bytes)
|
x509Cert, _ := x509.ParseCertificate(certDerBlock.Bytes)
|
||||||
|
|
||||||
var jwk jose.JSONWebKey
|
var jwk jose.JSONWebKey
|
||||||
jwk.Key = x509Cert.PublicKey
|
jwk.Key = x509Cert.PublicKey
|
||||||
jwk.Certificates = []*x509.Certificate{x509Cert}
|
jwk.Certificates = []*x509.Certificate{x509Cert}
|
||||||
|
jwk.KeyID = cert.Name
|
||||||
|
|
||||||
var jwks jose.JSONWebKeySet
|
var jwks jose.JSONWebKeySet
|
||||||
jwks.Keys = []jose.JSONWebKey{jwk}
|
jwks.Keys = []jose.JSONWebKey{jwk}
|
||||||
|
@ -15,10 +15,8 @@
|
|||||||
package object
|
package object
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"github.com/casdoor/casdoor/cred"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
"github.com/casbin/casdoor/cred"
|
|
||||||
"github.com/casbin/casdoor/util"
|
|
||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -39,10 +37,7 @@ type Organization struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetOrganizationCount(owner, field, value string) int {
|
func GetOrganizationCount(owner, field, value string) int {
|
||||||
session := adapter.Engine.Where("owner=?", owner)
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
if field != "" && value != "" {
|
|
||||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
|
||||||
}
|
|
||||||
count, err := session.Count(&Organization{})
|
count, err := session.Count(&Organization{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -124,7 +119,7 @@ func UpdateOrganization(id string, organization *Organization) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if name != organization.Name {
|
if name != organization.Name {
|
||||||
applications := getApplicationsByOrganizationName("admin", name)
|
applications := GetApplicationsByOrganizationName("admin", name)
|
||||||
for _, application := range applications {
|
for _, application := range applications {
|
||||||
application.Organization = organization.Name
|
application.Organization = organization.Name
|
||||||
UpdateApplication(application.GetId(), application)
|
UpdateApplication(application.GetId(), application)
|
||||||
|
129
object/permission.go
Normal file
129
object/permission.go
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
// 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 := GetSession(owner, -1, -1, field, 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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -29,6 +29,7 @@ type Provider struct {
|
|||||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||||
Category string `xorm:"varchar(100)" json:"category"`
|
Category string `xorm:"varchar(100)" json:"category"`
|
||||||
Type string `xorm:"varchar(100)" json:"type"`
|
Type string `xorm:"varchar(100)" json:"type"`
|
||||||
|
SubType string `xorm:"varchar(100)" json:"subType"`
|
||||||
Method string `xorm:"varchar(100)" json:"method"`
|
Method string `xorm:"varchar(100)" json:"method"`
|
||||||
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
||||||
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
||||||
@ -81,10 +82,7 @@ func GetMaskedProviders(providers []*Provider) []*Provider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetProviderCount(owner, field, value string) int {
|
func GetProviderCount(owner, field, value string) int {
|
||||||
session := adapter.Engine.Where("owner=?", owner)
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
if field != "" && value != "" {
|
|
||||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
|
||||||
}
|
|
||||||
count, err := session.Count(&Provider{})
|
count, err := session.Count(&Provider{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -20,7 +20,7 @@ import (
|
|||||||
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/astaxie/beego"
|
||||||
"github.com/astaxie/beego/context"
|
"github.com/astaxie/beego/context"
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var logPostOnly bool
|
var logPostOnly bool
|
||||||
@ -47,6 +47,8 @@ type Record struct {
|
|||||||
RequestUri string `xorm:"varchar(1000)" json:"requestUri"`
|
RequestUri string `xorm:"varchar(1000)" json:"requestUri"`
|
||||||
Action string `xorm:"varchar(1000)" json:"action"`
|
Action string `xorm:"varchar(1000)" json:"action"`
|
||||||
|
|
||||||
|
ExtendedUser *User `xorm:"-" json:"extendedUser"`
|
||||||
|
|
||||||
IsTriggered bool `json:"isTriggered"`
|
IsTriggered bool `json:"isTriggered"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,10 +102,7 @@ func AddRecord(record *Record) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetRecordCount(field, value string) int {
|
func GetRecordCount(field, value string) int {
|
||||||
session := adapter.Engine.Where("1=1")
|
session := GetSession("", -1, -1, field, value, "", "")
|
||||||
if field != "" && value != "" {
|
|
||||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
|
||||||
}
|
|
||||||
count, err := session.Count(&Record{})
|
count, err := session.Count(&Record{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -159,6 +158,11 @@ func SendWebhooks(record *Record) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if matched {
|
if matched {
|
||||||
|
if webhook.IsUserExtended {
|
||||||
|
user := getUser(record.Organization, record.User)
|
||||||
|
record.ExtendedUser = user
|
||||||
|
}
|
||||||
|
|
||||||
err := sendWebhook(webhook, record)
|
err := sendWebhook(webhook, record)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -17,7 +17,7 @@ package object
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -40,11 +40,8 @@ type Resource struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetResourceCount(owner, user, field, value string) int {
|
func GetResourceCount(owner, user, field, value string) int {
|
||||||
session := adapter.Engine.Where("owner=? and user=?", owner, user)
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
if field != "" && value != "" {
|
count, err := session.Count(&Resource{User: user})
|
||||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
|
||||||
}
|
|
||||||
count, err := session.Count(&Resource{})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
123
object/role.go
Normal file
123
object/role.go
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
// 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 := GetSession(owner, -1, -1, field, 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"
|
"strings"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/astaxie/beego"
|
||||||
"github.com/casbin/casdoor/storage"
|
"github.com/casdoor/casdoor/storage"
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var isCloudIntranet bool
|
var isCloudIntranet bool
|
||||||
|
@ -17,7 +17,7 @@ package object
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -48,6 +48,7 @@ type Syncer struct {
|
|||||||
TableColumns []*TableColumn `xorm:"mediumtext" json:"tableColumns"`
|
TableColumns []*TableColumn `xorm:"mediumtext" json:"tableColumns"`
|
||||||
AffiliationTable string `xorm:"varchar(100)" json:"affiliationTable"`
|
AffiliationTable string `xorm:"varchar(100)" json:"affiliationTable"`
|
||||||
AvatarBaseUrl string `xorm:"varchar(100)" json:"avatarBaseUrl"`
|
AvatarBaseUrl string `xorm:"varchar(100)" json:"avatarBaseUrl"`
|
||||||
|
ErrorText string `xorm:"mediumtext" json:"errorText"`
|
||||||
SyncInterval int `json:"syncInterval"`
|
SyncInterval int `json:"syncInterval"`
|
||||||
IsEnabled bool `json:"isEnabled"`
|
IsEnabled bool `json:"isEnabled"`
|
||||||
|
|
||||||
@ -55,10 +56,7 @@ type Syncer struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetSyncerCount(owner, field, value string) int {
|
func GetSyncerCount(owner, field, value string) int {
|
||||||
session := adapter.Engine.Where("owner=?", owner)
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
if field != "" && value != "" {
|
|
||||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
|
||||||
}
|
|
||||||
count, err := session.Count(&Syncer{})
|
count, err := session.Count(&Syncer{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -140,6 +138,26 @@ func UpdateSyncer(id string, syncer *Syncer) bool {
|
|||||||
panic(err)
|
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
|
return affected != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,6 +167,10 @@ func AddSyncer(syncer *Syncer) bool {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if affected == 1 {
|
||||||
|
addSyncerJob(syncer)
|
||||||
|
}
|
||||||
|
|
||||||
return affected != 0
|
return affected != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,6 +180,10 @@ func DeleteSyncer(syncer *Syncer) bool {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if affected == 1 {
|
||||||
|
deleteSyncerJob(syncer)
|
||||||
|
}
|
||||||
|
|
||||||
return affected != 0
|
return affected != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,27 +14,56 @@
|
|||||||
|
|
||||||
package object
|
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() {
|
func init() {
|
||||||
cronMap = map[string]*crontab.Crontab{}
|
cronMap = map[string]*cron.Cron{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCrontab(name string) *crontab.Crontab {
|
func getCronMap(name string) *cron.Cron {
|
||||||
ctab, ok := cronMap[name]
|
m, ok := cronMap[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
ctab = crontab.New()
|
m = cron.New()
|
||||||
cronMap[name] = ctab
|
cronMap[name] = m
|
||||||
}
|
}
|
||||||
return ctab
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
func clearCrontab(name string) {
|
func clearCron(name string) {
|
||||||
ctab, ok := cronMap[name]
|
cron, ok := cronMap[name]
|
||||||
if ok {
|
if ok {
|
||||||
ctab.Clear()
|
cron.Stop()
|
||||||
delete(cronMap, name)
|
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)
|
||||||
|
}
|
||||||
|
@ -14,13 +14,25 @@
|
|||||||
|
|
||||||
package object
|
package object
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
func (syncer *Syncer) syncUsers() {
|
func (syncer *Syncer) syncUsers() {
|
||||||
fmt.Printf("Running syncUsers()..\n")
|
fmt.Printf("Running syncUsers()..\n")
|
||||||
|
|
||||||
users, userMap := syncer.getUserMap()
|
users, userMap := syncer.getUserMap()
|
||||||
oUsers, oUserMap := syncer.getOriginalUserMap()
|
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))
|
fmt.Printf("Users: %d, oUsers: %d\n", len(users), len(oUsers))
|
||||||
|
|
||||||
var affiliationMap map[int]string
|
var affiliationMap map[int]string
|
||||||
|
@ -19,48 +19,51 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
type OriginalUser = User
|
type OriginalUser = User
|
||||||
|
|
||||||
func (syncer *Syncer) getOriginalUsers() []*OriginalUser {
|
func (syncer *Syncer) getOriginalUsers() ([]*OriginalUser, error) {
|
||||||
sql := fmt.Sprintf("select * from %s", syncer.getTable())
|
sql := fmt.Sprintf("select * from %s", syncer.getTable())
|
||||||
results, err := syncer.Adapter.Engine.QueryString(sql)
|
results, err := syncer.Adapter.Engine.QueryString(sql)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return syncer.getOriginalUsersFromMap(results)
|
return syncer.getOriginalUsersFromMap(results), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (syncer *Syncer) getOriginalUserMap() ([]*OriginalUser, map[string]*OriginalUser) {
|
func (syncer *Syncer) getOriginalUserMap() ([]*OriginalUser, map[string]*OriginalUser, error) {
|
||||||
users := syncer.getOriginalUsers()
|
users, err := syncer.getOriginalUsers()
|
||||||
|
if err != nil {
|
||||||
|
return users, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
m := map[string]*OriginalUser{}
|
m := map[string]*OriginalUser{}
|
||||||
for _, user := range users {
|
for _, user := range users {
|
||||||
m[user.Id] = user
|
m[user.Id] = user
|
||||||
}
|
}
|
||||||
return users, m
|
return users, m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (syncer *Syncer) addUser(user *OriginalUser) bool {
|
func (syncer *Syncer) addUser(user *OriginalUser) (bool, error) {
|
||||||
m := syncer.getMapFromOriginalUser(user)
|
m := syncer.getMapFromOriginalUser(user)
|
||||||
keyString, valueString := syncer.getSqlKeyValueStringFromMap(m)
|
keyString, valueString := syncer.getSqlKeyValueStringFromMap(m)
|
||||||
|
|
||||||
sql := fmt.Sprintf("insert into %s (%s) values (%s)", syncer.getTable(), keyString, valueString)
|
sql := fmt.Sprintf("insert into %s (%s) values (%s)", syncer.getTable(), keyString, valueString)
|
||||||
res, err := syncer.Adapter.Engine.Exec(sql)
|
res, err := syncer.Adapter.Engine.Exec(sql)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
affected, err := res.RowsAffected()
|
affected, err := res.RowsAffected()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return affected != 0
|
return affected != 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/*func (syncer *Syncer) getOriginalColumns() []string {
|
/*func (syncer *Syncer) getOriginalColumns() []string {
|
||||||
@ -84,7 +87,7 @@ func (syncer *Syncer) getCasdoorColumns() []string {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func (syncer *Syncer) updateUser(user *OriginalUser) bool {
|
func (syncer *Syncer) updateUser(user *OriginalUser) (bool, error) {
|
||||||
m := syncer.getMapFromOriginalUser(user)
|
m := syncer.getMapFromOriginalUser(user)
|
||||||
pkValue := m[syncer.TablePrimaryKey]
|
pkValue := m[syncer.TablePrimaryKey]
|
||||||
delete(m, syncer.TablePrimaryKey)
|
delete(m, syncer.TablePrimaryKey)
|
||||||
@ -93,22 +96,22 @@ func (syncer *Syncer) updateUser(user *OriginalUser) bool {
|
|||||||
sql := fmt.Sprintf("update %s set %s where %s = %s", syncer.getTable(), setString, syncer.TablePrimaryKey, pkValue)
|
sql := fmt.Sprintf("update %s set %s where %s = %s", syncer.getTable(), setString, syncer.TablePrimaryKey, pkValue)
|
||||||
res, err := syncer.Adapter.Engine.Exec(sql)
|
res, err := syncer.Adapter.Engine.Exec(sql)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
affected, err := res.RowsAffected()
|
affected, err := res.RowsAffected()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return affected != 0
|
return affected != 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (syncer *Syncer) updateUserForOriginalFields(user *User) bool {
|
func (syncer *Syncer) updateUserForOriginalFields(user *User) (bool, error) {
|
||||||
owner, name := util.GetOwnerAndNameFromId(user.GetId())
|
owner, name := util.GetOwnerAndNameFromId(user.GetId())
|
||||||
oldUser := getUserById(owner, name)
|
oldUser := getUserById(owner, name)
|
||||||
if oldUser == nil {
|
if oldUser == nil {
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.Avatar != oldUser.Avatar && user.Avatar != "" {
|
if user.Avatar != oldUser.Avatar && user.Avatar != "" {
|
||||||
@ -119,10 +122,10 @@ func (syncer *Syncer) updateUserForOriginalFields(user *User) bool {
|
|||||||
columns = append(columns, "affiliation", "hash", "pre_hash")
|
columns = append(columns, "affiliation", "hash", "pre_hash")
|
||||||
affected, err := adapter.Engine.ID(core.PK{oldUser.Owner, oldUser.Name}).Cols(columns...).Update(user)
|
affected, err := adapter.Engine.ID(core.PK{oldUser.Owner, oldUser.Name}).Cols(columns...).Update(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return affected != 0
|
return affected != 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (syncer *Syncer) calculateHash(user *OriginalUser) string {
|
func (syncer *Syncer) calculateHash(user *OriginalUser) string {
|
||||||
@ -158,22 +161,7 @@ func (syncer *Syncer) initAdapter() {
|
|||||||
func RunSyncUsersJob() {
|
func RunSyncUsersJob() {
|
||||||
syncers := GetSyncers("admin")
|
syncers := GetSyncers("admin")
|
||||||
for _, syncer := range syncers {
|
for _, syncer := range syncers {
|
||||||
if !syncer.IsEnabled {
|
addSyncerJob(syncer)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(time.Duration(1<<63 - 1))
|
time.Sleep(time.Duration(1<<63 - 1))
|
||||||
|
@ -19,7 +19,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (syncer *Syncer) getFullAvatarUrl(avatar string) string {
|
func (syncer *Syncer) getFullAvatarUrl(avatar string) string {
|
||||||
|
169
object/token.go
169
object/token.go
@ -15,11 +15,13 @@
|
|||||||
package object
|
package object
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -43,21 +45,22 @@ type Token struct {
|
|||||||
ExpiresIn int `json:"expiresIn"`
|
ExpiresIn int `json:"expiresIn"`
|
||||||
Scope string `xorm:"varchar(100)" json:"scope"`
|
Scope string `xorm:"varchar(100)" json:"scope"`
|
||||||
TokenType string `xorm:"varchar(100)" json:"tokenType"`
|
TokenType string `xorm:"varchar(100)" json:"tokenType"`
|
||||||
|
CodeChallenge string `xorm:"varchar(100)" json:"codeChallenge"`
|
||||||
|
CodeIsUsed bool `json:"codeIsUsed"`
|
||||||
|
CodeExpireIn int64 `json:"codeExpireIn"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TokenWrapper struct {
|
type TokenWrapper struct {
|
||||||
AccessToken string `json:"access_token"`
|
AccessToken string `json:"access_token"`
|
||||||
IdToken string `json:"id_token"`
|
IdToken string `json:"id_token"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
TokenType string `json:"token_type"`
|
TokenType string `json:"token_type"`
|
||||||
ExpiresIn int `json:"expires_in"`
|
ExpiresIn int `json:"expires_in"`
|
||||||
Scope string `json:"scope"`
|
Scope string `json:"scope"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTokenCount(owner, field, value string) int {
|
func GetTokenCount(owner, field, value string) int {
|
||||||
session := adapter.Engine.Where("owner=?", owner)
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
if field != "" && value != "" {
|
|
||||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
|
||||||
}
|
|
||||||
count, err := session.Count(&Token{})
|
count, err := session.Count(&Token{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -106,8 +109,8 @@ func getToken(owner string, name string) *Token {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getTokenByCode(code string) *Token {
|
func getTokenByCode(code string) *Token {
|
||||||
token := Token{}
|
token := Token{Code: code}
|
||||||
existed, err := adapter.Engine.Where("code=?", code).Get(&token)
|
existed, err := adapter.Engine.Get(&token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -119,6 +122,15 @@ func getTokenByCode(code string) *Token {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateUsedByCode(token *Token) bool {
|
||||||
|
affected, err := adapter.Engine.Where("code=?", token.Code).Cols("code_is_used").Update(token)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
func GetToken(id string) *Token {
|
func GetToken(id string) *Token {
|
||||||
owner, name := util.GetOwnerAndNameFromId(id)
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
return getToken(owner, name)
|
return getToken(owner, name)
|
||||||
@ -156,6 +168,16 @@ func DeleteToken(token *Token) bool {
|
|||||||
return affected != 0
|
return affected != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetTokenByAccessToken(accessToken string) *Token {
|
||||||
|
//Check if the accessToken is in the database
|
||||||
|
token := Token{AccessToken: accessToken}
|
||||||
|
existed, err := adapter.Engine.Get(&token)
|
||||||
|
if err != nil || !existed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &token
|
||||||
|
}
|
||||||
|
|
||||||
func CheckOAuthLogin(clientId string, responseType string, redirectUri string, scope string, state string) (string, *Application) {
|
func CheckOAuthLogin(clientId string, responseType string, redirectUri string, scope string, state string) (string, *Application) {
|
||||||
if responseType != "code" {
|
if responseType != "code" {
|
||||||
return "response_type should be \"code\"", nil
|
return "response_type should be \"code\"", nil
|
||||||
@ -182,7 +204,7 @@ func CheckOAuthLogin(clientId string, responseType string, redirectUri string, s
|
|||||||
return "", application
|
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)
|
user := GetUser(userId)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
return &Code{
|
return &Code{
|
||||||
@ -190,6 +212,12 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
|
|||||||
Code: "",
|
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)
|
msg, application := CheckOAuthLogin(clientId, responseType, redirectUri, scope, state)
|
||||||
if msg != "" {
|
if msg != "" {
|
||||||
@ -199,11 +227,15 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
accessToken, refreshToken, err := generateJwtToken(application, user, nonce)
|
accessToken, refreshToken, err := generateJwtToken(application, user, nonce, scope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if challenge == "null" {
|
||||||
|
challenge = ""
|
||||||
|
}
|
||||||
|
|
||||||
token := &Token{
|
token := &Token{
|
||||||
Owner: application.Owner,
|
Owner: application.Owner,
|
||||||
Name: util.GenerateId(),
|
Name: util.GenerateId(),
|
||||||
@ -217,6 +249,9 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
|
|||||||
ExpiresIn: application.ExpireInHours * 60,
|
ExpiresIn: application.ExpireInHours * 60,
|
||||||
Scope: scope,
|
Scope: scope,
|
||||||
TokenType: "Bearer",
|
TokenType: "Bearer",
|
||||||
|
CodeChallenge: challenge,
|
||||||
|
CodeIsUsed: false,
|
||||||
|
CodeExpireIn: time.Now().Add(time.Minute * 5).Unix(),
|
||||||
}
|
}
|
||||||
AddToken(token)
|
AddToken(token)
|
||||||
|
|
||||||
@ -226,7 +261,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)
|
application := GetApplicationByClientId(clientId)
|
||||||
if application == nil {
|
if application == nil {
|
||||||
return &TokenWrapper{
|
return &TokenWrapper{
|
||||||
@ -282,10 +317,41 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
|
|||||||
Scope: "",
|
Scope: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if token.CodeChallenge != "" && pkceChallenge(verifier) != token.CodeChallenge {
|
||||||
|
return &TokenWrapper{
|
||||||
|
AccessToken: "error: incorrect code_verifier",
|
||||||
|
TokenType: "",
|
||||||
|
ExpiresIn: 0,
|
||||||
|
Scope: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if token.CodeIsUsed {
|
||||||
|
//Resist replay attacks, if the code is reused, the token generated with this code will be deleted
|
||||||
|
DeleteToken(token)
|
||||||
|
return &TokenWrapper{
|
||||||
|
AccessToken: "error: code has been used.",
|
||||||
|
TokenType: "",
|
||||||
|
ExpiresIn: 0,
|
||||||
|
Scope: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if time.Now().Unix() > token.CodeExpireIn {
|
||||||
|
//can only use the code to generate a token within five minutes
|
||||||
|
DeleteToken(token)
|
||||||
|
return &TokenWrapper{
|
||||||
|
AccessToken: "error: code has expired",
|
||||||
|
TokenType: "",
|
||||||
|
ExpiresIn: 0,
|
||||||
|
Scope: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
token.CodeIsUsed = true
|
||||||
|
updateUsedByCode(token)
|
||||||
tokenWrapper := &TokenWrapper{
|
tokenWrapper := &TokenWrapper{
|
||||||
AccessToken: token.AccessToken,
|
AccessToken: token.AccessToken,
|
||||||
IdToken: token.AccessToken,
|
IdToken: token.AccessToken,
|
||||||
|
RefreshToken: token.RefreshToken,
|
||||||
TokenType: token.TokenType,
|
TokenType: token.TokenType,
|
||||||
ExpiresIn: token.ExpiresIn,
|
ExpiresIn: token.ExpiresIn,
|
||||||
Scope: token.Scope,
|
Scope: token.Scope,
|
||||||
@ -294,52 +360,66 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
|
|||||||
return tokenWrapper
|
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
|
// check parameters
|
||||||
if grantType != "refresh_token" {
|
if grantType != "refresh_token" {
|
||||||
return &Code{
|
return &TokenWrapper{
|
||||||
Message: "error: grant_type should be \"refresh_token\"",
|
AccessToken: "error: grant_type should be \"refresh_token\"",
|
||||||
Code: "",
|
TokenType: "",
|
||||||
|
ExpiresIn: 0,
|
||||||
|
Scope: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
application := GetApplicationByClientId(clientId)
|
application := GetApplicationByClientId(clientId)
|
||||||
if application == nil {
|
if application == nil {
|
||||||
return &Code{
|
return &TokenWrapper{
|
||||||
Message: "error: invalid client_id",
|
AccessToken: "error: invalid client_id",
|
||||||
Code: "",
|
TokenType: "",
|
||||||
|
ExpiresIn: 0,
|
||||||
|
Scope: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if application.ClientSecret != clientSecret {
|
if application.ClientSecret != clientSecret {
|
||||||
return &Code{
|
return &TokenWrapper{
|
||||||
Message: "error: invalid client_secret",
|
AccessToken: "error: invalid client_secret",
|
||||||
Code: "",
|
TokenType: "",
|
||||||
|
ExpiresIn: 0,
|
||||||
|
Scope: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// check whether the refresh token is valid, and has not expired.
|
// check whether the refresh token is valid, and has not expired.
|
||||||
token := Token{RefreshToken: refreshToken}
|
token := Token{RefreshToken: refreshToken}
|
||||||
existed, err := adapter.Engine.Get(&token)
|
existed, err := adapter.Engine.Get(&token)
|
||||||
if err != nil || !existed {
|
if err != nil || !existed {
|
||||||
return &Code{
|
return &TokenWrapper{
|
||||||
Message: "error: invalid refresh_token",
|
AccessToken: "error: invalid refresh_token",
|
||||||
Code: "",
|
TokenType: "",
|
||||||
|
ExpiresIn: 0,
|
||||||
|
Scope: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
claims, err := ParseJwtToken(refreshToken)
|
|
||||||
|
cert := getCertByApplication(application)
|
||||||
|
_, err = ParseJwtToken(refreshToken, cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &Code{
|
return &TokenWrapper{
|
||||||
Message: "error: invalid refresh_token",
|
AccessToken: fmt.Sprintf("error: %s", err.Error()),
|
||||||
Code: "",
|
TokenType: "",
|
||||||
}
|
ExpiresIn: 0,
|
||||||
}
|
Scope: "",
|
||||||
if time.Now().Unix() > claims.ExpiresAt.Unix() {
|
|
||||||
return &Code{
|
|
||||||
Message: "error: expired refresh_token",
|
|
||||||
Code: "",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// generate a new token
|
// generate a new token
|
||||||
user := getUser(application.Owner, token.User)
|
user := getUser(application.Organization, token.User)
|
||||||
newAccessToken, newRefreshToken, err := generateJwtToken(application, 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, "", scope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -360,8 +440,21 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
|||||||
}
|
}
|
||||||
AddToken(newToken)
|
AddToken(newToken)
|
||||||
|
|
||||||
return &Code{
|
tokenWrapper := &TokenWrapper{
|
||||||
Message: "",
|
AccessToken: token.AccessToken,
|
||||||
Code: token.Code,
|
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,21 +23,45 @@ import (
|
|||||||
"github.com/golang-jwt/jwt/v4"
|
"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 {
|
type Claims struct {
|
||||||
*User
|
*User
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
Owner string `json:"owner,omitempty"`
|
|
||||||
Nonce string `json:"nonce,omitempty"`
|
Nonce string `json:"nonce,omitempty"`
|
||||||
|
Tag string `json:"tag,omitempty"`
|
||||||
|
Scope string `json:"scope,omitempty"`
|
||||||
jwt.RegisteredClaims
|
jwt.RegisteredClaims
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateJwtToken(application *Application, user *User, nonce string) (string, string, error) {
|
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"`
|
||||||
|
Scope string `json:"scope,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,
|
||||||
|
Scope: claims.Scope,
|
||||||
|
RegisteredClaims: claims.RegisteredClaims,
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateJwtToken(application *Application, user *User, nonce string, scope string) (string, string, error) {
|
||||||
nowTime := time.Now()
|
nowTime := time.Now()
|
||||||
expireTime := nowTime.Add(time.Duration(application.ExpireInHours) * time.Hour)
|
expireTime := nowTime.Add(time.Duration(application.ExpireInHours) * time.Hour)
|
||||||
refreshExpireTime := nowTime.Add(time.Duration(application.RefreshExpireInHours) * time.Hour)
|
refreshExpireTime := nowTime.Add(time.Duration(application.RefreshExpireInHours) * time.Hour)
|
||||||
@ -47,6 +71,9 @@ func generateJwtToken(application *Application, user *User, nonce string) (strin
|
|||||||
claims := Claims{
|
claims := Claims{
|
||||||
User: user,
|
User: user,
|
||||||
Nonce: nonce,
|
Nonce: nonce,
|
||||||
|
// FIXME: A workaround for custom claim by reusing `tag` in user info
|
||||||
|
Tag: user.Tag,
|
||||||
|
Scope: scope,
|
||||||
RegisteredClaims: jwt.RegisteredClaims{
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
Issuer: beego.AppConfig.String("origin"),
|
Issuer: beego.AppConfig.String("origin"),
|
||||||
Subject: user.Id,
|
Subject: user.Id,
|
||||||
@ -57,24 +84,32 @@ func generateJwtToken(application *Application, user *User, nonce string) (strin
|
|||||||
ID: "",
|
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" {
|
if application.TokenFormat == "JWT-Empty" {
|
||||||
claims.User = nil
|
claimsShort := getShortClaims(claims)
|
||||||
}
|
|
||||||
claims.Name = user.Name
|
|
||||||
claims.Owner = user.Owner
|
|
||||||
|
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodRS256, 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)
|
claims.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
|
||||||
refreshToken := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
|
refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
|
||||||
|
}
|
||||||
|
|
||||||
// Use "token_jwt_key.key" as RSA private key
|
cert := getCertByApplication(application)
|
||||||
privateKey := tokenJwtPrivateKey
|
|
||||||
key, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(privateKey))
|
// RSA private key
|
||||||
|
key, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(cert.PrivateKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
token.Header["kid"] = cert.Name
|
||||||
tokenString, err := token.SignedString(key)
|
tokenString, err := token.SignedString(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
@ -84,14 +119,14 @@ func generateJwtToken(application *Application, user *User, nonce string) (strin
|
|||||||
return tokenString, refreshTokenString, err
|
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) {
|
t, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||||
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
|
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
|
||||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use "token_jwt_key.pem" as RSA public key
|
// RSA public key
|
||||||
publicKey, err := jwt.ParseRSAPublicKeyFromPEM([]byte(tokenJwtPublicKey))
|
publicKey, err := jwt.ParseRSAPublicKeyFromPEM([]byte(cert.PublicKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -20,19 +20,14 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"fmt"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"time"
|
"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/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
|
// https://stackoverflow.com/questions/43822945/golang-can-i-create-x509keypair-using-rsa-key
|
||||||
|
|
||||||
bitSize := 4096
|
|
||||||
|
|
||||||
// Generate RSA key.
|
// Generate RSA key.
|
||||||
key, err := rsa.GenerateKey(rand.Reader, bitSize)
|
key, err := rsa.GenerateKey(rand.Reader, bitSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -50,12 +45,12 @@ func generateRsaKeys(fileId string) {
|
|||||||
tml := x509.Certificate{
|
tml := x509.Certificate{
|
||||||
// you can add any attr that you need
|
// you can add any attr that you need
|
||||||
NotBefore: time.Now(),
|
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
|
// you have to generate a different serial number each execution
|
||||||
SerialNumber: big.NewInt(123456),
|
SerialNumber: big.NewInt(123456),
|
||||||
Subject: pkix.Name{
|
Subject: pkix.Name{
|
||||||
CommonName: "Casdoor Cert",
|
CommonName: commonName,
|
||||||
Organization: []string{"Casdoor Organization"},
|
Organization: []string{organization},
|
||||||
},
|
},
|
||||||
BasicConstraintsValid: true,
|
BasicConstraintsValid: true,
|
||||||
}
|
}
|
||||||
@ -70,9 +65,5 @@ func generateRsaKeys(fileId string) {
|
|||||||
Bytes: cert,
|
Bytes: cert,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Write private key to file.
|
return string(certPem), string(privateKeyPem)
|
||||||
util.WriteBytesToPath(privateKeyPem, fmt.Sprintf("%s.key", fileId))
|
|
||||||
|
|
||||||
// Write certificate (aka public key) to file.
|
|
||||||
util.WriteBytesToPath(certPem, fmt.Sprintf("%s.pem", fileId))
|
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,20 @@
|
|||||||
|
|
||||||
package object
|
package object
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
func TestGenerateRsaKeys(t *testing.T) {
|
func TestGenerateRsaKeys(t *testing.T) {
|
||||||
fileId := "token_jwt_key"
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -27,21 +28,21 @@ type User struct {
|
|||||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||||
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
|
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"`
|
Type string `xorm:"varchar(100)" json:"type"`
|
||||||
Password string `xorm:"varchar(100)" json:"password"`
|
Password string `xorm:"varchar(100)" json:"password"`
|
||||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||||
Avatar string `xorm:"varchar(500)" json:"avatar"`
|
Avatar string `xorm:"varchar(500)" json:"avatar"`
|
||||||
PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"`
|
PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"`
|
||||||
Email string `xorm:"varchar(100)" json:"email"`
|
Email string `xorm:"varchar(100) index" json:"email"`
|
||||||
Phone string `xorm:"varchar(100)" json:"phone"`
|
Phone string `xorm:"varchar(100) index" json:"phone"`
|
||||||
Location string `xorm:"varchar(100)" json:"location"`
|
Location string `xorm:"varchar(100)" json:"location"`
|
||||||
Address []string `json:"address"`
|
Address []string `json:"address"`
|
||||||
Affiliation string `xorm:"varchar(100)" json:"affiliation"`
|
Affiliation string `xorm:"varchar(100)" json:"affiliation"`
|
||||||
Title string `xorm:"varchar(100)" json:"title"`
|
Title string `xorm:"varchar(100)" json:"title"`
|
||||||
IdCardType string `xorm:"varchar(100)" json:"idCardType"`
|
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"`
|
Homepage string `xorm:"varchar(100)" json:"homepage"`
|
||||||
Bio string `xorm:"varchar(100)" json:"bio"`
|
Bio string `xorm:"varchar(100)" json:"bio"`
|
||||||
Tag string `xorm:"varchar(100)" json:"tag"`
|
Tag string `xorm:"varchar(100)" json:"tag"`
|
||||||
@ -78,6 +79,7 @@ type User struct {
|
|||||||
Wecom string `xorm:"wecom varchar(100)" json:"wecom"`
|
Wecom string `xorm:"wecom varchar(100)" json:"wecom"`
|
||||||
Lark string `xorm:"lark varchar(100)" json:"lark"`
|
Lark string `xorm:"lark varchar(100)" json:"lark"`
|
||||||
Gitlab string `xorm:"gitlab varchar(100)" json:"gitlab"`
|
Gitlab string `xorm:"gitlab varchar(100)" json:"gitlab"`
|
||||||
|
Baidu string `xorm:"baidu varchar(100)" json:"baidu"`
|
||||||
Apple string `xorm:"apple varchar(100)" json:"apple"`
|
Apple string `xorm:"apple varchar(100)" json:"apple"`
|
||||||
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"`
|
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"`
|
||||||
Slack string `xorm:"slack varchar(100)" json:"slack"`
|
Slack string `xorm:"slack varchar(100)" json:"slack"`
|
||||||
@ -87,10 +89,7 @@ type User struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetGlobalUserCount(field, value string) int {
|
func GetGlobalUserCount(field, value string) int {
|
||||||
session := adapter.Engine.Where("1=1")
|
session := GetSession("", -1, -1, field, value, "", "")
|
||||||
if field != "" && value != "" {
|
|
||||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
|
||||||
}
|
|
||||||
count, err := session.Count(&User{})
|
count, err := session.Count(&User{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -121,10 +120,7 @@ func GetPaginationGlobalUsers(offset, limit int, field, value, sortField, sortOr
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetUserCount(owner, field, value string) int {
|
func GetUserCount(owner, field, value string) int {
|
||||||
session := adapter.Engine.Where("owner=?", owner)
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
if field != "" && value != "" {
|
|
||||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
|
||||||
}
|
|
||||||
count, err := session.Count(&User{})
|
count, err := session.Count(&User{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -269,7 +265,7 @@ func GetLastUser(owner string) *User {
|
|||||||
return nil
|
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)
|
owner, name := util.GetOwnerAndNameFromIdNoCheck(id)
|
||||||
oldUser := getUser(owner, name)
|
oldUser := getUser(owner, name)
|
||||||
if oldUser == nil {
|
if oldUser == nil {
|
||||||
@ -284,9 +280,12 @@ func UpdateUser(id string, user *User, columns []string) bool {
|
|||||||
|
|
||||||
if len(columns) == 0 {
|
if len(columns) == 0 {
|
||||||
columns = []string{"owner", "display_name", "avatar",
|
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"}
|
"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)
|
affected, err := adapter.Engine.ID(core.PK{owner, name}).Cols(columns...).Update(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -347,9 +346,10 @@ func AddUsers(users []*User) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
organization := GetOrganizationByUser(users[0])
|
//organization := GetOrganizationByUser(users[0])
|
||||||
for _, user := range users {
|
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.UpdateUserHash()
|
||||||
user.PreHash = user.Hash
|
user.PreHash = user.Hash
|
||||||
@ -407,3 +407,7 @@ func LinkUserAccount(user *User, field string, value string) bool {
|
|||||||
func (user *User) GetId() string {
|
func (user *User) GetId() string {
|
||||||
return fmt.Sprintf("%s/%s", user.Owner, user.Name)
|
return fmt.Sprintf("%s/%s", user.Owner, user.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isUserIdGlobalAdmin(userId string) bool {
|
||||||
|
return strings.HasPrefix(userId, "built-in/")
|
||||||
|
}
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
package object
|
package object
|
||||||
|
|
||||||
import "github.com/casbin/casdoor/cred"
|
import "github.com/casdoor/casdoor/cred"
|
||||||
|
|
||||||
func calculateHash(user *User) string {
|
func calculateHash(user *User) string {
|
||||||
syncer := getDbSyncerForUser(user)
|
syncer := getDbSyncerForUser(user)
|
||||||
|
@ -19,7 +19,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"xorm.io/core"
|
"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"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/casbin/casdoor/idp"
|
"github.com/casdoor/casdoor/idp"
|
||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -64,6 +64,12 @@ func GetUserByFields(organization string, field string) *User {
|
|||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check ID card
|
||||||
|
user = GetUserByField(organization, "id_card", field)
|
||||||
|
if user != nil {
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/astaxie/beego"
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ package object
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -38,14 +38,12 @@ type Webhook struct {
|
|||||||
ContentType string `xorm:"varchar(100)" json:"contentType"`
|
ContentType string `xorm:"varchar(100)" json:"contentType"`
|
||||||
Headers []*Header `xorm:"mediumtext" json:"headers"`
|
Headers []*Header `xorm:"mediumtext" json:"headers"`
|
||||||
Events []string `xorm:"varchar(100)" json:"events"`
|
Events []string `xorm:"varchar(100)" json:"events"`
|
||||||
|
IsUserExtended bool `json:"isUserExtended"`
|
||||||
IsEnabled bool `json:"isEnabled"`
|
IsEnabled bool `json:"isEnabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetWebhookCount(owner, field, value string) int {
|
func GetWebhookCount(owner, field, value string) int {
|
||||||
session := adapter.Engine.Where("owner=?", owner)
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
if field != "" && value != "" {
|
|
||||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
|
||||||
}
|
|
||||||
count, err := session.Count(&Webhook{})
|
count, err := session.Count(&Webhook{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -18,7 +18,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func sendWebhook(webhook *Webhook, record *Record) error {
|
func sendWebhook(webhook *Webhook, record *Record) error {
|
||||||
|
@ -20,8 +20,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/astaxie/beego/context"
|
"github.com/astaxie/beego/context"
|
||||||
"github.com/casbin/casdoor/authz"
|
"github.com/casdoor/casdoor/authz"
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Object struct {
|
type Object struct {
|
||||||
|
@ -16,11 +16,10 @@ package routers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/astaxie/beego/context"
|
"github.com/astaxie/beego/context"
|
||||||
"github.com/casbin/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func AutoSigninFilter(ctx *context.Context) {
|
func AutoSigninFilter(ctx *context.Context) {
|
||||||
@ -28,20 +27,28 @@ func AutoSigninFilter(ctx *context.Context) {
|
|||||||
// return
|
// return
|
||||||
//}
|
//}
|
||||||
|
|
||||||
// "/page?access_token=123"
|
// GET parameter like "/page?access_token=123" or
|
||||||
|
// HTTP Bearer token like "Authorization: Bearer 123"
|
||||||
accessToken := ctx.Input.Query("accessToken")
|
accessToken := ctx.Input.Query("accessToken")
|
||||||
|
if accessToken == "" {
|
||||||
|
accessToken = parseBearerToken(ctx)
|
||||||
|
}
|
||||||
if accessToken != "" {
|
if accessToken != "" {
|
||||||
claims, err := object.ParseJwtToken(accessToken)
|
token := object.GetTokenByAccessToken(accessToken)
|
||||||
if err != nil {
|
if token == nil {
|
||||||
responseError(ctx, "invalid JWT token")
|
responseError(ctx, "Access token doesn't exist")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if time.Now().Unix() > claims.ExpiresAt.Unix() {
|
|
||||||
responseError(ctx, "expired JWT token")
|
if !util.IsTokenExpired(token.CreatedTime, token.ExpiresIn) {
|
||||||
|
responseError(ctx, "Access token has expired")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
userId := fmt.Sprintf("%s/%s", claims.User.Owner, claims.User.Name)
|
userId := fmt.Sprintf("%s/%s", token.Organization, token.User)
|
||||||
|
application, _ := object.GetApplicationByUserId(fmt.Sprintf("app/%s", token.Application))
|
||||||
setSessionUser(ctx, userId)
|
setSessionUser(ctx, userId)
|
||||||
|
setSessionOidc(ctx, token.Scope, application.ClientId)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,17 +74,4 @@ func AutoSigninFilter(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP Bearer token
|
|
||||||
// Authorization: Bearer bearerToken
|
|
||||||
bearerToken := parseBearerToken(ctx)
|
|
||||||
if bearerToken != "" {
|
|
||||||
claims, err := object.ParseJwtToken(bearerToken)
|
|
||||||
if err != nil {
|
|
||||||
responseError(ctx, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setSessionUser(ctx, fmt.Sprintf("%s/%s", claims.Owner, claims.Name))
|
|
||||||
setSessionExpire(ctx, claims.ExpiresAt.Unix())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -19,8 +19,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/astaxie/beego/context"
|
"github.com/astaxie/beego/context"
|
||||||
"github.com/casbin/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Response struct {
|
type Response struct {
|
||||||
@ -97,6 +97,18 @@ func setSessionExpire(ctx *context.Context, ExpireTime int64) {
|
|||||||
ctx.Input.CruSession.SessionRelease(ctx.ResponseWriter)
|
ctx.Input.CruSession.SessionRelease(ctx.ResponseWriter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setSessionOidc(ctx *context.Context, scope string, aud string) {
|
||||||
|
err := ctx.Input.CruSession.Set("scope", scope)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
err = ctx.Input.CruSession.Set("aud", aud)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
ctx.Input.CruSession.SessionRelease(ctx.ResponseWriter)
|
||||||
|
}
|
||||||
|
|
||||||
func parseBearerToken(ctx *context.Context) string {
|
func parseBearerToken(ctx *context.Context) string {
|
||||||
header := ctx.Request.Header.Get("Authorization")
|
header := ctx.Request.Header.Get("Authorization")
|
||||||
tokens := strings.Split(header, " ")
|
tokens := strings.Split(header, " ")
|
||||||
|
@ -18,8 +18,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/astaxie/beego/context"
|
"github.com/astaxie/beego/context"
|
||||||
"github.com/casbin/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getUser(ctx *context.Context) (username string) {
|
func getUser(ctx *context.Context) (username string) {
|
||||||
|
@ -22,7 +22,7 @@ package routers
|
|||||||
import (
|
import (
|
||||||
"github.com/astaxie/beego"
|
"github.com/astaxie/beego"
|
||||||
|
|
||||||
"github.com/casbin/casdoor/controllers"
|
"github.com/casdoor/casdoor/controllers"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -50,6 +50,7 @@ func initAPI() {
|
|||||||
beego.Router("/api/get-app-login", &controllers.ApiController{}, "GET:GetApplicationLogin")
|
beego.Router("/api/get-app-login", &controllers.ApiController{}, "GET:GetApplicationLogin")
|
||||||
beego.Router("/api/logout", &controllers.ApiController{}, "POST:Logout")
|
beego.Router("/api/logout", &controllers.ApiController{}, "POST:Logout")
|
||||||
beego.Router("/api/get-account", &controllers.ApiController{}, "GET:GetAccount")
|
beego.Router("/api/get-account", &controllers.ApiController{}, "GET:GetAccount")
|
||||||
|
beego.Router("/api/userinfo", &controllers.ApiController{}, "GET:GetUserinfo")
|
||||||
beego.Router("/api/unlink", &controllers.ApiController{}, "POST:Unlink")
|
beego.Router("/api/unlink", &controllers.ApiController{}, "POST:Unlink")
|
||||||
beego.Router("/api/get-saml-login", &controllers.ApiController{}, "GET:GetSamlLogin")
|
beego.Router("/api/get-saml-login", &controllers.ApiController{}, "GET:GetSamlLogin")
|
||||||
beego.Router("/api/acs", &controllers.ApiController{}, "POST:HandleSamlLogin")
|
beego.Router("/api/acs", &controllers.ApiController{}, "POST:HandleSamlLogin")
|
||||||
@ -68,6 +69,19 @@ func initAPI() {
|
|||||||
beego.Router("/api/update-user", &controllers.ApiController{}, "POST:UpdateUser")
|
beego.Router("/api/update-user", &controllers.ApiController{}, "POST:UpdateUser")
|
||||||
beego.Router("/api/add-user", &controllers.ApiController{}, "POST:AddUser")
|
beego.Router("/api/add-user", &controllers.ApiController{}, "POST:AddUser")
|
||||||
beego.Router("/api/delete-user", &controllers.ApiController{}, "POST:DeleteUser")
|
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/set-password", &controllers.ApiController{}, "POST:SetPassword")
|
||||||
beego.Router("/api/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword")
|
beego.Router("/api/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword")
|
||||||
@ -112,6 +126,7 @@ func initAPI() {
|
|||||||
beego.Router("/api/delete-token", &controllers.ApiController{}, "POST:DeleteToken")
|
beego.Router("/api/delete-token", &controllers.ApiController{}, "POST:DeleteToken")
|
||||||
beego.Router("/api/login/oauth/code", &controllers.ApiController{}, "POST:GetOAuthCode")
|
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/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", &controllers.ApiController{}, "GET:GetRecords")
|
||||||
beego.Router("/api/get-records-filter", &controllers.ApiController{}, "POST:GetRecordsByFilter")
|
beego.Router("/api/get-records-filter", &controllers.ApiController{}, "POST:GetRecordsByFilter")
|
||||||
@ -128,6 +143,12 @@ func initAPI() {
|
|||||||
beego.Router("/api/add-syncer", &controllers.ApiController{}, "POST:AddSyncer")
|
beego.Router("/api/add-syncer", &controllers.ApiController{}, "POST:AddSyncer")
|
||||||
beego.Router("/api/delete-syncer", &controllers.ApiController{}, "POST:DeleteSyncer")
|
beego.Router("/api/delete-syncer", &controllers.ApiController{}, "POST:DeleteSyncer")
|
||||||
|
|
||||||
|
beego.Router("/api/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-email", &controllers.ApiController{}, "POST:SendEmail")
|
||||||
beego.Router("/api/send-sms", &controllers.ApiController{}, "POST:SendSms")
|
beego.Router("/api/send-sms", &controllers.ApiController{}, "POST:SendSms")
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/astaxie/beego/context"
|
"github.com/astaxie/beego/context"
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func StaticFilter(ctx *context.Context) {
|
func StaticFilter(ctx *context.Context) {
|
||||||
|
19
util/path.go
19
util/path.go
@ -18,6 +18,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,6 +29,24 @@ func FileExist(path string) bool {
|
|||||||
return true
|
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 {
|
func UrlJoin(base string, path string) string {
|
||||||
res := fmt.Sprintf("%s/%s", strings.TrimRight(base, "/"), strings.TrimLeft(path, "/"))
|
res := fmt.Sprintf("%s/%s", strings.TrimRight(base, "/"), strings.TrimLeft(path, "/"))
|
||||||
return res
|
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)
|
||||||
|
}
|
@ -28,3 +28,9 @@ func GetCurrentTime() string {
|
|||||||
func GetCurrentUnixTime() string {
|
func GetCurrentUnixTime() string {
|
||||||
return strconv.FormatInt(time.Now().UnixNano(), 10)
|
return strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsTokenExpired(createdTime string, expiresIn int) bool {
|
||||||
|
createdTimeObj, _ := time.Parse(time.RFC3339, createdTime)
|
||||||
|
expiresAtObj := createdTimeObj.Add(time.Duration(expiresIn) * time.Minute)
|
||||||
|
return time.Now().Before(expiresAtObj)
|
||||||
|
}
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"codemirror": "^5.61.1",
|
"codemirror": "^5.61.1",
|
||||||
"copy-to-clipboard": "^3.3.1",
|
"copy-to-clipboard": "^3.3.1",
|
||||||
"craco-less": "^1.17.1",
|
"craco-less": "^1.17.1",
|
||||||
|
"file-saver": "^2.0.5",
|
||||||
"i18n-iso-countries": "^7.0.0",
|
"i18n-iso-countries": "^7.0.0",
|
||||||
"i18next": "^19.8.9",
|
"i18next": "^19.8.9",
|
||||||
"moment": "^2.29.1",
|
"moment": "^2.29.1",
|
||||||
|
@ -23,12 +23,15 @@ import OrganizationListPage from "./OrganizationListPage";
|
|||||||
import OrganizationEditPage from "./OrganizationEditPage";
|
import OrganizationEditPage from "./OrganizationEditPage";
|
||||||
import UserListPage from "./UserListPage";
|
import UserListPage from "./UserListPage";
|
||||||
import UserEditPage from "./UserEditPage";
|
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 ProviderListPage from "./ProviderListPage";
|
||||||
import ProviderEditPage from "./ProviderEditPage";
|
import ProviderEditPage from "./ProviderEditPage";
|
||||||
import ApplicationListPage from "./ApplicationListPage";
|
import ApplicationListPage from "./ApplicationListPage";
|
||||||
import ApplicationEditPage from "./ApplicationEditPage";
|
import ApplicationEditPage from "./ApplicationEditPage";
|
||||||
import ResourceListPage from "./ResourceListPage";
|
import ResourceListPage from "./ResourceListPage";
|
||||||
// import ResourceEditPage from "./ResourceEditPage";
|
|
||||||
import LdapEditPage from "./LdapEditPage";
|
import LdapEditPage from "./LdapEditPage";
|
||||||
import LdapSyncPage from "./LdapSyncPage";
|
import LdapSyncPage from "./LdapSyncPage";
|
||||||
import TokenListPage from "./TokenListPage";
|
import TokenListPage from "./TokenListPage";
|
||||||
@ -38,6 +41,8 @@ import WebhookListPage from "./WebhookListPage";
|
|||||||
import WebhookEditPage from "./WebhookEditPage";
|
import WebhookEditPage from "./WebhookEditPage";
|
||||||
import SyncerListPage from "./SyncerListPage";
|
import SyncerListPage from "./SyncerListPage";
|
||||||
import SyncerEditPage from "./SyncerEditPage";
|
import SyncerEditPage from "./SyncerEditPage";
|
||||||
|
import CertListPage from "./CertListPage";
|
||||||
|
import CertEditPage from "./CertEditPage";
|
||||||
import AccountPage from "./account/AccountPage";
|
import AccountPage from "./account/AccountPage";
|
||||||
import HomePage from "./basic/HomePage";
|
import HomePage from "./basic/HomePage";
|
||||||
import CustomGithubCorner from "./CustomGithubCorner";
|
import CustomGithubCorner from "./CustomGithubCorner";
|
||||||
@ -101,6 +106,10 @@ class App extends Component {
|
|||||||
this.setState({ selectedMenuKey: '/organizations' });
|
this.setState({ selectedMenuKey: '/organizations' });
|
||||||
} else if (uri.includes('/users')) {
|
} else if (uri.includes('/users')) {
|
||||||
this.setState({ selectedMenuKey: '/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')) {
|
} else if (uri.includes('/providers')) {
|
||||||
this.setState({ selectedMenuKey: '/providers' });
|
this.setState({ selectedMenuKey: '/providers' });
|
||||||
} else if (uri.includes('/applications')) {
|
} else if (uri.includes('/applications')) {
|
||||||
@ -115,6 +124,8 @@ class App extends Component {
|
|||||||
this.setState({ selectedMenuKey: '/webhooks' });
|
this.setState({ selectedMenuKey: '/webhooks' });
|
||||||
} else if (uri.includes('/syncers')) {
|
} else if (uri.includes('/syncers')) {
|
||||||
this.setState({ selectedMenuKey: '/syncers' });
|
this.setState({ selectedMenuKey: '/syncers' });
|
||||||
|
} else if (uri.includes('/certs')) {
|
||||||
|
this.setState({ selectedMenuKey: '/certs' });
|
||||||
} else if (uri.includes('/signup')) {
|
} else if (uri.includes('/signup')) {
|
||||||
this.setState({ selectedMenuKey: '/signup' });
|
this.setState({ selectedMenuKey: '/signup' });
|
||||||
} else if (uri.includes('/login')) {
|
} else if (uri.includes('/login')) {
|
||||||
@ -324,6 +335,20 @@ class App extends Component {
|
|||||||
</Link>
|
</Link>
|
||||||
</Menu.Item>
|
</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(
|
res.push(
|
||||||
<Menu.Item key="/providers">
|
<Menu.Item key="/providers">
|
||||||
<Link to="/providers">
|
<Link to="/providers">
|
||||||
@ -376,6 +401,13 @@ class App extends Component {
|
|||||||
</Link>
|
</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
);
|
);
|
||||||
|
res.push(
|
||||||
|
<Menu.Item key="/certs">
|
||||||
|
<Link to="/certs">
|
||||||
|
{i18next.t("general:Certs")}
|
||||||
|
</Link>
|
||||||
|
</Menu.Item>
|
||||||
|
);
|
||||||
res.push(
|
res.push(
|
||||||
<Menu.Item key="/swagger">
|
<Menu.Item key="/swagger">
|
||||||
<a target="_blank" rel="noreferrer" href={Setting.isLocalhost() ? `${Setting.ServerUrl}/swagger` : "/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="/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" 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="/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" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)}/>
|
||||||
<Route exact path="/providers/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)}/>
|
<Route exact path="/providers/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)}/>
|
||||||
<Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)}/>
|
<Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)}/>
|
||||||
@ -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="/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" 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="/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="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)}/>
|
||||||
<Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />}/>
|
<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.")}
|
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
|
||||||
|
@ -21,7 +21,6 @@ import * as Setting from "./Setting";
|
|||||||
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
import * as ProviderBackend from "./backend/ProviderBackend";
|
|
||||||
|
|
||||||
class ApplicationListPage extends BaseListPage {
|
class ApplicationListPage extends 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.
|
// limitations under the License.
|
||||||
|
|
||||||
export const ShowGithubCorner = false;
|
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 ForceLanguage = "";
|
||||||
export const DefaultLanguage = "en";
|
export const DefaultLanguage = "en";
|
||||||
|
@ -59,7 +59,7 @@ export const PasswordModal = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Row>
|
<Row>
|
||||||
<Button type="default" onClick={showModal}>
|
<Button type="default" disabled={props.disabled} onClick={showModal}>
|
||||||
{ hasOldPassword ? i18next.t("user:Modify password...") : i18next.t("user:Set password...")}
|
{ hasOldPassword ? i18next.t("user:Modify password...") : i18next.t("user:Set password...")}
|
||||||
</Button>
|
</Button>
|
||||||
<Modal
|
<Modal
|
||||||
|
276
web/src/PermissionEditPage.js
Normal file
276
web/src/PermissionEditPage.js
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
// 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, Row, Select, Switch} from 'antd';
|
||||||
|
import * as PermissionBackend from "./backend/PermissionBackend";
|
||||||
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
|
import * as UserBackend from "./backend/UserBackend";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import * as RoleBackend from "./backend/RoleBackend";
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
|
class PermissionEditPage extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
classes: props,
|
||||||
|
organizationName: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
|
||||||
|
permissionName: props.match.params.permissionName,
|
||||||
|
permission: null,
|
||||||
|
organizations: [],
|
||||||
|
users: [],
|
||||||
|
roles: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
UNSAFE_componentWillMount() {
|
||||||
|
this.getPermission();
|
||||||
|
this.getOrganizations();
|
||||||
|
}
|
||||||
|
|
||||||
|
getPermission() {
|
||||||
|
PermissionBackend.getPermission(this.state.organizationName, this.state.permissionName)
|
||||||
|
.then((permission) => {
|
||||||
|
this.setState({
|
||||||
|
permission: permission,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.getUsers(permission.owner);
|
||||||
|
this.getRoles(permission.owner);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrganizations() {
|
||||||
|
OrganizationBackend.getOrganizations("admin")
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
organizations: (res.msg === undefined) ? res : [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getUsers(organizationName) {
|
||||||
|
UserBackend.getUsers(organizationName)
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
users: res,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getRoles(organizationName) {
|
||||||
|
RoleBackend.getRoles(organizationName)
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
roles: res,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
parsePermissionField(key, value) {
|
||||||
|
if ([""].includes(key)) {
|
||||||
|
value = Setting.myParseInt(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePermissionField(key, value) {
|
||||||
|
value = this.parsePermissionField(key, value);
|
||||||
|
|
||||||
|
let permission = this.state.permission;
|
||||||
|
permission[key] = value;
|
||||||
|
this.setState({
|
||||||
|
permission: permission,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPermission() {
|
||||||
|
return (
|
||||||
|
<Card size="small" title={
|
||||||
|
<div>
|
||||||
|
{i18next.t("permission:Edit Permission")}
|
||||||
|
<Button onClick={() => this.submitPermissionEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||||
|
<Button style={{marginLeft: '20px'}} type="primary" onClick={() => this.submitPermissionEdit(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:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: '100%'}} value={this.state.permission.owner} onChange={(owner => {
|
||||||
|
this.updatePermissionField('owner', owner);
|
||||||
|
|
||||||
|
this.getUsers(owner);
|
||||||
|
this.getRoles(owner);
|
||||||
|
})}>
|
||||||
|
{
|
||||||
|
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.permission.name} onChange={e => {
|
||||||
|
this.updatePermissionField('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.permission.displayName} onChange={e => {
|
||||||
|
this.updatePermissionField('displayName', e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("role:Sub users"), i18next.t("role:Sub users - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} mode="tags" style={{width: '100%'}} value={this.state.permission.users} onChange={(value => {this.updatePermissionField('users', value);})}>
|
||||||
|
{
|
||||||
|
this.state.users.map((user, index) => <Option key={index} value={`${user.owner}/${user.name}`}>{`${user.owner}/${user.name}`}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("role:Sub roles"), i18next.t("role:Sub roles - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} mode="tags" style={{width: '100%'}} value={this.state.permission.roles} onChange={(value => {this.updatePermissionField('roles', value);})}>
|
||||||
|
{
|
||||||
|
this.state.roles.filter(roles => (roles.owner !== this.state.roles.owner || roles.name !== this.state.roles.name)).map((permission, index) => <Option key={index} value={`${permission.owner}/${permission.name}`}>{`${permission.owner}/${permission.name}`}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("permission:Resource type"), i18next.t("permission:Resource type - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: '100%'}} value={this.state.permission.resourceType} onChange={(value => {
|
||||||
|
this.updatePermissionField('resourceType', value);
|
||||||
|
})}>
|
||||||
|
{
|
||||||
|
[
|
||||||
|
{id: 'Application', name: 'Application'},
|
||||||
|
].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("permission:Actions"), i18next.t("permission:Actions - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} mode="tags" style={{width: '100%'}} value={this.state.permission.actions} onChange={(value => {
|
||||||
|
this.updatePermissionField('actions', value);
|
||||||
|
})}>
|
||||||
|
{
|
||||||
|
[
|
||||||
|
{id: 'Read', name: 'Read'},
|
||||||
|
{id: 'Write', name: 'Write'},
|
||||||
|
{id: 'Admin', name: 'Admin'},
|
||||||
|
].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("permission:Effect"), i18next.t("permission:Effect - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: '100%'}} value={this.state.permission.effect} onChange={(value => {
|
||||||
|
this.updatePermissionField('effect', value);
|
||||||
|
})}>
|
||||||
|
{
|
||||||
|
[
|
||||||
|
{id: 'Allow', name: 'Allow'},
|
||||||
|
{id: 'Deny', name: 'Deny'},
|
||||||
|
].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()) ? 19 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={1} >
|
||||||
|
<Switch checked={this.state.permission.isEnabled} onChange={checked => {
|
||||||
|
this.updatePermissionField('isEnabled', checked);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
submitPermissionEdit(willExist) {
|
||||||
|
let permission = Setting.deepCopy(this.state.permission);
|
||||||
|
PermissionBackend.updatePermission(this.state.organizationName, this.state.permissionName, permission)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.msg === "") {
|
||||||
|
Setting.showMessage("success", `Successfully saved`);
|
||||||
|
this.setState({
|
||||||
|
permissionName: this.state.permission.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (willExist) {
|
||||||
|
this.props.history.push(`/permissions`);
|
||||||
|
} else {
|
||||||
|
this.props.history.push(`/permissions/${this.state.permission.owner}/${this.state.permission.name}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", res.msg);
|
||||||
|
this.updatePermissionField('name', this.state.permissionName);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `Failed to connect to server: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
this.state.permission !== null ? this.renderPermission() : null
|
||||||
|
}
|
||||||
|
<div style={{marginTop: '20px', marginLeft: '40px'}}>
|
||||||
|
<Button size="large" onClick={() => this.submitPermissionEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||||
|
<Button style={{marginLeft: '20px'}} type="primary" size="large" onClick={() => this.submitPermissionEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PermissionEditPage;
|
271
web/src/PermissionListPage.js
Normal file
271
web/src/PermissionListPage.js
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
// 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, Switch, Table} from 'antd';
|
||||||
|
import moment from "moment";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import * as PermissionBackend from "./backend/PermissionBackend";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import BaseListPage from "./BaseListPage";
|
||||||
|
|
||||||
|
class PermissionListPage extends BaseListPage {
|
||||||
|
newPermission() {
|
||||||
|
const randomName = Setting.getRandomName();
|
||||||
|
return {
|
||||||
|
owner: "built-in",
|
||||||
|
name: `permission_${randomName}`,
|
||||||
|
createdTime: moment().format(),
|
||||||
|
displayName: `New Permission - ${randomName}`,
|
||||||
|
users: [],
|
||||||
|
roles: [],
|
||||||
|
resourceType: "Application",
|
||||||
|
resources: ["app-built-in"],
|
||||||
|
action: "Read",
|
||||||
|
effect: "Allow",
|
||||||
|
isEnabled: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addPermission() {
|
||||||
|
const newPermission = this.newPermission();
|
||||||
|
PermissionBackend.addPermission(newPermission)
|
||||||
|
.then((res) => {
|
||||||
|
Setting.showMessage("success", `Permission added successfully`);
|
||||||
|
this.props.history.push(`/permissions/${newPermission.owner}/${newPermission.name}`);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `Permission failed to add: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deletePermission(i) {
|
||||||
|
PermissionBackend.deletePermission(this.state.data[i])
|
||||||
|
.then((res) => {
|
||||||
|
Setting.showMessage("success", `Permission deleted successfully`);
|
||||||
|
this.setState({
|
||||||
|
data: Setting.deleteRow(this.state.data, i),
|
||||||
|
pagination: {total: this.state.pagination.total - 1},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `Permission failed to delete: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTable(permissions) {
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Organization"),
|
||||||
|
dataIndex: 'owner',
|
||||||
|
key: 'owner',
|
||||||
|
width: '120px',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps('owner'),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Link to={`/organizations/${text}`}>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Name"),
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
width: '150px',
|
||||||
|
fixed: 'left',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps('name'),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Link to={`/permissions/${text}`}>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Created time"),
|
||||||
|
dataIndex: 'createdTime',
|
||||||
|
key: 'createdTime',
|
||||||
|
width: '160px',
|
||||||
|
sorter: true,
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return Setting.getFormattedDate(text);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Display name"),
|
||||||
|
dataIndex: 'displayName',
|
||||||
|
key: 'displayName',
|
||||||
|
width: '160px',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps('displayName'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("role:Sub users"),
|
||||||
|
dataIndex: 'users',
|
||||||
|
key: 'users',
|
||||||
|
// width: '100px',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps('users'),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return Setting.getTags(text);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("role:Sub roles"),
|
||||||
|
dataIndex: 'roles',
|
||||||
|
key: 'roles',
|
||||||
|
// width: '100px',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps('roles'),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return Setting.getTags(text);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("permission:Resource type"),
|
||||||
|
dataIndex: 'resourceType',
|
||||||
|
key: 'resourceType',
|
||||||
|
filterMultiple: false,
|
||||||
|
filters: [
|
||||||
|
{text: 'Application', value: 'Application'},
|
||||||
|
],
|
||||||
|
width: '170px',
|
||||||
|
sorter: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("permission:Resources"),
|
||||||
|
dataIndex: 'resources',
|
||||||
|
key: 'resources',
|
||||||
|
// width: '100px',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps('resources'),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return Setting.getTags(text);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("permission:Actions"),
|
||||||
|
dataIndex: 'actions',
|
||||||
|
key: 'actions',
|
||||||
|
// width: '100px',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps('actions'),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return Setting.getTags(text);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("permission:Effect"),
|
||||||
|
dataIndex: 'effect',
|
||||||
|
key: 'effect',
|
||||||
|
filterMultiple: false,
|
||||||
|
filters: [
|
||||||
|
{text: 'Allow', value: 'Allow'},
|
||||||
|
{text: 'Deny', value: 'Deny'},
|
||||||
|
],
|
||||||
|
width: '120px',
|
||||||
|
sorter: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Is enabled"),
|
||||||
|
dataIndex: 'isEnabled',
|
||||||
|
key: 'isEnabled',
|
||||||
|
width: '120px',
|
||||||
|
sorter: true,
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Action"),
|
||||||
|
dataIndex: '',
|
||||||
|
key: 'op',
|
||||||
|
width: '170px',
|
||||||
|
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/permissions/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
|
<Popconfirm
|
||||||
|
title={`Sure to delete permission: ${record.name} ?`}
|
||||||
|
onConfirm={() => this.deletePermission(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={permissions} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||||
|
title={() => (
|
||||||
|
<div>
|
||||||
|
{i18next.t("general:Permissions")}
|
||||||
|
<Button type="primary" size="small" onClick={this.addPermission.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
loading={this.state.loading}
|
||||||
|
onChange={this.handleTableChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch = (params = {}) => {
|
||||||
|
let field = params.searchedColumn, value = params.searchText;
|
||||||
|
let sortField = params.sortField, sortOrder = params.sortOrder;
|
||||||
|
if (params.type !== undefined && params.type !== null) {
|
||||||
|
field = "type";
|
||||||
|
value = params.type;
|
||||||
|
}
|
||||||
|
this.setState({ loading: true });
|
||||||
|
PermissionBackend.getPermissions("", 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 PermissionListPage;
|
@ -91,18 +91,21 @@ class ProviderEditPage extends React.Component {
|
|||||||
getAppIdRow() {
|
getAppIdRow() {
|
||||||
let text, tooltip;
|
let text, tooltip;
|
||||||
if (this.state.provider.category === "SMS" && this.state.provider.type === "Tencent Cloud SMS") {
|
if (this.state.provider.category === "SMS" && this.state.provider.type === "Tencent Cloud SMS") {
|
||||||
text = "provider:App ID";
|
text = i18next.t("provider:App ID");
|
||||||
tooltip = "provider:App ID - Tooltip";
|
tooltip = i18next.t("provider:App ID - Tooltip");
|
||||||
|
} else if (this.state.provider.type === "WeCom" && this.state.provider.subType === "Internal") {
|
||||||
|
text = i18next.t("provider:Agent ID");
|
||||||
|
tooltip = i18next.t("provider:Agent ID - Tooltip");
|
||||||
} else if (this.state.provider.category === "SMS" && this.state.provider.type === "Volc Engine SMS") {
|
} else if (this.state.provider.category === "SMS" && this.state.provider.type === "Volc Engine SMS") {
|
||||||
text = "provider:SMS account";
|
text = i18next.t("provider:SMS account");
|
||||||
tooltip = "provider:SMS account - Tooltip";
|
tooltip = i18next.t("provider:SMS account - Tooltip");
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Row style={{marginTop: '20px'}} >
|
return <Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t(text), i18next.t(tooltip))} :
|
{Setting.getLabel(text, tooltip)} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.provider.appId} onChange={e => {
|
<Input value={this.state.provider.appId} onChange={e => {
|
||||||
@ -205,6 +208,21 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Row>
|
</Row>
|
||||||
{
|
{
|
||||||
this.state.provider.type !== "WeCom" ? null : (
|
this.state.provider.type !== "WeCom" ? null : (
|
||||||
|
<React.Fragment>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={2}>
|
||||||
|
{Setting.getLabel(i18next.t("provider:Sub type"), i18next.t("provider:Sub type - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: '100%'}} value={this.state.provider.subType} onChange={value => {
|
||||||
|
this.updateProviderField('subType', value);
|
||||||
|
}}>
|
||||||
|
{
|
||||||
|
Setting.getProviderSubTypeOptions(this.state.provider.type).map((providerSubType, index) => <Option key={index} value={providerSubType.id}>{providerSubType.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={2}>
|
<Col style={{marginTop: '5px'}} span={2}>
|
||||||
{Setting.getLabel(i18next.t("provider:Method"), i18next.t("provider:Method - Tooltip"))} :
|
{Setting.getLabel(i18next.t("provider:Method"), i18next.t("provider:Method - Tooltip"))} :
|
||||||
@ -219,6 +237,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Select>
|
</Select>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
@ -119,14 +119,14 @@ class ProviderListPage extends BaseListPage {
|
|||||||
{text: 'Storage', value: 'Storage'},
|
{text: 'Storage', value: 'Storage'},
|
||||||
{text: 'SAML', value: 'SAML'},
|
{text: 'SAML', value: 'SAML'},
|
||||||
],
|
],
|
||||||
width: '100px',
|
width: '110px',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("provider:Type"),
|
title: i18next.t("provider:Type"),
|
||||||
dataIndex: 'type',
|
dataIndex: 'type',
|
||||||
key: 'type',
|
key: 'type',
|
||||||
width: '80px',
|
width: '110px',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
filterMultiple: false,
|
filterMultiple: false,
|
||||||
filters: [
|
filters: [
|
||||||
@ -152,13 +152,6 @@ class ProviderListPage extends BaseListPage {
|
|||||||
return Setting.getShortText(text);
|
return Setting.getShortText(text);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// {
|
|
||||||
// title: 'Client secret',
|
|
||||||
// dataIndex: 'clientSecret',
|
|
||||||
// key: 'clientSecret',
|
|
||||||
// width: '150px',
|
|
||||||
// sorter: (a, b) => a.clientSecret.localeCompare(b.clientSecret),
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
title: i18next.t("provider:Provider URL"),
|
title: i18next.t("provider:Provider URL"),
|
||||||
dataIndex: 'providerUrl',
|
dataIndex: 'providerUrl',
|
||||||
|
@ -20,7 +20,6 @@ import * as RecordBackend from "./backend/RecordBackend";
|
|||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
import * as ProviderBackend from "./backend/ProviderBackend";
|
|
||||||
|
|
||||||
class RecordListPage extends BaseListPage {
|
class RecordListPage extends BaseListPage {
|
||||||
|
|
||||||
@ -92,7 +91,7 @@ class RecordListPage extends BaseListPage {
|
|||||||
title: i18next.t("general:Organization"),
|
title: i18next.t("general:Organization"),
|
||||||
dataIndex: 'organization',
|
dataIndex: 'organization',
|
||||||
key: 'organization',
|
key: 'organization',
|
||||||
width: '80px',
|
width: '110px',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps('organization'),
|
...this.getColumnSearchProps('organization'),
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
@ -122,7 +121,7 @@ class RecordListPage extends BaseListPage {
|
|||||||
title: i18next.t("general:Method"),
|
title: i18next.t("general:Method"),
|
||||||
dataIndex: 'method',
|
dataIndex: 'method',
|
||||||
key: 'method',
|
key: 'method',
|
||||||
width: '100px',
|
width: '110px',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
filterMultiple: false,
|
filterMultiple: false,
|
||||||
filters: [
|
filters: [
|
||||||
|
@ -17,7 +17,7 @@ import i18next from "i18next";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import * as Setting from "./Setting"
|
import * as Setting from "./Setting"
|
||||||
import * as UserBackend from "./backend/UserBackend"
|
import * as UserBackend from "./backend/UserBackend"
|
||||||
import {CountDownInput} from "./component/CountDownInput";
|
import {CountDownInput} from "./common/CountDownInput";
|
||||||
import {MailOutlined, PhoneOutlined} from "@ant-design/icons";
|
import {MailOutlined, PhoneOutlined} from "@ant-design/icons";
|
||||||
|
|
||||||
export const ResetModal = (props) => {
|
export const ResetModal = (props) => {
|
||||||
|
@ -21,7 +21,6 @@ import * as ResourceBackend from "./backend/ResourceBackend";
|
|||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
import * as ProviderBackend from "./backend/ProviderBackend";
|
|
||||||
|
|
||||||
class ResourceListPage extends BaseListPage {
|
class ResourceListPage extends BaseListPage {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
219
web/src/RoleEditPage.js
Normal file
219
web/src/RoleEditPage.js
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
// 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, Row, Select, Switch} from 'antd';
|
||||||
|
import * as RoleBackend from "./backend/RoleBackend";
|
||||||
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
|
import * as UserBackend from "./backend/UserBackend";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import i18next from "i18next";
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
|
class RoleEditPage extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
classes: props,
|
||||||
|
organizationName: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
|
||||||
|
roleName: props.match.params.roleName,
|
||||||
|
role: null,
|
||||||
|
organizations: [],
|
||||||
|
users: [],
|
||||||
|
roles: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
UNSAFE_componentWillMount() {
|
||||||
|
this.getRole();
|
||||||
|
this.getOrganizations();
|
||||||
|
}
|
||||||
|
|
||||||
|
getRole() {
|
||||||
|
RoleBackend.getRole(this.state.organizationName, this.state.roleName)
|
||||||
|
.then((role) => {
|
||||||
|
this.setState({
|
||||||
|
role: role,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.getUsers(role.owner);
|
||||||
|
this.getRoles(role.owner);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrganizations() {
|
||||||
|
OrganizationBackend.getOrganizations("admin")
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
organizations: (res.msg === undefined) ? res : [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getUsers(organizationName) {
|
||||||
|
UserBackend.getUsers(organizationName)
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
users: res,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getRoles(organizationName) {
|
||||||
|
RoleBackend.getRoles(organizationName)
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
roles: res,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
parseRoleField(key, value) {
|
||||||
|
if ([""].includes(key)) {
|
||||||
|
value = Setting.myParseInt(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateRoleField(key, value) {
|
||||||
|
value = this.parseRoleField(key, value);
|
||||||
|
|
||||||
|
let role = this.state.role;
|
||||||
|
role[key] = value;
|
||||||
|
this.setState({
|
||||||
|
role: role,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderRole() {
|
||||||
|
return (
|
||||||
|
<Card size="small" title={
|
||||||
|
<div>
|
||||||
|
{i18next.t("role:Edit Role")}
|
||||||
|
<Button onClick={() => this.submitRoleEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||||
|
<Button style={{marginLeft: '20px'}} type="primary" onClick={() => this.submitRoleEdit(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:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: '100%'}} value={this.state.role.owner} onChange={(value => {this.updateRoleField('owner', value);})}>
|
||||||
|
{
|
||||||
|
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.role.name} onChange={e => {
|
||||||
|
this.updateRoleField('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.role.displayName} onChange={e => {
|
||||||
|
this.updateRoleField('displayName', e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("role:Sub users"), i18next.t("role:Sub users - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} mode="tags" style={{width: '100%'}} value={this.state.role.users} onChange={(value => {this.updateRoleField('users', value);})}>
|
||||||
|
{
|
||||||
|
this.state.users.map((user, index) => <Option key={index} value={`${user.owner}/${user.name}`}>{`${user.owner}/${user.name}`}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("role:Sub roles"), i18next.t("role:Sub roles - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} mode="tags" style={{width: '100%'}} value={this.state.role.roles} onChange={(value => {this.updateRoleField('roles', value);})}>
|
||||||
|
{
|
||||||
|
this.state.roles.filter(role => (role.owner !== this.state.role.owner || role.name !== this.state.role.name)).map((role, index) => <Option key={index} value={`${role.owner}/${role.name}`}>{`${role.owner}/${role.name}`}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={1} >
|
||||||
|
<Switch checked={this.state.role.isEnabled} onChange={checked => {
|
||||||
|
this.updateRoleField('isEnabled', checked);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
submitRoleEdit(willExist) {
|
||||||
|
let role = Setting.deepCopy(this.state.role);
|
||||||
|
RoleBackend.updateRole(this.state.organizationName, this.state.roleName, role)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.msg === "") {
|
||||||
|
Setting.showMessage("success", `Successfully saved`);
|
||||||
|
this.setState({
|
||||||
|
roleName: this.state.role.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (willExist) {
|
||||||
|
this.props.history.push(`/roles`);
|
||||||
|
} else {
|
||||||
|
this.props.history.push(`/roles/${this.state.role.owner}/${this.state.role.name}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", res.msg);
|
||||||
|
this.updateRoleField('name', this.state.roleName);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `Failed to connect to server: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
this.state.role !== null ? this.renderRole() : null
|
||||||
|
}
|
||||||
|
<div style={{marginTop: '20px', marginLeft: '40px'}}>
|
||||||
|
<Button size="large" onClick={() => this.submitRoleEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||||
|
<Button style={{marginLeft: '20px'}} type="primary" size="large" onClick={() => this.submitRoleEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RoleEditPage;
|
222
web/src/RoleListPage.js
Normal file
222
web/src/RoleListPage.js
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
// 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, Switch, Table} from 'antd';
|
||||||
|
import moment from "moment";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import * as RoleBackend from "./backend/RoleBackend";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import BaseListPage from "./BaseListPage";
|
||||||
|
|
||||||
|
class RoleListPage extends BaseListPage {
|
||||||
|
newRole() {
|
||||||
|
const randomName = Setting.getRandomName();
|
||||||
|
return {
|
||||||
|
owner: "built-in",
|
||||||
|
name: `role_${randomName}`,
|
||||||
|
createdTime: moment().format(),
|
||||||
|
displayName: `New Role - ${randomName}`,
|
||||||
|
users: [],
|
||||||
|
roles: [],
|
||||||
|
isEnabled: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addRole() {
|
||||||
|
const newRole = this.newRole();
|
||||||
|
RoleBackend.addRole(newRole)
|
||||||
|
.then((res) => {
|
||||||
|
Setting.showMessage("success", `Role added successfully`);
|
||||||
|
this.props.history.push(`/roles/${newRole.owner}/${newRole.name}`);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `Role failed to add: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteRole(i) {
|
||||||
|
RoleBackend.deleteRole(this.state.data[i])
|
||||||
|
.then((res) => {
|
||||||
|
Setting.showMessage("success", `Role deleted successfully`);
|
||||||
|
this.setState({
|
||||||
|
data: Setting.deleteRow(this.state.data, i),
|
||||||
|
pagination: {total: this.state.pagination.total - 1},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `Role failed to delete: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTable(roles) {
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Organization"),
|
||||||
|
dataIndex: 'owner',
|
||||||
|
key: 'owner',
|
||||||
|
width: '120px',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps('owner'),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Link to={`/organizations/${text}`}>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Name"),
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
width: '150px',
|
||||||
|
fixed: 'left',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps('name'),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Link to={`/roles/${text}`}>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Created time"),
|
||||||
|
dataIndex: 'createdTime',
|
||||||
|
key: 'createdTime',
|
||||||
|
width: '160px',
|
||||||
|
sorter: true,
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return Setting.getFormattedDate(text);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Display name"),
|
||||||
|
dataIndex: 'displayName',
|
||||||
|
key: 'displayName',
|
||||||
|
width: '200px',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps('displayName'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("role:Sub users"),
|
||||||
|
dataIndex: 'users',
|
||||||
|
key: 'users',
|
||||||
|
// width: '100px',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps('users'),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return Setting.getTags(text);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("role:Sub roles"),
|
||||||
|
dataIndex: 'roles',
|
||||||
|
key: 'roles',
|
||||||
|
// width: '100px',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps('roles'),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return Setting.getTags(text);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Is enabled"),
|
||||||
|
dataIndex: 'isEnabled',
|
||||||
|
key: 'isEnabled',
|
||||||
|
width: '120px',
|
||||||
|
sorter: true,
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Action"),
|
||||||
|
dataIndex: '',
|
||||||
|
key: 'op',
|
||||||
|
width: '170px',
|
||||||
|
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/roles/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
|
<Popconfirm
|
||||||
|
title={`Sure to delete role: ${record.name} ?`}
|
||||||
|
onConfirm={() => this.deleteRole(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={roles} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||||
|
title={() => (
|
||||||
|
<div>
|
||||||
|
{i18next.t("general:Roles")}
|
||||||
|
<Button type="primary" size="small" onClick={this.addRole.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
loading={this.state.loading}
|
||||||
|
onChange={this.handleTableChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch = (params = {}) => {
|
||||||
|
let field = params.searchedColumn, value = params.searchText;
|
||||||
|
let sortField = params.sortField, sortOrder = params.sortOrder;
|
||||||
|
if (params.type !== undefined && params.type !== null) {
|
||||||
|
field = "type";
|
||||||
|
value = params.type;
|
||||||
|
}
|
||||||
|
this.setState({ loading: true });
|
||||||
|
RoleBackend.getRoles("", 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 RoleListPage;
|
@ -398,6 +398,7 @@ export function getProviderTypeOptions(category) {
|
|||||||
{id: 'WeCom', name: 'WeCom'},
|
{id: 'WeCom', name: 'WeCom'},
|
||||||
{id: 'Lark', name: 'Lark'},
|
{id: 'Lark', name: 'Lark'},
|
||||||
{id: 'GitLab', name: 'GitLab'},
|
{id: 'GitLab', name: 'GitLab'},
|
||||||
|
{id: 'Baidu', name: 'Baidu'},
|
||||||
{id: 'Apple', name: 'Apple'},
|
{id: 'Apple', name: 'Apple'},
|
||||||
{id: 'AzureAD', name: 'AzureAD'},
|
{id: 'AzureAD', name: 'AzureAD'},
|
||||||
{id: 'Slack', name: 'Slack'},
|
{id: 'Slack', name: 'Slack'},
|
||||||
@ -436,6 +437,19 @@ export function getProviderTypeOptions(category) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getProviderSubTypeOptions(type) {
|
||||||
|
if (type === "WeCom") {
|
||||||
|
return (
|
||||||
|
[
|
||||||
|
{id: 'Internal', name: 'Internal'},
|
||||||
|
{id: 'Third-party', name: 'Third-party'},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function renderLogo(application) {
|
export function renderLogo(application) {
|
||||||
if (application === null) {
|
if (application === null) {
|
||||||
return null;
|
return null;
|
||||||
@ -590,11 +604,12 @@ export function getNewRowNameForTable(table, rowName) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getTagColor(s) {
|
export function getTagColor(s) {
|
||||||
return "success";
|
return "processing";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTags(tags) {
|
export function getTags(tags) {
|
||||||
let res = [];
|
let res = [];
|
||||||
|
if (!tags) return res;
|
||||||
tags.forEach((tag, i) => {
|
tags.forEach((tag, i) => {
|
||||||
res.push(
|
res.push(
|
||||||
<Tag color={getTagColor(tag)}>
|
<Tag color={getTagColor(tag)}>
|
||||||
|
@ -74,6 +74,7 @@ class SignupTable extends React.Component {
|
|||||||
{id: 'Display name', name: 'Display name'},
|
{id: 'Display name', name: 'Display name'},
|
||||||
{id: 'Affiliation', name: 'Affiliation'},
|
{id: 'Affiliation', name: 'Affiliation'},
|
||||||
{id: 'Country/Region', name: 'Country/Region'},
|
{id: 'Country/Region', name: 'Country/Region'},
|
||||||
|
{id: 'ID card', name: 'ID card'},
|
||||||
{id: 'Email', name: 'Email'},
|
{id: 'Email', name: 'Email'},
|
||||||
{id: 'Password', name: 'Password'},
|
{id: 'Password', name: 'Password'},
|
||||||
{id: 'Confirm password', name: 'Confirm password'},
|
{id: 'Confirm password', name: 'Confirm password'},
|
||||||
|
@ -21,6 +21,11 @@ import * as Setting from "./Setting";
|
|||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import SyncerTableColumnTable from "./SyncerTableColumnTable";
|
import SyncerTableColumnTable from "./SyncerTableColumnTable";
|
||||||
|
|
||||||
|
import {Controlled as CodeMirror} from 'react-codemirror2';
|
||||||
|
import "codemirror/lib/codemirror.css";
|
||||||
|
require('codemirror/theme/material-darker.css');
|
||||||
|
require("codemirror/mode/javascript/javascript");
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
class SyncerEditPage extends React.Component {
|
class SyncerEditPage extends React.Component {
|
||||||
@ -248,6 +253,22 @@ class SyncerEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("syncer:Error text"), i18next.t("syncer:Error text - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<div style={{width: "100%", height: "300px"}} >
|
||||||
|
<CodeMirror
|
||||||
|
value={this.state.syncer.errorText}
|
||||||
|
options={{mode: 'javascript', theme: "material-darker"}}
|
||||||
|
onBeforeChange={(editor, data, value) => {
|
||||||
|
this.updateSyncerField("errorText", value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} :
|
||||||
|
@ -20,7 +20,6 @@ import * as Setting from "./Setting";
|
|||||||
import * as SyncerBackend from "./backend/SyncerBackend";
|
import * as SyncerBackend from "./backend/SyncerBackend";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
import * as ProviderBackend from "./backend/ProviderBackend";
|
|
||||||
|
|
||||||
class SyncerListPage extends BaseListPage {
|
class SyncerListPage extends BaseListPage {
|
||||||
|
|
||||||
@ -42,7 +41,7 @@ class SyncerListPage extends BaseListPage {
|
|||||||
tableColumns: [],
|
tableColumns: [],
|
||||||
affiliationTable: "",
|
affiliationTable: "",
|
||||||
avatarBaseUrl: "",
|
avatarBaseUrl: "",
|
||||||
syncInterval: 1,
|
syncInterval: 10,
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -112,7 +111,7 @@ class SyncerListPage extends BaseListPage {
|
|||||||
title: i18next.t("general:Created time"),
|
title: i18next.t("general:Created time"),
|
||||||
dataIndex: 'createdTime',
|
dataIndex: 'createdTime',
|
||||||
key: 'createdTime',
|
key: 'createdTime',
|
||||||
width: '180px',
|
width: '160px',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return Setting.getFormattedDate(text);
|
return Setting.getFormattedDate(text);
|
||||||
@ -187,7 +186,7 @@ class SyncerListPage extends BaseListPage {
|
|||||||
title: i18next.t("syncer:Sync interval"),
|
title: i18next.t("syncer:Sync interval"),
|
||||||
dataIndex: 'syncInterval',
|
dataIndex: 'syncInterval',
|
||||||
key: 'syncInterval',
|
key: 'syncInterval',
|
||||||
width: '120px',
|
width: '130px',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps('syncInterval'),
|
...this.getColumnSearchProps('syncInterval'),
|
||||||
},
|
},
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user