Compare commits

...

28 Commits

Author SHA1 Message Date
June
55d5ae10f2 fix: fix infinite loop in containsRole() (#2136) 2023-07-25 20:53:08 +08:00
Yang Luo
6986dad295 Use arg to control createDatabaseForPostgres() 2023-07-25 18:36:15 +08:00
Yaodong Yu
949feb18af feat: add basic enforcer manager (#2130)
* feat: add basic enforcer manager

* chore: generate swagger
2023-07-25 17:17:59 +08:00
haiwu
d1f88ca9b8 feat: support google one tap signin (#2131)
* feat: add google one tap support

* feat: gofumpt

* feat: add google provider rule conf

* feat: update i18n
2023-07-25 15:49:15 +08:00
Yaodong Yu
bfe8e5f3e7 fix: fix response data assignment error (#2129) 2023-07-25 13:52:31 +08:00
Yang Luo
702ee6acd0 Print log for StartLdapServer()'s error 2023-07-25 01:49:43 +08:00
Yaodong Yu
0a9587901a fix: fix response data assignment error in ApplicationEditPage.js (#2126) 2023-07-24 20:09:09 +08:00
Yaodong Yu
577bd6ce58 feat: fix response data assignment error (#2123) 2023-07-24 14:52:30 +08:00
Yaodong Yu
3c4112dd44 refactor: optimize the code to getEnforcer (#2120) 2023-07-24 14:02:34 +08:00
haiwu
b7a37126ad feat: restrict redirectUrls for CAS login (#2118)
* feat: support cas restricted login

* feat: add cas login i18n

* feat: add CheckCasService for all cas api

* feat: gofumpt

* feat: replace 404

* feat: reuse i18n

* feat: delete CheckCasService

* Update token_cas.go

* Update LoginPage.js

* Update token_cas.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-07-24 11:47:31 +08:00
UsherFall
8669d5bb0d chore: hide field of IntranetEndpoint in Tencent COS storage provider (#2117) 2023-07-23 19:02:42 +08:00
Baihhh
aee3ea4981 feat: improve TermsOfUse UI in mobile (#2106)
* style: Mobile interface adaptation

Signed-off-by: baihhh <2542274498@qq.com>

* Update index.css

---------

Signed-off-by: baihhh <2542274498@qq.com>
Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-07-23 15:28:13 +08:00
Yang Luo
516f4b7569 Fix response of /api/get-sorted-users and /api/get-user-count 2023-07-23 14:46:38 +08:00
UsherFall
7d7ca10481 fix: hide fields of minio storage provider (#2115)
* feat: hide field of minio storage provider

* feat: hide field of domain in minio storage provider
2023-07-23 14:40:30 +08:00
UsherFall
a9d4978a0f chore: hide fields of local file system storage provider (#2109)
* style: adjust local file system storage

* style: disable domain when use local file system
2023-07-23 11:48:15 +08:00
Yang Luo
09f40bb5ce Fix id of "/api/get-resource" API 2023-07-23 11:33:48 +08:00
Yaodong Yu
a6f803aff1 feat: refactor code to use responseOK everywhere (#2111)
* refactor: use responseOK return frontend format json data

* revert handle error

* revert handle error
2023-07-23 09:49:16 +08:00
Yang Luo
fc9528be43 Add createDatabaseForPostgres() 2023-07-22 16:19:13 +08:00
imp2002
58e8f9f90b feat: fix Effect in Casbin rule (#2103)
* fix: Add `Effect` to Casbin rule of role

fix: https://github.com/casdoor/casdoor/issues/2102

* Update permission_enforcer.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-07-21 18:01:37 +08:00
Yang Luo
e850e33f37 Fix error message of missing cert when login 2023-07-20 19:45:22 +08:00
haiwu
d7110ff8bf feat: support MetaMask provider (#2084)
* feat: add metamask provider

* feat: add eth login

* feat: check eth sign

* feat: finish metamask signin/signup

* feat: support MetaMask provider link/unlink

* feat: update web/craco.config.js to handle polyfill

* feat: gofumpt idp/metamask.go

* feat: update MetaMask logo path

* feat: support MetaMask avatar
2023-07-20 17:51:36 +08:00
f923a8f0d7 fix: provide detailed description of ldap in swagger (#2094)
* provide detailed description of ldap in swagger

* modify the directory of swagger

fix: provide detailed description of ldap in swagger
2023-07-20 12:32:48 +08:00
Yang Luo
7bfb74ba18 Fix typo 2023-07-19 19:34:43 +08:00
Yang Luo
38f031bc86 Show access secret if isAdminOrSelf is true in get-user and get-account APIs 2023-07-19 19:14:53 +08:00
Yang Luo
5c441d195c Add Effect to Casbin rule of add-permission 2023-07-19 18:52:22 +08:00
Yaodong Yu
0639564d27 fix: check group name cannot be same as organization name (#2090) 2023-07-19 11:37:28 +08:00
Yang Luo
6c647818ca feat: add "Sender number" input for Twilio SMS provider 2023-07-18 22:46:56 +08:00
Yaodong Yu
8bc73d17aa feat: fix bug that themeEditor can not load saved theme data (#2085) 2023-07-17 22:57:55 +08:00
117 changed files with 2942 additions and 653 deletions

View File

@@ -380,7 +380,8 @@ func (c *ApiController) GetAccount() {
return
}
u, err := object.GetMaskedUser(user)
isAdminOrSelf := c.IsAdminOrSelf(user)
u, err := object.GetMaskedUser(user, isAdminOrSelf)
if err != nil {
c.ResponseError(err.Error())
return

View File

@@ -48,14 +48,11 @@ func (c *ApiController) GetApplications() {
} else {
applications, err = object.GetOrganizationApplications(owner, organization)
}
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = object.GetMaskedApplications(applications, userId)
c.ServeJSON()
c.ResponseOk(object.GetMaskedApplications(applications, userId))
} else {
limit := util.ParseInt(limit)
count, err := object.GetApplicationCount(owner, field, value)
@@ -86,14 +83,14 @@ func (c *ApiController) GetApplications() {
func (c *ApiController) GetApplication() {
userId := c.GetSessionUsername()
id := c.Input().Get("id")
app, err := object.GetApplication(id)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = object.GetMaskedApplication(app, userId)
c.ServeJSON()
c.ResponseOk(object.GetMaskedApplication(app, userId))
}
// GetUserApplication
@@ -106,25 +103,24 @@ func (c *ApiController) GetApplication() {
func (c *ApiController) GetUserApplication() {
userId := c.GetSessionUsername()
id := c.Input().Get("id")
user, err := object.GetUser(id)
if err != nil {
c.ResponseError(err.Error())
return
}
if user == nil {
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), id))
return
}
app, err := object.GetApplicationByUser(user)
application, err := object.GetApplicationByUser(user)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = object.GetMaskedApplication(app, userId)
c.ServeJSON()
c.ResponseOk(object.GetMaskedApplication(application, userId))
}
// GetOrganizationApplications
@@ -157,8 +153,7 @@ func (c *ApiController) GetOrganizationApplications() {
return
}
c.Data["json"] = object.GetMaskedApplications(applications, userId)
c.ServeJSON()
c.ResponseOk(object.GetMaskedApplications(applications, userId))
} else {
limit := util.ParseInt(limit)

View File

@@ -422,7 +422,7 @@ func (c *ApiController) Login() {
c.ResponseError(err.Error())
return
}
} else if provider.Category == "OAuth" {
} else if provider.Category == "OAuth" || provider.Category == "Web3" {
// OAuth
idpInfo := object.FromProviderToIdpInfo(c.Ctx, provider)
idProvider := idp.GetIdProvider(idpInfo, authForm.RedirectUri)
@@ -465,7 +465,7 @@ func (c *ApiController) Login() {
c.ResponseError(err.Error())
return
}
} else if provider.Category == "OAuth" {
} else if provider.Category == "OAuth" || provider.Category == "Web3" {
user, err = object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
if err != nil {
c.ResponseError(err.Error())
@@ -486,7 +486,7 @@ func (c *ApiController) Login() {
record.Organization = application.Organization
record.User = user.Name
util.SafeGoroutine(func() { object.AddRecord(record) })
} else if provider.Category == "OAuth" {
} else if provider.Category == "OAuth" || provider.Category == "Web3" {
// Sign up via OAuth
if application.EnableLinkWithEmail {
if userInfo.Email != "" {

View File

@@ -55,6 +55,18 @@ func (c *ApiController) IsAdmin() bool {
return isGlobalAdmin || user.IsAdmin
}
func (c *ApiController) IsAdminOrSelf(user2 *object.User) bool {
isGlobalAdmin, user := c.isGlobalAdmin()
if isGlobalAdmin || (user != nil && user.IsAdmin) {
return true
}
if user.Owner == user2.Owner && user.Name == user2.Name {
return true
}
return false
}
func (c *ApiController) isGlobalAdmin() (bool, *object.User) {
username := c.GetSessionUsername()
if strings.HasPrefix(username, "app/") {

226
controllers/casbin_api.go Normal file
View File

@@ -0,0 +1,226 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import (
"encoding/json"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
// Enforce
// @Title Enforce
// @Tag Enforce API
// @Description Call Casbin Enforce API
// @Param body body object.CasbinRequest true "Casbin request"
// @Param permissionId query string false "permission id"
// @Param modelId query string false "model id"
// @Param resourceId query string false "resource id"
// @Success 200 {object} controllers.Response The Response object
// @router /enforce [post]
func (c *ApiController) Enforce() {
permissionId := c.Input().Get("permissionId")
modelId := c.Input().Get("modelId")
resourceId := c.Input().Get("resourceId")
var request object.CasbinRequest
err := json.Unmarshal(c.Ctx.Input.RequestBody, &request)
if err != nil {
c.ResponseError(err.Error())
return
}
if permissionId != "" {
permission, err := object.GetPermission(permissionId)
if err != nil {
c.ResponseError(err.Error())
return
}
res := []bool{}
if permission == nil {
res = append(res, false)
} else {
enforceResult, err := object.Enforce(permission, &request)
if err != nil {
c.ResponseError(err.Error())
return
}
res = append(res, enforceResult)
}
c.ResponseOk(res)
return
}
permissions := []*object.Permission{}
if modelId != "" {
owner, modelName := util.GetOwnerAndNameFromId(modelId)
permissions, err = object.GetPermissionsByModel(owner, modelName)
if err != nil {
c.ResponseError(err.Error())
return
}
} else if resourceId != "" {
permissions, err = object.GetPermissionsByResource(resourceId)
if err != nil {
c.ResponseError(err.Error())
return
}
} else {
c.ResponseError(c.T("general:Missing parameter"))
return
}
res := []bool{}
listPermissionIdMap := object.GroupPermissionsByModelAdapter(permissions)
for _, permissionIds := range listPermissionIdMap {
firstPermission, err := object.GetPermission(permissionIds[0])
if err != nil {
c.ResponseError(err.Error())
return
}
enforceResult, err := object.Enforce(firstPermission, &request, permissionIds...)
if err != nil {
c.ResponseError(err.Error())
return
}
res = append(res, enforceResult)
}
c.ResponseOk(res)
}
// BatchEnforce
// @Title BatchEnforce
// @Tag Enforce API
// @Description Call Casbin BatchEnforce API
// @Param body body object.CasbinRequest true "array of casbin requests"
// @Param permissionId query string false "permission id"
// @Param modelId query string false "model id"
// @Success 200 {object} controllers.Response The Response object
// @router /batch-enforce [post]
func (c *ApiController) BatchEnforce() {
permissionId := c.Input().Get("permissionId")
modelId := c.Input().Get("modelId")
var requests []object.CasbinRequest
err := json.Unmarshal(c.Ctx.Input.RequestBody, &requests)
if err != nil {
c.ResponseError(err.Error())
return
}
if permissionId != "" {
permission, err := object.GetPermission(permissionId)
if err != nil {
c.ResponseError(err.Error())
return
}
res := [][]bool{}
if permission == nil {
l := len(requests)
resRequest := make([]bool, l)
for i := 0; i < l; i++ {
resRequest[i] = false
}
res = append(res, resRequest)
} else {
enforceResult, err := object.BatchEnforce(permission, &requests)
if err != nil {
c.ResponseError(err.Error())
return
}
res = append(res, enforceResult)
}
c.ResponseOk(res)
return
}
permissions := []*object.Permission{}
if modelId != "" {
owner, modelName := util.GetOwnerAndNameFromId(modelId)
permissions, err = object.GetPermissionsByModel(owner, modelName)
if err != nil {
c.ResponseError(err.Error())
return
}
} else {
c.ResponseError(c.T("general:Missing parameter"))
return
}
res := [][]bool{}
listPermissionIdMap := object.GroupPermissionsByModelAdapter(permissions)
for _, permissionIds := range listPermissionIdMap {
firstPermission, err := object.GetPermission(permissionIds[0])
if err != nil {
c.ResponseError(err.Error())
return
}
enforceResult, err := object.BatchEnforce(firstPermission, &requests, permissionIds...)
if err != nil {
c.ResponseError(err.Error())
return
}
res = append(res, enforceResult)
}
c.ResponseOk(res)
}
func (c *ApiController) GetAllObjects() {
userId := c.GetSessionUsername()
if userId == "" {
c.ResponseError(c.T("general:Please login first"))
return
}
c.ResponseOk(object.GetAllObjects(userId))
}
func (c *ApiController) GetAllActions() {
userId := c.GetSessionUsername()
if userId == "" {
c.ResponseError(c.T("general:Please login first"))
return
}
c.ResponseOk(object.GetAllActions(userId))
}
func (c *ApiController) GetAllRoles() {
userId := c.GetSessionUsername()
if userId == "" {
c.ResponseError(c.T("general:Please login first"))
return
}
c.ResponseOk(object.GetAllRoles(userId))
}

View File

@@ -45,8 +45,7 @@ func (c *ApiController) GetCerts() {
return
}
c.Data["json"] = maskedCerts
c.ServeJSON()
c.ResponseOk(maskedCerts)
} else {
limit := util.ParseInt(limit)
count, err := object.GetCertCount(owner, field, value)
@@ -87,8 +86,7 @@ func (c *ApiController) GetGlobleCerts() {
return
}
c.Data["json"] = maskedCerts
c.ServeJSON()
c.ResponseOk(maskedCerts)
} else {
limit := util.ParseInt(limit)
count, err := object.GetGlobalCertsCount(field, value)
@@ -123,8 +121,7 @@ func (c *ApiController) GetCert() {
return
}
c.Data["json"] = object.GetMaskedCert(cert)
c.ServeJSON()
c.ResponseOk(object.GetMaskedCert(cert))
}
// UpdateCert

View File

@@ -45,8 +45,7 @@ func (c *ApiController) GetChats() {
return
}
c.Data["json"] = maskedChats
c.ServeJSON()
c.ResponseOk(maskedChats)
} else {
limit := util.ParseInt(limit)
count, err := object.GetChatCount(owner, field, value)
@@ -82,8 +81,7 @@ func (c *ApiController) GetChat() {
return
}
c.Data["json"] = maskedChat
c.ServeJSON()
c.ResponseOk(maskedChat)
}
// UpdateChat

View File

@@ -1,4 +1,4 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -17,210 +17,129 @@ package controllers
import (
"encoding/json"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
// Enforce
// @Title Enforce
// @Tag Enforce API
// @Description Call Casbin Enforce API
// @Param body body object.CasbinRequest true "Casbin request"
// @Param permissionId query string false "permission id"
// @Param modelId query string false "model id"
// @Param resourceId query string false "resource id"
// @Success 200 {object} controllers.Response The Response object
// @router /enforce [post]
func (c *ApiController) Enforce() {
permissionId := c.Input().Get("permissionId")
modelId := c.Input().Get("modelId")
resourceId := c.Input().Get("resourceId")
// GetEnforcers
// @Title GetEnforcers
// @Tag Enforcer API
// @Description get enforcers
// @Param owner query string true "The owner of enforcers"
// @Success 200 {array} object.Enforcer
// @router /get-enforcers [get]
func (c *ApiController) GetEnforcers() {
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")
var request object.CasbinRequest
err := json.Unmarshal(c.Ctx.Input.RequestBody, &request)
if limit == "" || page == "" {
enforcers, err := object.GetEnforcers(owner)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(enforcers)
} else {
limit := util.ParseInt(limit)
count, err := object.GetEnforcerCount(owner, field, value)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
enforcers, err := object.GetPaginationEnforcers(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(enforcers)
}
}
// GetEnforcer
// @Title GetEnforcer
// @Tag Enforcer API
// @Description get enforcer
// @Param id query string true "The id ( owner/name ) of enforcer"
// @Success 200 {object} object
// @router /get-enforcer [get]
func (c *ApiController) GetEnforcer() {
id := c.Input().Get("id")
enforcer, err := object.GetEnforcer(id)
if err != nil {
c.ResponseError(err.Error())
return
}
if permissionId != "" {
permission, err := object.GetPermission(permissionId)
if err != nil {
c.ResponseError(err.Error())
return
}
res := []bool{}
if permission == nil {
res = append(res, false)
} else {
enforceResult, err := object.Enforce(permission, &request)
if err != nil {
c.ResponseError(err.Error())
return
}
res = append(res, enforceResult)
}
c.ResponseOk(res)
return
}
permissions := []*object.Permission{}
if modelId != "" {
owner, modelName := util.GetOwnerAndNameFromId(modelId)
permissions, err = object.GetPermissionsByModel(owner, modelName)
if err != nil {
c.ResponseError(err.Error())
return
}
} else if resourceId != "" {
permissions, err = object.GetPermissionsByResource(resourceId)
if err != nil {
c.ResponseError(err.Error())
return
}
} else {
c.ResponseError(c.T("general:Missing parameter"))
return
}
res := []bool{}
listPermissionIdMap := object.GroupPermissionsByModelAdapter(permissions)
for _, permissionIds := range listPermissionIdMap {
firstPermission, err := object.GetPermission(permissionIds[0])
if err != nil {
c.ResponseError(err.Error())
return
}
enforceResult, err := object.Enforce(firstPermission, &request, permissionIds...)
if err != nil {
c.ResponseError(err.Error())
return
}
res = append(res, enforceResult)
}
c.ResponseOk(res)
c.ResponseOk(enforcer)
}
// BatchEnforce
// @Title BatchEnforce
// @Tag Enforce API
// @Description Call Casbin BatchEnforce API
// @Param body body object.CasbinRequest true "array of casbin requests"
// @Param permissionId query string false "permission id"
// @Param modelId query string false "model id"
// @Success 200 {object} controllers.Response The Response object
// @router /batch-enforce [post]
func (c *ApiController) BatchEnforce() {
permissionId := c.Input().Get("permissionId")
modelId := c.Input().Get("modelId")
// UpdateEnforcer
// @Title UpdateEnforcer
// @Tag Enforcer API
// @Description update enforcer
// @Param id query string true "The id ( owner/name ) of enforcer"
// @Param enforcer body object true "The enforcer object"
// @Success 200 {object} object
// @router /update-enforcer [post]
func (c *ApiController) UpdateEnforcer() {
id := c.Input().Get("id")
var requests []object.CasbinRequest
err := json.Unmarshal(c.Ctx.Input.RequestBody, &requests)
enforcer := object.Enforcer{}
err := json.Unmarshal(c.Ctx.Input.RequestBody, &enforcer)
if err != nil {
c.ResponseError(err.Error())
return
}
if permissionId != "" {
permission, err := object.GetPermission(permissionId)
if err != nil {
c.ResponseError(err.Error())
return
}
res := [][]bool{}
if permission == nil {
l := len(requests)
resRequest := make([]bool, l)
for i := 0; i < l; i++ {
resRequest[i] = false
}
res = append(res, resRequest)
} else {
enforceResult, err := object.BatchEnforce(permission, &requests)
if err != nil {
c.ResponseError(err.Error())
return
}
res = append(res, enforceResult)
}
c.ResponseOk(res)
return
}
permissions := []*object.Permission{}
if modelId != "" {
owner, modelName := util.GetOwnerAndNameFromId(modelId)
permissions, err = object.GetPermissionsByModel(owner, modelName)
if err != nil {
c.ResponseError(err.Error())
return
}
} else {
c.ResponseError(c.T("general:Missing parameter"))
return
}
res := [][]bool{}
listPermissionIdMap := object.GroupPermissionsByModelAdapter(permissions)
for _, permissionIds := range listPermissionIdMap {
firstPermission, err := object.GetPermission(permissionIds[0])
if err != nil {
c.ResponseError(err.Error())
return
}
enforceResult, err := object.BatchEnforce(firstPermission, &requests, permissionIds...)
if err != nil {
c.ResponseError(err.Error())
return
}
res = append(res, enforceResult)
}
c.ResponseOk(res)
c.Data["json"] = wrapActionResponse(object.UpdateEnforcer(id, &enforcer))
c.ServeJSON()
}
func (c *ApiController) GetAllObjects() {
userId := c.GetSessionUsername()
if userId == "" {
c.ResponseError(c.T("general:Please login first"))
// AddEnforcer
// @Title AddEnforcer
// @Tag Enforcer API
// @Description add enforcer
// @Param enforcer body object true "The enforcer object"
// @Success 200 {object} object
// @router /add-enforcer [post]
func (c *ApiController) AddEnforcer() {
enforcer := object.Enforcer{}
err := json.Unmarshal(c.Ctx.Input.RequestBody, &enforcer)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(object.GetAllObjects(userId))
c.Data["json"] = wrapActionResponse(object.AddEnforcer(&enforcer))
c.ServeJSON()
}
func (c *ApiController) GetAllActions() {
userId := c.GetSessionUsername()
if userId == "" {
c.ResponseError(c.T("general:Please login first"))
// DeleteEnforcer
// @Title DeleteEnforcer
// @Tag Enforcer API
// @Description delete enforcer
// @Param body body object.Enforce true "The enforcer object"
// @Success 200 {object} object
// @router /delete-enforcer [post]
func (c *ApiController) DeleteEnforcer() {
var enforcer object.Enforcer
err := json.Unmarshal(c.Ctx.Input.RequestBody, &enforcer)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(object.GetAllActions(userId))
}
func (c *ApiController) GetAllRoles() {
userId := c.GetSessionUsername()
if userId == "" {
c.ResponseError(c.T("general:Please login first"))
return
}
c.ResponseOk(object.GetAllRoles(userId))
c.Data["json"] = wrapActionResponse(object.DeleteEnforcer(&enforcer))
c.ServeJSON()
}

View File

@@ -82,9 +82,9 @@ func (c *ApiController) GetGroup() {
group, err := object.GetGroup(id)
if err != nil {
c.ResponseError(err.Error())
} else {
c.ResponseOk(group)
return
}
c.ResponseOk(group)
}
// UpdateGroup

View File

@@ -38,8 +38,11 @@ type LdapSyncResp struct {
}
// GetLdapUsers
// @Tag Account API
// @Title GetLdapser
// @Tag Account API
// @Description get ldap users
// Param id string true "id"
// @Success 200 {object} LdapResp The Response object
// @router /get-ldap-users [get]
func (c *ApiController) GetLdapUsers() {
id := c.Input().Get("id")
@@ -94,8 +97,11 @@ func (c *ApiController) GetLdapUsers() {
}
// GetLdaps
// @Tag Account API
// @Title GetLdaps
// @Tag Account API
// @Description get ldaps
// @Param owner query string false "owner"
// @Success 200 {array} object.Ldap The Response object
// @router /get-ldaps [get]
func (c *ApiController) GetLdaps() {
owner := c.Input().Get("owner")
@@ -104,8 +110,11 @@ func (c *ApiController) GetLdaps() {
}
// GetLdap
// @Tag Account API
// @Title GetLdap
// @Tag Account API
// @Description get ldap
// @Param id query string true "id"
// @Success 200 {object} object.Ldap The Response object
// @router /get-ldap [get]
func (c *ApiController) GetLdap() {
id := c.Input().Get("id")
@@ -116,12 +125,20 @@ func (c *ApiController) GetLdap() {
}
_, name := util.GetOwnerAndNameFromId(id)
c.ResponseOk(object.GetMaskedLdap(object.GetLdap(name)))
ldap, err := object.GetLdap(name)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(object.GetMaskedLdap(ldap))
}
// AddLdap
// @Tag Account API
// @Title AddLdap
// @Tag Account API
// @Description add ldap
// @Param body body object.Ldap true "The details of the ldap"
// @Success 200 {object} controllers.Response The Response object
// @router /add-ldap [post]
func (c *ApiController) AddLdap() {
var ldap object.Ldap
@@ -160,8 +177,11 @@ func (c *ApiController) AddLdap() {
}
// UpdateLdap
// @Tag Account API
// @Title UpdateLdap
// @Tag Account API
// @Description update ldap
// @Param body body object.Ldap true "The details of the ldap"
// @Success 200 {object} controllers.Response The Response object
// @router /update-ldap [post]
func (c *ApiController) UpdateLdap() {
var ldap object.Ldap
@@ -198,8 +218,11 @@ func (c *ApiController) UpdateLdap() {
}
// DeleteLdap
// @Tag Account API
// @Title DeleteLdap
// @Tag Account API
// @Description delete ldap
// @Param body body object.Ldap true "The details of the ldap"
// @Success 200 {object} controllers.Response The Response object
// @router /delete-ldap [post]
func (c *ApiController) DeleteLdap() {
var ldap object.Ldap
@@ -222,8 +245,11 @@ func (c *ApiController) DeleteLdap() {
}
// SyncLdapUsers
// @Tag Account API
// @Title SyncLdapUsers
// @Tag Account API
// @Description sync ldap users
// @Param id query string true "id"
// @Success 200 {object} LdapSyncResp The Response object
// @router /sync-ldap-users [post]
func (c *ApiController) SyncLdapUsers() {
id := c.Input().Get("id")

View File

@@ -57,8 +57,7 @@ func (c *ApiController) GetMessages() {
return
}
c.Data["json"] = object.GetMaskedMessages(messages)
c.ServeJSON()
c.ResponseOk(object.GetMaskedMessages(messages))
} else {
limit := util.ParseInt(limit)
count, err := object.GetMessageCount(owner, organization, field, value)
@@ -94,8 +93,7 @@ func (c *ApiController) GetMessage() {
return
}
c.Data["json"] = object.GetMaskedMessage(message)
c.ServeJSON()
c.ResponseOk(message)
}
func (c *ApiController) ResponseErrorStream(errorText string) {

View File

@@ -45,8 +45,7 @@ func (c *ApiController) GetModels() {
return
}
c.Data["json"] = models
c.ServeJSON()
c.ResponseOk(models)
} else {
limit := util.ParseInt(limit)
count, err := object.GetModelCount(owner, field, value)
@@ -82,8 +81,7 @@ func (c *ApiController) GetModel() {
return
}
c.Data["json"] = model
c.ServeJSON()
c.ResponseOk(model)
}
// UpdateModel

View File

@@ -55,8 +55,7 @@ func (c *ApiController) GetOrganizations() {
return
}
c.Data["json"] = maskedOrganizations
c.ServeJSON()
c.ResponseOk(maskedOrganizations)
} else {
if !isGlobalAdmin {
maskedOrganizations, err := object.GetMaskedOrganizations(object.GetOrganizations(owner, c.getCurrentUser().Owner))
@@ -184,6 +183,8 @@ func (c *ApiController) DeleteOrganization() {
func (c *ApiController) GetDefaultApplication() {
userId := c.GetSessionUsername()
id := c.Input().Get("id")
redirectUri := c.Input().Get("redirectUri")
typ := c.Input().Get("type")
application, err := object.GetDefaultApplication(id)
if err != nil {
@@ -191,6 +192,14 @@ func (c *ApiController) GetDefaultApplication() {
return
}
if typ == "cas" {
err = object.CheckCasRestrict(application, c.GetAcceptLanguage(), redirectUri)
if err != nil {
c.ResponseError(err.Error())
return
}
}
maskedApplication := object.GetMaskedApplication(application, userId)
c.ResponseOk(maskedApplication)
}

View File

@@ -46,8 +46,7 @@ func (c *ApiController) GetPayments() {
return
}
c.Data["json"] = payments
c.ServeJSON()
c.ResponseOk(payments)
} else {
limit := util.ParseInt(limit)
count, err := object.GetPaymentCount(owner, organization, field, value)
@@ -106,8 +105,7 @@ func (c *ApiController) GetPayment() {
return
}
c.Data["json"] = payment
c.ServeJSON()
c.ResponseOk(payment)
}
// UpdatePayment

View File

@@ -45,8 +45,7 @@ func (c *ApiController) GetPermissions() {
return
}
c.Data["json"] = permissions
c.ServeJSON()
c.ResponseOk(permissions)
} else {
limit := util.ParseInt(limit)
count, err := object.GetPermissionCount(owner, field, value)
@@ -85,7 +84,6 @@ func (c *ApiController) GetPermissionsBySubmitter() {
}
c.ResponseOk(permissions, len(permissions))
return
}
// GetPermissionsByRole
@@ -104,7 +102,6 @@ func (c *ApiController) GetPermissionsByRole() {
}
c.ResponseOk(permissions, len(permissions))
return
}
// GetPermission
@@ -123,8 +120,7 @@ func (c *ApiController) GetPermission() {
return
}
c.Data["json"] = permission
c.ServeJSON()
c.ResponseOk(permission)
}
// UpdatePermission

View File

@@ -45,8 +45,7 @@ func (c *ApiController) GetPlans() {
return
}
c.Data["json"] = plans
c.ServeJSON()
c.ResponseOk(plans)
} else {
limit := util.ParseInt(limit)
count, err := object.GetPlanCount(owner, field, value)
@@ -95,11 +94,10 @@ func (c *ApiController) GetPlan() {
plan.Options = append(plan.Options, option.DisplayName)
}
c.Data["json"] = plan
c.ResponseOk(plan)
} else {
c.Data["json"] = plan
c.ResponseOk(plan)
}
c.ServeJSON()
}
// UpdatePlan

View File

@@ -45,8 +45,7 @@ func (c *ApiController) GetPricings() {
return
}
c.Data["json"] = pricings
c.ServeJSON()
c.ResponseOk(pricings)
} else {
limit := util.ParseInt(limit)
count, err := object.GetPricingCount(owner, field, value)
@@ -82,8 +81,7 @@ func (c *ApiController) GetPricing() {
return
}
c.Data["json"] = pricing
c.ServeJSON()
c.ResponseOk(pricing)
}
// UpdatePricing

View File

@@ -46,8 +46,7 @@ func (c *ApiController) GetProducts() {
return
}
c.Data["json"] = products
c.ServeJSON()
c.ResponseOk(products)
} else {
limit := util.ParseInt(limit)
count, err := object.GetProductCount(owner, field, value)
@@ -89,8 +88,7 @@ func (c *ApiController) GetProduct() {
return
}
c.Data["json"] = product
c.ServeJSON()
c.ResponseOk(product)
}
// UpdateProduct

View File

@@ -51,8 +51,7 @@ func (c *ApiController) GetRecords() {
return
}
c.Data["json"] = records
c.ServeJSON()
c.ResponseOk(records)
} else {
limit := util.ParseInt(limit)
if c.IsGlobalAdmin() && organizationName != "" {
@@ -99,8 +98,7 @@ func (c *ApiController) GetRecordsByFilter() {
return
}
c.Data["json"] = records
c.ServeJSON()
c.ResponseOk(records)
}
// AddRecord

View File

@@ -67,8 +67,7 @@ func (c *ApiController) GetResources() {
return
}
c.Data["json"] = resources
c.ServeJSON()
c.ResponseOk(resources)
} else {
limit := util.ParseInt(limit)
count, err := object.GetResourceCount(owner, user, field, value)
@@ -104,8 +103,7 @@ func (c *ApiController) GetResource() {
return
}
c.Data["json"] = resource
c.ServeJSON()
c.ResponseOk(resource)
}
// UpdateResource

View File

@@ -45,8 +45,7 @@ func (c *ApiController) GetRoles() {
return
}
c.Data["json"] = roles
c.ServeJSON()
c.ResponseOk(roles)
} else {
limit := util.ParseInt(limit)
count, err := object.GetRoleCount(owner, field, value)
@@ -82,8 +81,7 @@ func (c *ApiController) GetRole() {
return
}
c.Data["json"] = role
c.ServeJSON()
c.ResponseOk(role)
}
// UpdateRole

View File

@@ -45,8 +45,7 @@ func (c *ApiController) GetSessions() {
return
}
c.Data["json"] = sessions
c.ServeJSON()
c.ResponseOk(sessions)
} else {
limit := util.ParseInt(limit)
count, err := object.GetSessionCount(owner, field, value)
@@ -81,8 +80,7 @@ func (c *ApiController) GetSingleSession() {
return
}
c.Data["json"] = session
c.ServeJSON()
c.ResponseOk(session)
}
// UpdateSession
@@ -161,7 +159,5 @@ func (c *ApiController) IsSessionDuplicated() {
return
}
c.Data["json"] = &Response{Status: "ok", Msg: "", Data: isUserSessionDuplicated}
c.ServeJSON()
c.ResponseOk(isUserSessionDuplicated)
}

View File

@@ -45,8 +45,7 @@ func (c *ApiController) GetSubscriptions() {
return
}
c.Data["json"] = subscriptions
c.ServeJSON()
c.ResponseOk(subscriptions)
} else {
limit := util.ParseInt(limit)
count, err := object.GetSubscriptionCount(owner, field, value)
@@ -82,8 +81,7 @@ func (c *ApiController) GetSubscription() {
return
}
c.Data["json"] = subscription
c.ServeJSON()
c.ResponseOk(subscription)
}
// UpdateSubscription

View File

@@ -46,8 +46,7 @@ func (c *ApiController) GetSyncers() {
return
}
c.Data["json"] = organizationSyncers
c.ServeJSON()
c.ResponseOk(organizationSyncers)
} else {
limit := util.ParseInt(limit)
count, err := object.GetSyncerCount(owner, organization, field, value)
@@ -83,8 +82,7 @@ func (c *ApiController) GetSyncer() {
return
}
c.Data["json"] = syncer
c.ServeJSON()
c.ResponseOk(syncer)
}
// UpdateSyncer

View File

@@ -47,8 +47,7 @@ func (c *ApiController) GetTokens() {
return
}
c.Data["json"] = token
c.ServeJSON()
c.ResponseOk(token)
} else {
limit := util.ParseInt(limit)
count, err := object.GetTokenCount(owner, organization, field, value)
@@ -83,8 +82,7 @@ func (c *ApiController) GetToken() {
return
}
c.Data["json"] = token
c.ServeJSON()
c.ResponseOk(token)
}
// UpdateToken

View File

@@ -45,8 +45,7 @@ func (c *ApiController) GetGlobalUsers() {
return
}
c.Data["json"] = maskedUsers
c.ServeJSON()
c.ResponseOk(maskedUsers)
} else {
limit := util.ParseInt(limit)
count, err := object.GetGlobalUserCount(field, value)
@@ -106,8 +105,7 @@ func (c *ApiController) GetUsers() {
return
}
c.Data["json"] = maskedUsers
c.ServeJSON()
c.ResponseOk(maskedUsers)
} else {
limit := util.ParseInt(limit)
count, err := object.GetUserCount(owner, field, value, groupName)
@@ -208,14 +206,14 @@ func (c *ApiController) GetUser() {
return
}
maskedUser, err := object.GetMaskedUser(user)
isAdminOrSelf := c.IsAdminOrSelf(user)
maskedUser, err := object.GetMaskedUser(user, isAdminOrSelf)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = maskedUser
c.ServeJSON()
c.ResponseOk(maskedUser)
}
// UpdateUser
@@ -512,8 +510,7 @@ func (c *ApiController) GetSortedUsers() {
return
}
c.Data["json"] = maskedUsers
c.ServeJSON()
c.ResponseOk(maskedUsers)
}
// GetUserCount
@@ -540,8 +537,7 @@ func (c *ApiController) GetUserCount() {
return
}
c.Data["json"] = count
c.ServeJSON()
c.ResponseOk(count)
}
// AddUserkeys

View File

@@ -46,8 +46,7 @@ func (c *ApiController) GetWebhooks() {
return
}
c.Data["json"] = webhooks
c.ServeJSON()
c.ResponseOk(webhooks)
} else {
limit := util.ParseInt(limit)
count, err := object.GetWebhookCount(owner, organization, field, value)
@@ -84,8 +83,7 @@ func (c *ApiController) GetWebhook() {
return
}
c.Data["json"] = webhook
c.ServeJSON()
c.ResponseOk(webhook)
}
// UpdateWebhook

2
go.sum
View File

@@ -546,8 +546,6 @@ github.com/russellhaering/goxmldsig v1.2.0 h1:Y6GTTc9Un5hCxSzVz4UIWQ/zuVwDvzJk80
github.com/russellhaering/goxmldsig v1.2.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sashabaranov/go-openai v1.9.1 h1:3N52HkJKo9Zlo/oe1AVv5ZkCOny0ra58/ACvAxkN3MM=
github.com/sashabaranov/go-openai v1.9.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/sashabaranov/go-openai v1.12.0 h1:aRNHH0gtVfrpIaEolD0sWrLLRnYQNK4cH/bIAHwL8Rk=
github.com/sashabaranov/go-openai v1.12.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=

View File

@@ -21,15 +21,39 @@ import (
"fmt"
"io"
"net/http"
"strings"
"time"
"github.com/casdoor/casdoor/util"
"golang.org/x/oauth2"
)
const GoogleIdTokenKey = "GoogleIdToken"
type GoogleIdProvider struct {
Client *http.Client
Config *oauth2.Config
}
// https://developers.google.com/identity/sign-in/web/backend-auth#calling-the-tokeninfo-endpoint
type GoogleIdToken struct {
// These six fields are included in all Google ID Tokens.
Iss string `json:"iss"` // The issuer, or signer, of the token. For Google-signed ID tokens, this value is https://accounts.google.com.
Sub string `json:"sub"` // The subject: the ID that represents the principal making the request.
Azp string `json:"azp"` // Optional. Who the token was issued to. Here is the ClientID
Aud string `json:"aud"` // The audience of the token. Here is the ClientID
Iat string `json:"iat"` // Unix epoch time when the token was issued.
Exp string `json:"exp"` // Unix epoch time when the token expires.
// These seven fields are only included when the user has granted the "profile" and "email" OAuth scopes to the application.
Email string `json:"email"`
EmailVerified string `json:"email_verified"`
Name string `json:"name"`
Picture string `json:"picture"`
GivenName string `json:"given_name"`
FamilyName string `json:"family_name"`
Locale string `json:"locale"`
}
func NewGoogleIdProvider(clientId string, clientSecret string, redirectUrl string) *GoogleIdProvider {
idp := &GoogleIdProvider{}
@@ -61,6 +85,25 @@ func (idp *GoogleIdProvider) getConfig() *oauth2.Config {
}
func (idp *GoogleIdProvider) GetToken(code string) (*oauth2.Token, error) {
// Obtained the GoogleIdToken through Google OneTap authorization.
if strings.HasPrefix(code, GoogleIdTokenKey) {
code = strings.TrimPrefix(code, GoogleIdTokenKey+"-")
var googleIdToken GoogleIdToken
if err := json.Unmarshal([]byte(code), &googleIdToken); err != nil {
return nil, err
}
expiry := int64(util.ParseInt(googleIdToken.Exp))
token := &oauth2.Token{
AccessToken: fmt.Sprintf("%v-%v", GoogleIdTokenKey, googleIdToken.Sub),
TokenType: "Bearer",
Expiry: time.Unix(expiry, 0),
}
token = token.WithExtra(map[string]interface{}{
GoogleIdTokenKey: googleIdToken,
})
return token, nil
}
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, idp.Client)
return idp.Config.Exchange(ctx, code)
}
@@ -88,6 +131,20 @@ type GoogleUserInfo struct {
}
func (idp *GoogleIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
if strings.HasPrefix(token.AccessToken, GoogleIdTokenKey) {
googleIdToken, ok := token.Extra(GoogleIdTokenKey).(GoogleIdToken)
if !ok {
return nil, errors.New("invalid googleIdToken")
}
userInfo := UserInfo{
Id: googleIdToken.Sub,
Username: googleIdToken.Email,
DisplayName: googleIdToken.Name,
Email: googleIdToken.Email,
AvatarUrl: googleIdToken.Picture,
}
return &userInfo, nil
}
url := fmt.Sprintf("https://www.googleapis.com/oauth2/v2/userinfo?alt=json&access_token=%s", token.AccessToken)
resp, err := idp.Client.Get(url)
if err != nil {

80
idp/metamask.go Normal file
View File

@@ -0,0 +1,80 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package idp
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"time"
"golang.org/x/oauth2"
)
const Web3AuthTokenKey = "web3AuthToken"
type MetaMaskIdProvider struct {
Client *http.Client
}
type Web3AuthToken struct {
Address string `json:"address"`
Nonce string `json:"nonce"`
CreateAt uint64 `json:"createAt"`
TypedData string `json:"typedData"`
Signature string `json:"signature"` // signature for typed data
}
func NewMetaMaskIdProvider() *MetaMaskIdProvider {
idp := &MetaMaskIdProvider{}
return idp
}
func (idp *MetaMaskIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
func (idp *MetaMaskIdProvider) GetToken(code string) (*oauth2.Token, error) {
web3AuthToken := Web3AuthToken{}
if err := json.Unmarshal([]byte(code), &web3AuthToken); err != nil {
return nil, err
}
token := &oauth2.Token{
AccessToken: web3AuthToken.Signature,
TokenType: "Bearer",
Expiry: time.Now().AddDate(0, 1, 0),
}
token = token.WithExtra(map[string]interface{}{
Web3AuthTokenKey: web3AuthToken,
})
return token, nil
}
func (idp *MetaMaskIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
// TODO use "github.com/ethereum/go-ethereum" to check address's eth balance or transaction
web3AuthToken, ok := token.Extra(Web3AuthTokenKey).(Web3AuthToken)
if !ok {
return nil, errors.New("invalid web3AuthToken")
}
userInfo := &UserInfo{
Id: web3AuthToken.Address,
Username: web3AuthToken.Address,
DisplayName: web3AuthToken.Address,
AvatarUrl: fmt.Sprintf("metamask:%v", web3AuthToken.Address),
}
return userInfo, nil
}

View File

@@ -109,6 +109,8 @@ func GetIdProvider(idpInfo *ProviderInfo, redirectUrl string) IdProvider {
return NewDouyinIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
case "Bilibili":
return NewBilibiliIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
case "MetaMask":
return NewMetaMaskIdProvider()
default:
if isGothSupport(idpInfo.Type) {
return NewGothIdProvider(idpInfo.Type, idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl)

View File

@@ -34,7 +34,7 @@ func StartLdapServer() {
server.Handle(routes)
err := server.ListenAndServe("0.0.0.0:" + conf.GetConfigString("ldapServerPort"))
if err != nil {
return
log.Printf("StartLdapServer() failed, ErrMsg = %s", err.Error())
}
}

View File

@@ -39,7 +39,7 @@ func getCreateDatabaseFlag() bool {
func main() {
createDatabase := getCreateDatabaseFlag()
object.InitAdapter()
object.InitAdapter(createDatabase)
object.CreateTables(createDatabase)
object.DoMigration()

View File

@@ -15,8 +15,10 @@
package object
import (
"database/sql"
"fmt"
"runtime"
"strings"
"github.com/beego/beego"
"github.com/casdoor/casdoor/conf"
@@ -40,12 +42,19 @@ func InitConfig() {
beego.BConfig.WebConfig.Session.SessionOn = true
InitAdapter()
InitAdapter(true)
CreateTables(true)
DoMigration()
}
func InitAdapter() {
func InitAdapter(createDatabase bool) {
if createDatabase {
err := createDatabaseForPostgres(conf.GetConfigString("driverName"), conf.GetConfigDataSourceName(), conf.GetConfigString("dbName"))
if err != nil {
panic(err)
}
}
adapter = NewAdapter(conf.GetConfigString("driverName"), conf.GetConfigDataSourceName(), conf.GetConfigString("dbName"))
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
@@ -96,7 +105,32 @@ func NewAdapter(driverName string, dataSourceName string, dbName string) *Adapte
return a
}
func createDatabaseForPostgres(driverName string, dataSourceName string, dbName string) error {
if driverName == "postgres" {
db, err := sql.Open(driverName, dataSourceName)
if err != nil {
return err
}
defer db.Close()
_, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s;", dbName))
if err != nil {
if !strings.Contains(err.Error(), "already exists") {
return err
}
}
return nil
} else {
return nil
}
}
func (a *Adapter) CreateDatabase() error {
if a.driverName == "postgres" {
return nil
}
engine, err := xorm.NewEngine(a.driverName, a.dataSourceName)
if err != nil {
return err
@@ -165,6 +199,11 @@ func (a *Adapter) createTable() {
panic(err)
}
err = a.Engine.Sync2(new(Enforcer))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Provider))
if err != nil {
panic(err)

View File

@@ -100,6 +100,13 @@ func UpdateCasbinAdapter(id string, casbinAdapter *CasbinAdapter) (bool, error)
return false, err
}
if name != casbinAdapter.Name {
err := casbinAdapterChangeTrigger(name, casbinAdapter.Name)
if err != nil {
return false, err
}
}
session := adapter.Engine.ID(core.PK{owner, name}).AllCols()
if casbinAdapter.Password == "***" {
session.Omit("password")
@@ -180,6 +187,26 @@ func initEnforcer(modelObj *Model, casbinAdapter *CasbinAdapter) (*casbin.Enforc
return enforcer, nil
}
func casbinAdapterChangeTrigger(oldName string, newName string) error {
session := adapter.Engine.NewSession()
defer session.Close()
err := session.Begin()
if err != nil {
return err
}
enforcer := new(Enforcer)
enforcer.Adapter = newName
_, err = session.Where("adapter=?", oldName).Update(enforcer)
if err != nil {
session.Rollback()
return err
}
return session.Commit()
}
func safeReturn(policy []string, i int) string {
if len(policy) > i {
return policy[i]

View File

@@ -365,7 +365,7 @@ func CheckAccessPermission(userId string, application *Application) (bool, error
if containsAsterisk {
return true, err
}
enforcer := getEnforcer(permission)
enforcer := getPermissionEnforcer(permission)
if allowed, err = enforcer.Enforce(userId, application.Name, "read"); allowed {
return allowed, err
}

119
object/enforcer.go Normal file
View File

@@ -0,0 +1,119 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"github.com/casbin/casbin/v2"
"github.com/casdoor/casdoor/util"
"github.com/xorm-io/core"
)
type Enforcer 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"`
UpdatedTime string `xorm:"varchar(100) updated" json:"updatedTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Description string `xorm:"varchar(100)" json:"description"`
Model string `xorm:"varchar(100)" json:"model"`
Adapter string `xorm:"varchar(100)" json:"adapter"`
IsEnabled bool `json:"isEnabled"`
*casbin.Enforcer
}
func GetEnforcerCount(owner, field, value string) (int64, error) {
session := GetSession(owner, -1, -1, field, value, "", "")
return session.Count(&Enforcer{})
}
func GetEnforcers(owner string) ([]*Enforcer, error) {
enforcers := []*Enforcer{}
err := adapter.Engine.Desc("created_time").Find(&enforcers, &Enforcer{Owner: owner})
if err != nil {
return enforcers, err
}
return enforcers, nil
}
func GetPaginationEnforcers(owner string, offset, limit int, field, value, sortField, sortOrder string) ([]*Enforcer, error) {
enforcers := []*Enforcer{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&enforcers)
if err != nil {
return enforcers, err
}
return enforcers, nil
}
func getEnforcer(owner string, name string) (*Enforcer, error) {
if owner == "" || name == "" {
return nil, nil
}
enforcer := Enforcer{Owner: owner, Name: name}
existed, err := adapter.Engine.Get(&enforcer)
if err != nil {
return &enforcer, err
}
if existed {
return &enforcer, nil
} else {
return nil, nil
}
}
func GetEnforcer(id string) (*Enforcer, error) {
owner, name := util.GetOwnerAndNameFromId(id)
return getEnforcer(owner, name)
}
func UpdateEnforcer(id string, enforcer *Enforcer) (bool, error) {
owner, name := util.GetOwnerAndNameFromId(id)
if oldEnforcer, err := getEnforcer(owner, name); err != nil {
return false, err
} else if oldEnforcer == nil {
return false, nil
}
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(enforcer)
if err != nil {
return false, err
}
return affected != 0, nil
}
func AddEnforcer(enforcer *Enforcer) (bool, error) {
affected, err := adapter.Engine.Insert(enforcer)
if err != nil {
return false, err
}
return affected != 0, nil
}
func DeleteEnforcer(enforcer *Enforcer) (bool, error) {
affected, err := adapter.Engine.ID(core.PK{enforcer.Owner, enforcer.Name}).Delete(&Enforcer{})
if err != nil {
return false, err
}
return affected != 0, nil
}

View File

@@ -107,6 +107,11 @@ func UpdateGroup(id string, group *Group) (bool, error) {
return false, err
}
err = checkGroupName(group.Name)
if err != nil {
return false, err
}
if name != group.Name {
err := GroupChangeTrigger(name, group.Name)
if err != nil {
@@ -123,6 +128,11 @@ func UpdateGroup(id string, group *Group) (bool, error) {
}
func AddGroup(group *Group) (bool, error) {
err := checkGroupName(group.Name)
if err != nil {
return false, err
}
affected, err := adapter.Engine.Insert(group)
if err != nil {
return false, err
@@ -168,6 +178,17 @@ func DeleteGroup(group *Group) (bool, error) {
return affected != 0, nil
}
func checkGroupName(name string) error {
exist, err := adapter.Engine.Exist(&Organization{Owner: "admin", Name: name})
if err != nil {
return err
}
if exist {
return errors.New("group name can't be same as the organization name")
}
return nil
}
func (group *Group) GetId() string {
return fmt.Sprintf("%s/%s", group.Owner, group.Name)
}

View File

@@ -154,6 +154,15 @@ func modelChangeTrigger(oldName string, newName string) error {
permission.Model = newName
_, err = session.Where("model=?", oldName).Update(permission)
if err != nil {
session.Rollback()
return err
}
enforcer := new(Enforcer)
enforcer.Model = newName
_, err = session.Where("model=?", oldName).Update(enforcer)
if err != nil {
session.Rollback()
return err
}
@@ -161,5 +170,8 @@ func modelChangeTrigger(oldName string, newName string) error {
}
func HasRoleDefinition(m model.Model) bool {
if m == nil {
return false
}
return m["g"] != nil
}

View File

@@ -118,7 +118,7 @@ func GetPermission(id string) (*Permission, error) {
// checkPermissionValid verifies if the permission is valid
func checkPermissionValid(permission *Permission) error {
enforcer := getEnforcer(permission)
enforcer := getPermissionEnforcer(permission)
enforcer.EnableAutoSave(false)
policies := getPolicies(permission)

View File

@@ -26,42 +26,7 @@ import (
xormadapter "github.com/casdoor/xorm-adapter/v3"
)
func getEnforcer(permission *Permission, permissionIDs ...string) *casbin.Enforcer {
tableName := "permission_rule"
if len(permission.Adapter) != 0 {
adapterObj, err := getCasbinAdapter(permission.Owner, permission.Adapter)
if err != nil {
panic(err)
}
if adapterObj != nil && adapterObj.Table != "" {
tableName = adapterObj.Table
}
}
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
driverName := conf.GetConfigString("driverName")
dataSourceName := conf.GetConfigRealDataSourceName(driverName)
adapter, err := xormadapter.NewAdapterWithTableName(driverName, dataSourceName, tableName, tableNamePrefix, true)
if err != nil {
panic(err)
}
permissionModel, err := getModel(permission.Owner, permission.Model)
if err != nil {
panic(err)
}
m := model.Model{}
if permissionModel != nil {
m, err = GetBuiltInModel(permissionModel.ModelText)
} else {
m, err = GetBuiltInModel("")
}
if err != nil {
panic(err)
}
func getPermissionEnforcer(p *Permission, permissionIDs ...string) *casbin.Enforcer {
// Init an enforcer instance without specifying a model or adapter.
// If you specify an adapter, it will load all policies, which is a
// heavy process that can slow down the application.
@@ -70,14 +35,17 @@ func getEnforcer(permission *Permission, permissionIDs ...string) *casbin.Enforc
panic(err)
}
err = enforcer.InitWithModelAndAdapter(m, nil)
err = p.setEnforcerModel(enforcer)
if err != nil {
panic(err)
}
enforcer.SetAdapter(adapter)
err = p.setEnforcerAdapter(enforcer)
if err != nil {
panic(err)
}
policyFilterV5 := []string{permission.GetId()}
policyFilterV5 := []string{p.GetId()}
if len(permissionIDs) != 0 {
policyFilterV5 = permissionIDs
}
@@ -86,7 +54,7 @@ func getEnforcer(permission *Permission, permissionIDs ...string) *casbin.Enforc
V5: policyFilterV5,
}
if !HasRoleDefinition(m) {
if !HasRoleDefinition(enforcer.GetModel()) {
policyFilter.Ptype = []string{"p"}
}
@@ -98,35 +66,70 @@ func getEnforcer(permission *Permission, permissionIDs ...string) *casbin.Enforc
return enforcer
}
func (p *Permission) setEnforcerAdapter(enforcer *casbin.Enforcer) error {
tableName := "permission_rule"
if len(p.Adapter) != 0 {
adapterObj, err := getCasbinAdapter(p.Owner, p.Adapter)
if err != nil {
return err
}
if adapterObj != nil && adapterObj.Table != "" {
tableName = adapterObj.Table
}
}
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
driverName := conf.GetConfigString("driverName")
dataSourceName := conf.GetConfigRealDataSourceName(driverName)
casbinAdapter, err := xormadapter.NewAdapterWithTableName(driverName, dataSourceName, tableName, tableNamePrefix, true)
if err != nil {
return err
}
enforcer.SetAdapter(casbinAdapter)
return nil
}
func (p *Permission) setEnforcerModel(enforcer *casbin.Enforcer) error {
permissionModel, err := getModel(p.Owner, p.Model)
if err != nil {
return err
}
// TODO: return error if permissionModel is nil.
m := model.Model{}
if permissionModel != nil {
m, err = GetBuiltInModel(permissionModel.ModelText)
} else {
m, err = GetBuiltInModel("")
}
if err != nil {
return err
}
err = enforcer.InitWithModelAndAdapter(m, nil)
if err != nil {
return err
}
return nil
}
func getPolicies(permission *Permission) [][]string {
var policies [][]string
permissionId := permission.GetId()
domainExist := len(permission.Domains) > 0
for _, user := range permission.Users {
usersAndRoles := append(permission.Users, permission.Roles...)
for _, userOrRole := range usersAndRoles {
for _, resource := range permission.Resources {
for _, action := range permission.Actions {
if domainExist {
for _, domain := range permission.Domains {
policies = append(policies, []string{user, domain, resource, strings.ToLower(action), "", permissionId})
policies = append(policies, []string{userOrRole, domain, resource, strings.ToLower(action), strings.ToLower(permission.Effect), permissionId})
}
} else {
policies = append(policies, []string{user, resource, strings.ToLower(action), "", "", permissionId})
}
}
}
}
for _, role := range permission.Roles {
for _, resource := range permission.Resources {
for _, action := range permission.Actions {
if domainExist {
for _, domain := range permission.Domains {
policies = append(policies, []string{role, domain, resource, strings.ToLower(action), "", permissionId})
}
} else {
policies = append(policies, []string{role, resource, strings.ToLower(action), "", "", permissionId})
policies = append(policies, []string{userOrRole, resource, strings.ToLower(action), strings.ToLower(permission.Effect), "", permissionId})
}
}
}
@@ -201,7 +204,7 @@ func getGroupingPolicies(permission *Permission) [][]string {
}
func addPolicies(permission *Permission) {
enforcer := getEnforcer(permission)
enforcer := getPermissionEnforcer(permission)
policies := getPolicies(permission)
_, err := enforcer.AddPolicies(policies)
@@ -211,7 +214,7 @@ func addPolicies(permission *Permission) {
}
func addGroupingPolicies(permission *Permission) {
enforcer := getEnforcer(permission)
enforcer := getPermissionEnforcer(permission)
groupingPolicies := getGroupingPolicies(permission)
if len(groupingPolicies) > 0 {
@@ -223,7 +226,7 @@ func addGroupingPolicies(permission *Permission) {
}
func removeGroupingPolicies(permission *Permission) {
enforcer := getEnforcer(permission)
enforcer := getPermissionEnforcer(permission)
groupingPolicies := getGroupingPolicies(permission)
if len(groupingPolicies) > 0 {
@@ -235,7 +238,7 @@ func removeGroupingPolicies(permission *Permission) {
}
func removePolicies(permission *Permission) {
enforcer := getEnforcer(permission)
enforcer := getPermissionEnforcer(permission)
policies := getPolicies(permission)
_, err := enforcer.RemovePolicies(policies)
@@ -247,12 +250,12 @@ func removePolicies(permission *Permission) {
type CasbinRequest = []interface{}
func Enforce(permission *Permission, request *CasbinRequest, permissionIds ...string) (bool, error) {
enforcer := getEnforcer(permission, permissionIds...)
enforcer := getPermissionEnforcer(permission, permissionIds...)
return enforcer.Enforce(*request...)
}
func BatchEnforce(permission *Permission, requests *[]CasbinRequest, permissionIds ...string) ([]bool, error) {
enforcer := getEnforcer(permission, permissionIds...)
enforcer := getPermissionEnforcer(permission, permissionIds...)
return enforcer.BatchEnforce(*requests)
}
@@ -273,7 +276,7 @@ func getAllValues(userId string, fn func(enforcer *casbin.Enforcer) []string) []
var values []string
for _, permission := range permissions {
enforcer := getEnforcer(permission)
enforcer := getPermissionEnforcer(permission)
values = append(values, fn(enforcer)...)
}
return values

View File

@@ -49,7 +49,7 @@ func (pi *ProviderItem) IsProviderVisible() bool {
if pi.Provider == nil {
return false
}
return pi.Provider.Category == "OAuth" || pi.Provider.Category == "SAML"
return pi.Provider.Category == "OAuth" || pi.Provider.Category == "SAML" || pi.Provider.Category == "Web3"
}
func (pi *ProviderItem) isProviderPrompted() bool {

View File

@@ -161,7 +161,8 @@ func SendWebhooks(record *Record) error {
if matched {
if webhook.IsUserExtended {
user, err := GetMaskedUser(getUser(record.Organization, record.User))
user, err := getUser(record.Organization, record.User)
user, err = GetMaskedUser(user, false, err)
if err != nil {
return err
}

View File

@@ -16,6 +16,7 @@ package object
import (
"fmt"
"strings"
"github.com/casdoor/casdoor/util"
"github.com/xorm-io/core"
@@ -76,6 +77,10 @@ func GetPaginationResources(owner, user string, offset, limit int, field, value,
}
func getResource(owner string, name string) (*Resource, error) {
if !strings.HasPrefix(name, "/") {
name = "/" + name
}
resource := Resource{Owner: owner, Name: name}
existed, err := adapter.Engine.Get(&resource)
if err != nil {

View File

@@ -391,10 +391,13 @@ func GetAncestorRoles(roleIds ...string) ([]*Role, error) {
// containsRole is a helper function to check if a roles is related to any role in the given list roles
func containsRole(role *Role, roleMap map[string]*Role, visited map[string]bool, roleIds ...string) bool {
if isContain, ok := visited[role.GetId()]; ok {
roleId := role.GetId()
if isContain, ok := visited[roleId]; ok {
return isContain
}
visited[role.GetId()] = false
for _, subRole := range role.Roles {
if util.HasString(roleIds, subRole) {
return true

View File

@@ -42,7 +42,11 @@ func SendSms(provider *Provider, content string, phoneNumbers ...string) error {
return err
}
if provider.Type == sender.Aliyun {
if provider.Type == sender.Twilio {
if provider.AppId != "" {
phoneNumbers = append([]string{provider.AppId}, phoneNumbers...)
}
} else if provider.Type == sender.Aliyun {
for i, number := range phoneNumbers {
phoneNumbers[i] = strings.TrimPrefix(number, "+86")
}

View File

@@ -26,6 +26,7 @@ import (
"time"
"github.com/beevik/etree"
"github.com/casdoor/casdoor/i18n"
"github.com/casdoor/casdoor/util"
dsig "github.com/russellhaering/goxmldsig"
)
@@ -122,6 +123,13 @@ var stToServiceResponse sync.Map
// pgt is short for proxy granting ticket
var pgtToServiceResponse sync.Map
func CheckCasRestrict(application *Application, lang string, service string) error {
if len(application.RedirectUris) > 0 && !application.IsRedirectUriValid(service) {
return fmt.Errorf(i18n.Translate(lang, "token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), service)
}
return nil
}
func StoreCasTokenForPgt(token *CasAuthenticationSuccess, service, userId string) string {
pgt := fmt.Sprintf("PGT-%s", util.GenerateId())
pgtToServiceResponse.Store(pgt, &CasAuthenticationSuccessWrapper{

View File

@@ -281,6 +281,14 @@ func generateJwtToken(application *Application, user *User, nonce string, scope
return "", "", "", err
}
if cert == nil {
if application.Cert == "" {
return "", "", "", fmt.Errorf("The cert field of the application \"%s\" should not be empty", application.GetId())
} else {
return "", "", "", fmt.Errorf("The cert \"%s\" does not exist", application.Cert)
}
}
// RSA private key
key, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(cert.PrivateKey))
if err != nil {

View File

@@ -156,6 +156,7 @@ type User struct {
Yammer string `xorm:"yammer varchar(100)" json:"yammer"`
Yandex string `xorm:"yandex varchar(100)" json:"yandex"`
Zoom string `xorm:"zoom varchar(100)" json:"zoom"`
MetaMask string `xorm:"metamask varchar(100)" json:"metamask"`
Custom string `xorm:"custom varchar(100)" json:"custom"`
WebauthnCredentials []webauthn.Credential `xorm:"webauthnCredentials blob" json:"webauthnCredentials"`
@@ -418,7 +419,7 @@ func GetUserNoCheck(id string) (*User, error) {
return getUser(owner, name)
}
func GetMaskedUser(user *User, errs ...error) (*User, error) {
func GetMaskedUser(user *User, isAdminOrSelf bool, errs ...error) (*User, error) {
if len(errs) > 0 && errs[0] != nil {
return nil, errs[0]
}
@@ -430,9 +431,13 @@ func GetMaskedUser(user *User, errs ...error) (*User, error) {
if user.Password != "" {
user.Password = "***"
}
if user.AccessSecret != "" {
user.AccessSecret = "***"
if !isAdminOrSelf {
if user.AccessSecret != "" {
user.AccessSecret = "***"
}
}
if user.ManagedAccounts != nil {
for _, manageAccount := range user.ManagedAccounts {
manageAccount.Password = "***"
@@ -456,7 +461,7 @@ func GetMaskedUsers(users []*User, errs ...error) ([]*User, error) {
var err error
for _, user := range users {
user, err = GetMaskedUser(user)
user, err = GetMaskedUser(user, false)
if err != nil {
return nil, err
}

View File

@@ -156,7 +156,7 @@ func AuthzFilter(ctx *context.Context) {
urlPath := getUrlPath(ctx.Request.URL.Path)
objOwner, objName := "", ""
if urlPath != "/api/get-app-login" {
if urlPath != "/api/get-app-login" && urlPath != "/api/get-resource" {
objOwner, objName = getObject(ctx)
}

View File

@@ -123,6 +123,12 @@ func initAPI() {
beego.Router("/api/add-policy", &controllers.ApiController{}, "POST:AddPolicy")
beego.Router("/api/remove-policy", &controllers.ApiController{}, "POST:RemovePolicy")
beego.Router("/api/get-enforcers", &controllers.ApiController{}, "GET:GetEnforcers")
beego.Router("/api/get-enforcer", &controllers.ApiController{}, "GET:GetEnforcer")
beego.Router("/api/update-enforcer", &controllers.ApiController{}, "POST:UpdateEnforcer")
beego.Router("/api/add-enforcer", &controllers.ApiController{}, "POST:AddEnforcer")
beego.Router("/api/delete-enforcer", &controllers.ApiController{}, "POST:DeleteEnforcer")
beego.Router("/api/set-password", &controllers.ApiController{}, "POST:SetPassword")
beego.Router("/api/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword")
beego.Router("/api/get-email-and-phone", &controllers.ApiController{}, "GET:GetEmailAndPhone")

View File

@@ -23,7 +23,7 @@ func GetStorageProvider(providerType string, clientId string, clientSecret strin
case "AWS S3":
return NewAwsS3StorageProvider(clientId, clientSecret, region, bucket, endpoint)
case "MinIO":
return NewMinIOS3StorageProvider(clientId, clientSecret, region, bucket, endpoint)
return NewMinIOS3StorageProvider(clientId, clientSecret, "_", bucket, endpoint)
case "Aliyun OSS":
return NewAliyunOssStorageProvider(clientId, clientSecret, region, bucket, endpoint)
case "Tencent Cloud COS":

View File

@@ -155,6 +155,34 @@
}
}
},
"/api/add-enforcer": {
"post": {
"tags": [
"Enforcer API"
],
"description": "add enforcer",
"operationId": "ApiController.AddEnforcer",
"parameters": [
{
"in": "body",
"name": "enforcer",
"description": "The enforcer object",
"required": true,
"schema": {
"$ref": "#/definitions/object"
}
}
],
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/object"
}
}
}
}
},
"/api/add-group": {
"post": {
"tags": [
@@ -188,7 +216,27 @@
"tags": [
"Account API"
],
"operationId": "ApiController.AddLdap"
"description": "add ldap",
"operationId": "ApiController.AddLdap",
"parameters": [
{
"in": "body",
"name": "body",
"description": "The details of the ldap",
"required": true,
"schema": {
"$ref": "#/definitions/object.Ldap"
}
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/add-message": {
@@ -1053,6 +1101,34 @@
}
}
},
"/api/delete-enforcer": {
"post": {
"tags": [
"Enforcer API"
],
"description": "delete enforcer",
"operationId": "ApiController.DeleteEnforcer",
"parameters": [
{
"in": "body",
"name": "body",
"description": "The enforcer object",
"required": true,
"schema": {
"$ref": "#/definitions/object.Enforce"
}
}
],
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/object"
}
}
}
}
},
"/api/delete-group": {
"post": {
"tags": [
@@ -1086,7 +1162,27 @@
"tags": [
"Account API"
],
"operationId": "ApiController.DeleteLdap"
"description": "delete ldap",
"operationId": "ApiController.DeleteLdap",
"parameters": [
{
"in": "body",
"name": "body",
"description": "The details of the ldap",
"required": true,
"schema": {
"$ref": "#/definitions/object.Ldap"
}
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/delete-message": {
@@ -1978,6 +2074,61 @@
}
}
},
"/api/get-enforcer": {
"get": {
"tags": [
"Enforcer API"
],
"description": "get enforcer",
"operationId": "ApiController.GetEnforcer",
"parameters": [
{
"in": "query",
"name": "id",
"description": "The id ( owner/name ) of enforcer",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/object"
}
}
}
}
},
"/api/get-enforcers": {
"get": {
"tags": [
"Enforcer API"
],
"description": "get enforcers",
"operationId": "ApiController.GetEnforcers",
"parameters": [
{
"in": "query",
"name": "owner",
"description": "The owner of enforcers",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/object.Enforcer"
}
}
}
}
}
},
"/api/get-global-providers": {
"get": {
"tags": [
@@ -2098,7 +2249,25 @@
"tags": [
"Account API"
],
"operationId": "ApiController.GetLdap"
"description": "get ldap",
"operationId": "ApiController.GetLdap",
"parameters": [
{
"in": "query",
"name": "id",
"description": "id",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/object.Ldap"
}
}
}
}
},
"/api/get-ldap-users": {
@@ -2106,7 +2275,16 @@
"tags": [
"Account API"
],
"operationId": "ApiController.GetLdapser"
"description": "get ldap users",
"operationId": "ApiController.GetLdapser",
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/LdapResp"
}
}
}
}
},
"/api/get-ldaps": {
@@ -2114,7 +2292,27 @@
"tags": [
"Account API"
],
"operationId": "ApiController.GetLdaps"
"description": "get ldaps",
"operationId": "ApiController.GetLdaps",
"parameters": [
{
"in": "query",
"name": "owner",
"description": "owner",
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/object.Ldap"
}
}
}
}
}
},
"/api/get-message": {
@@ -4139,7 +4337,25 @@
"tags": [
"Account API"
],
"operationId": "ApiController.SyncLdapUsers"
"description": "sync ldap users",
"operationId": "ApiController.SyncLdapUsers",
"parameters": [
{
"in": "query",
"name": "id",
"description": "id",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/LdapSyncResp"
}
}
}
}
},
"/api/unlink": {
@@ -4289,6 +4505,41 @@
}
}
},
"/api/update-enforcer": {
"post": {
"tags": [
"Enforcer API"
],
"description": "update enforcer",
"operationId": "ApiController.UpdateEnforcer",
"parameters": [
{
"in": "query",
"name": "id",
"description": "The id ( owner/name ) of enforcer",
"required": true,
"type": "string"
},
{
"in": "body",
"name": "enforcer",
"description": "The enforcer object",
"required": true,
"schema": {
"$ref": "#/definitions/object"
}
}
],
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/object"
}
}
}
}
},
"/api/update-group": {
"post": {
"tags": [
@@ -4329,7 +4580,27 @@
"tags": [
"Account API"
],
"operationId": "ApiController.UpdateLdap"
"description": "update ldap",
"operationId": "ApiController.UpdateLdap",
"parameters": [
{
"in": "body",
"name": "body",
"description": "The details of the ldap",
"required": true,
"schema": {
"$ref": "#/definitions/object.Ldap"
}
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/update-message": {
@@ -5148,14 +5419,34 @@
}
},
"definitions": {
"1225.0xc000333110.false": {
"title": "false",
"type": "object"
},
"1260.0xc000333140.false": {
"title": "false",
"type": "object"
},
"LaravelResponse": {
"title": "LaravelResponse",
"type": "object"
},
"LdapResp": {
"title": "LdapResp",
"type": "object"
},
"LdapSyncResp": {
"title": "LdapSyncResp",
"type": "object"
},
"Response": {
"title": "Response",
"type": "object"
},
"casbin.Enforcer": {
"title": "Enforcer",
"type": "object"
},
"controllers.AuthForm": {
"title": "AuthForm",
"type": "object"
@@ -5189,16 +5480,10 @@
"type": "object",
"properties": {
"data": {
"additionalProperties": {
"description": "support string, struct or []struct",
"type": "string"
}
"$ref": "#/definitions/1225.0xc000333110.false"
},
"data2": {
"additionalProperties": {
"description": "support string, struct or []struct",
"type": "string"
}
"$ref": "#/definitions/1260.0xc000333140.false"
},
"msg": {
"type": "string"
@@ -5240,6 +5525,10 @@
"title": "object",
"type": "object"
},
"object.\u0026{197582 0xc000ace360 false}": {
"title": "\u0026{197582 0xc000ace360 false}",
"type": "object"
},
"object.AccountItem": {
"title": "AccountItem",
"type": "object",
@@ -5433,7 +5722,7 @@
"title": "CasbinRequest",
"type": "array",
"items": {
"$ref": "#/definitions/object.CasbinRequest"
"$ref": "#/definitions/object.\u0026{197582 0xc000ace360 false}"
}
},
"object.Cert": {
@@ -5529,6 +5818,43 @@
}
}
},
"object.Enforce": {
"title": "Enforce",
"type": "object"
},
"object.Enforcer": {
"title": "Enforcer",
"type": "object",
"properties": {
"adapter": {
"type": "string"
},
"createdTime": {
"type": "string"
},
"description": {
"type": "string"
},
"displayName": {
"type": "string"
},
"isEnabled": {
"type": "boolean"
},
"model": {
"type": "string"
},
"name": {
"type": "string"
},
"owner": {
"type": "string"
},
"updatedTime": {
"type": "string"
}
}
},
"object.GaugeVecInfo": {
"title": "GaugeVecInfo",
"type": "object",
@@ -5681,6 +6007,59 @@
}
}
},
"object.Ldap": {
"title": "Ldap",
"type": "object",
"properties": {
"autoSync": {
"type": "integer",
"format": "int64"
},
"baseDn": {
"type": "string"
},
"createdTime": {
"type": "string"
},
"enableSsl": {
"type": "boolean"
},
"filter": {
"type": "string"
},
"filterFields": {
"type": "array",
"items": {
"type": "string"
}
},
"host": {
"type": "string"
},
"id": {
"type": "string"
},
"lastSync": {
"type": "string"
},
"owner": {
"type": "string"
},
"password": {
"type": "string"
},
"port": {
"type": "integer",
"format": "int64"
},
"serverName": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"object.ManagedAccount": {
"title": "ManagedAccount",
"type": "object",
@@ -7137,6 +7516,9 @@
"meetup": {
"type": "string"
},
"metamask": {
"type": "string"
},
"mfaEmailEnabled": {
"type": "boolean"
},

View File

@@ -100,6 +100,24 @@ paths:
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/add-enforcer:
post:
tags:
- Enforcer API
description: add enforcer
operationId: ApiController.AddEnforcer
parameters:
- in: body
name: enforcer
description: The enforcer object
required: true
schema:
$ref: '#/definitions/object'
responses:
"200":
description: ""
schema:
$ref: '#/definitions/object'
/api/add-group:
post:
tags:
@@ -122,7 +140,20 @@ paths:
post:
tags:
- Account API
description: add ldap
operationId: ApiController.AddLdap
parameters:
- in: body
name: body
description: The details of the ldap
required: true
schema:
$ref: '#/definitions/object.Ldap'
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/add-message:
post:
tags:
@@ -680,6 +711,24 @@ paths:
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/delete-enforcer:
post:
tags:
- Enforcer API
description: delete enforcer
operationId: ApiController.DeleteEnforcer
parameters:
- in: body
name: body
description: The enforcer object
required: true
schema:
$ref: '#/definitions/object.Enforce'
responses:
"200":
description: ""
schema:
$ref: '#/definitions/object'
/api/delete-group:
post:
tags:
@@ -702,7 +751,20 @@ paths:
post:
tags:
- Account API
description: delete ldap
operationId: ApiController.DeleteLdap
parameters:
- in: body
name: body
description: The details of the ldap
required: true
schema:
$ref: '#/definitions/object.Ldap'
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/delete-message:
post:
tags:
@@ -1281,6 +1343,42 @@ paths:
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/get-enforcer:
get:
tags:
- Enforcer API
description: get enforcer
operationId: ApiController.GetEnforcer
parameters:
- in: query
name: id
description: The id ( owner/name ) of enforcer
required: true
type: string
responses:
"200":
description: ""
schema:
$ref: '#/definitions/object'
/api/get-enforcers:
get:
tags:
- Enforcer API
description: get enforcers
operationId: ApiController.GetEnforcers
parameters:
- in: query
name: owner
description: The owner of enforcers
required: true
type: string
responses:
"200":
description: ""
schema:
type: array
items:
$ref: '#/definitions/object.Enforcer'
/api/get-global-providers:
get:
tags:
@@ -1360,17 +1458,48 @@ paths:
get:
tags:
- Account API
description: get ldap
operationId: ApiController.GetLdap
parameters:
- in: query
name: id
description: id
required: true
type: string
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/object.Ldap'
/api/get-ldap-users:
get:
tags:
- Account API
description: get ldap users
operationId: ApiController.GetLdapser
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/LdapResp'
/api/get-ldaps:
get:
tags:
- Account API
description: get ldaps
operationId: ApiController.GetLdaps
parameters:
- in: query
name: owner
description: owner
type: string
responses:
"200":
description: The Response object
schema:
type: array
items:
$ref: '#/definitions/object.Ldap'
/api/get-message:
get:
tags:
@@ -2703,7 +2832,19 @@ paths:
post:
tags:
- Account API
description: sync ldap users
operationId: ApiController.SyncLdapUsers
parameters:
- in: query
name: id
description: id
required: true
type: string
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/LdapSyncResp'
/api/unlink:
post:
tags:
@@ -2800,6 +2941,29 @@ paths:
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/update-enforcer:
post:
tags:
- Enforcer API
description: update enforcer
operationId: ApiController.UpdateEnforcer
parameters:
- in: query
name: id
description: The id ( owner/name ) of enforcer
required: true
type: string
- in: body
name: enforcer
description: The enforcer object
required: true
schema:
$ref: '#/definitions/object'
responses:
"200":
description: ""
schema:
$ref: '#/definitions/object'
/api/update-group:
post:
tags:
@@ -2827,7 +2991,20 @@ paths:
post:
tags:
- Account API
description: update ldap
operationId: ApiController.UpdateLdap
parameters:
- in: body
name: body
description: The details of the ldap
required: true
schema:
$ref: '#/definitions/object.Ldap'
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/update-message:
post:
tags:
@@ -3364,12 +3541,27 @@ paths:
schema:
$ref: '#/definitions/controllers.Response'
definitions:
1225.0xc000333110.false:
title: "false"
type: object
1260.0xc000333140.false:
title: "false"
type: object
LaravelResponse:
title: LaravelResponse
type: object
LdapResp:
title: LdapResp
type: object
LdapSyncResp:
title: LdapSyncResp
type: object
Response:
title: Response
type: object
casbin.Enforcer:
title: Enforcer
type: object
controllers.AuthForm:
title: AuthForm
type: object
@@ -3394,13 +3586,9 @@ definitions:
type: object
properties:
data:
additionalProperties:
description: support string, struct or []struct
type: string
$ref: '#/definitions/1225.0xc000333110.false'
data2:
additionalProperties:
description: support string, struct or []struct
type: string
$ref: '#/definitions/1260.0xc000333140.false'
msg:
type: string
name:
@@ -3427,6 +3615,9 @@ definitions:
object:
title: object
type: object
object.&{197582 0xc000ace360 false}:
title: '&{197582 0xc000ace360 false}'
type: object
object.AccountItem:
title: AccountItem
type: object
@@ -3558,7 +3749,7 @@ definitions:
title: CasbinRequest
type: array
items:
$ref: '#/definitions/object.CasbinRequest'
$ref: '#/definitions/object.&{197582 0xc000ace360 false}'
object.Cert:
title: Cert
type: object
@@ -3622,6 +3813,31 @@ definitions:
type: array
items:
type: string
object.Enforce:
title: Enforce
type: object
object.Enforcer:
title: Enforcer
type: object
properties:
adapter:
type: string
createdTime:
type: string
description:
type: string
displayName:
type: string
isEnabled:
type: boolean
model:
type: string
name:
type: string
owner:
type: string
updatedTime:
type: string
object.GaugeVecInfo:
title: GaugeVecInfo
type: object
@@ -3725,6 +3941,42 @@ definitions:
type: string
username:
type: string
object.Ldap:
title: Ldap
type: object
properties:
autoSync:
type: integer
format: int64
baseDn:
type: string
createdTime:
type: string
enableSsl:
type: boolean
filter:
type: string
filterFields:
type: array
items:
type: string
host:
type: string
id:
type: string
lastSync:
type: string
owner:
type: string
password:
type: string
port:
type: integer
format: int64
serverName:
type: string
username:
type: string
object.ManagedAccount:
title: ManagedAccount
type: object
@@ -4704,6 +4956,8 @@ definitions:
$ref: '#/definitions/object.ManagedAccount'
meetup:
type: string
metamask:
type: string
mfaEmailEnabled:
type: boolean
mfaPhoneEnabled:

View File

@@ -50,4 +50,32 @@ module.exports = {
},
},
],
webpack: {
// use polyfill Buffer with Webpack 5
// https://viglucci.io/articles/how-to-polyfill-buffer-with-webpack-5
// https://craco.js.org/docs/configuration/webpack/
configure: (webpackConfig, { env, paths }) => {
webpackConfig.resolve.fallback = {
// "process": require.resolve('process/browser'),
// "util": require.resolve("util/"),
// "url": require.resolve("url/"),
// "zlib": require.resolve("browserify-zlib"),
// "stream": require.resolve("stream-browserify"),
// "http": require.resolve("stream-http"),
// "https": require.resolve("https-browserify"),
// "assert": require.resolve("assert/"),
"buffer": require.resolve('buffer/'),
"process": false,
"util": false,
"url": false,
"zlib": false,
"stream": false,
"http": false,
"https": false,
"assert": false,
"buffer": false,
};
return webpackConfig;
},
}
};

View File

@@ -9,11 +9,13 @@
"@crowdin/cli": "^3.7.10",
"@ctrl/tinycolor": "^3.5.0",
"@emotion/react": "^11.10.5",
"@metamask/eth-sig-util": "^6.0.0",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"antd": "5.2.3",
"antd-token-previewer": "^1.1.0-22",
"buffer": "^6.0.3",
"codemirror": "^5.61.1",
"copy-to-clipboard": "^3.3.1",
"core-js": "^3.25.0",
@@ -31,9 +33,11 @@
"react-device-detect": "^2.2.2",
"react-dom": "^18.2.0",
"react-github-corner": "^2.5.0",
"react-google-one-tap-login": "^0.1.1",
"react-helmet": "^6.1.0",
"react-highlight-words": "^0.18.0",
"react-i18next": "^11.8.7",
"react-metamask-avatar": "^1.2.1",
"react-router-dom": "^5.3.3",
"react-scripts": "5.0.1",
"react-social-login-buttons": "^3.4.0"

View File

@@ -68,7 +68,7 @@ class AdapterEditPage extends React.Component {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
organizations: res.data || [],
});
});
}
@@ -80,8 +80,9 @@ class AdapterEditPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
models: res,
models: res.data,
});
});
}

View File

@@ -15,10 +15,6 @@
import React, {Component} from "react";
import "./App.less";
import {Helmet} from "react-helmet";
import EnableMfaNotification from "./common/notifaction/EnableMfaNotification";
import GroupTreePage from "./GroupTreePage";
import GroupEditPage from "./GroupEdit";
import GroupListPage from "./GroupList";
import {MfaRuleRequired} from "./Setting";
import * as Setting from "./Setting";
import {StyleProvider, legacyLogicalPropertiesTransformer} from "@ant-design/cssinjs";
@@ -33,6 +29,11 @@ import RoleListPage from "./RoleListPage";
import RoleEditPage from "./RoleEditPage";
import PermissionListPage from "./PermissionListPage";
import PermissionEditPage from "./PermissionEditPage";
import EnforcerEditPage from "./EnforcerEditPage";
import EnforcerListPage from "./EnforcerListPage";
import GroupTreePage from "./GroupTreePage";
import GroupEditPage from "./GroupEdit";
import GroupListPage from "./GroupList";
import ProviderListPage from "./ProviderListPage";
import ProviderEditPage from "./ProviderEditPage";
import ApplicationListPage from "./ApplicationListPage";
@@ -86,9 +87,12 @@ import OdicDiscoveryPage from "./auth/OidcDiscoveryPage";
import SamlCallback from "./auth/SamlCallback";
import i18next from "i18next";
import {withTranslation} from "react-i18next";
import EnableMfaNotification from "./common/notifaction/EnableMfaNotification";
import LanguageSelect from "./common/select/LanguageSelect";
import ThemeSelect from "./common/select/ThemeSelect";
import OrganizationSelect from "./common/select/OrganizationSelect";
import {clearWeb3AuthToken} from "./auth/Web3Auth";
import AccountAvatar from "./account/AccountAvatar";
const {Header, Footer, Content} = Layout;
@@ -162,6 +166,8 @@ class App extends Component {
this.setState({selectedMenuKey: "/models"});
} else if (uri.includes("/adapters")) {
this.setState({selectedMenuKey: "/adapters"});
} else if (uri.includes("/enforcers")) {
this.setState({selectedMenuKey: "/enforcers"});
} else if (uri.includes("/providers")) {
this.setState({selectedMenuKey: "/providers"});
} else if (uri.includes("/applications")) {
@@ -312,12 +318,11 @@ class App extends Component {
.then((res) => {
if (res.status === "ok") {
const owner = this.state.account.owner;
this.setState({
account: null,
themeAlgorithm: ["default"],
});
clearWeb3AuthToken();
Setting.showMessage("success", i18next.t("application:Logged out successfully"));
const redirectUri = res.data2;
if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") {
@@ -348,7 +353,9 @@ class App extends Component {
);
} else {
return (
<Avatar src={this.state.account.avatar} style={{verticalAlign: "middle"}} size="large">
<Avatar src={this.state.account.avatar} style={{verticalAlign: "middle"}} size="large"
icon={<AccountAvatar src={this.state.account.avatar} style={{verticalAlign: "middle"}} size={40} />}
>
{Setting.getShortName(this.state.account.name)}
</Avatar>
);
@@ -478,6 +485,10 @@ class App extends Component {
res.push(Setting.getItem(<Link to="/adapters">{i18next.t("general:Adapters")}</Link>,
"/adapters"
));
res.push(Setting.getItem(<Link to="/enforcers">{i18next.t("general:Enforcers")}</Link>,
"/enforcers"
));
}
if (Setting.isLocalAdminUser(this.state.account)) {
@@ -596,6 +607,8 @@ class App extends Component {
<Route exact path="/permissions/:organizationName/:permissionName" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionEditPage account={this.state.account} {...props} />)} />
<Route exact path="/models" render={(props) => this.renderLoginIfNotLoggedIn(<ModelListPage account={this.state.account} {...props} />)} />
<Route exact path="/models/:organizationName/:modelName" render={(props) => this.renderLoginIfNotLoggedIn(<ModelEditPage account={this.state.account} {...props} />)} />
<Route exact path="/enforcers" render={(props) => this.renderLoginIfNotLoggedIn(<EnforcerListPage account={this.state.account} {...props} />)} />
<Route exact path="/enforcers/:organizationName/:enforcerName" render={(props) => this.renderLoginIfNotLoggedIn(<EnforcerEditPage account={this.state.account} {...props} />)} />
<Route exact path="/adapters" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterListPage account={this.state.account} {...props} />)} />
<Route exact path="/adapters/:organizationName/:adapterName" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterEditPage account={this.state.account} {...props} />)} />
<Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)} />

View File

@@ -119,7 +119,7 @@ class ApplicationEditPage extends React.Component {
getApplication() {
ApplicationBackend.getApplication("admin", this.state.applicationName)
.then((res) => {
if (res === null) {
if (res.data === null) {
this.props.history.push("/404");
return;
}
@@ -129,32 +129,33 @@ class ApplicationEditPage extends React.Component {
return;
}
if (res.grantTypes === null || res.grantTypes === undefined || res.grantTypes.length === 0) {
res.grantTypes = ["authorization_code"];
const application = res.data;
if (application.grantTypes === null || application.grantTypes === undefined || application.grantTypes.length === 0) {
application.grantTypes = ["authorization_code"];
}
if (res.tags === null || res.tags === undefined) {
res.tags = [];
if (application.tags === null || application.tags === undefined) {
application.tags = [];
}
this.setState({
application: res,
application: application,
});
this.getCerts(res.organization);
this.getCerts(application.organization);
});
}
getOrganizations() {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
if (res?.status === "error") {
if (res.status === "error") {
this.setState({
isAuthorized: false,
});
} else {
this.setState({
organizations: (res.msg === undefined) ? res : [],
organizations: res.data || [],
});
}
});
@@ -164,7 +165,7 @@ class ApplicationEditPage extends React.Component {
CertBackend.getCerts(owner)
.then((res) => {
this.setState({
certs: (res.msg === undefined) ? res : [],
certs: res.data || [],
});
});
}
@@ -184,9 +185,9 @@ class ApplicationEditPage extends React.Component {
getSamlMetadata() {
ApplicationBackend.getSamlMetadata("admin", this.state.applicationName)
.then((res) => {
.then((data) => {
this.setState({
samlMetadata: res,
samlMetadata: data,
});
});
}

View File

@@ -45,7 +45,7 @@ class CertEditPage extends React.Component {
getCert() {
CertBackend.getCert(this.state.owner, this.state.certName)
.then((res) => {
if (res === null) {
if (res.data === null) {
this.props.history.push("/404");
return;
}
@@ -56,7 +56,7 @@ class CertEditPage extends React.Component {
}
this.setState({
cert: res,
cert: res.data,
});
});
}
@@ -65,7 +65,7 @@ class CertEditPage extends React.Component {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
organizations: res.data || [],
});
});
}

View File

@@ -41,7 +41,7 @@ class ChatEditPage extends React.Component {
getChat() {
ChatBackend.getChat("admin", this.state.chatName)
.then((res) => {
if (res === null) {
if (res.data === null) {
this.props.history.push("/404");
return;
}
@@ -51,10 +51,10 @@ class ChatEditPage extends React.Component {
return;
}
this.setState({
chat: res,
chat: res.data,
});
this.getUsers(res.organization);
this.getUsers(res.data.organization);
});
}
@@ -62,7 +62,7 @@ class ChatEditPage extends React.Component {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
organizations: res.data || [],
});
});
}
@@ -76,7 +76,7 @@ class ChatEditPage extends React.Component {
}
this.setState({
users: res,
users: res.data,
});
});
}

View File

@@ -79,7 +79,8 @@ class ChatPage extends BaseListPage {
getMessages(chatName) {
MessageBackend.getChatMessages(chatName)
.then((messages) => {
.then((res) => {
const messages = res.data;
this.setState({
messages: messages,
});
@@ -229,7 +230,7 @@ class ChatPage extends BaseListPage {
</div>
)
}
<ChatBox messages={this.state.messages} sendMessage={(text) => {this.sendMessage(text);}} account={this.props.account} />
<ChatBox messages={this.state.messages || []} sendMessage={(text) => {this.sendMessage(text);}} account={this.props.account} />
</div>
</div>
);

257
web/src/EnforcerEditPage.js Normal file
View File

@@ -0,0 +1,257 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from "react";
import {Button, Card, Col, Input, Row, Select, Switch} from "antd";
import * as AdapterBackend from "./backend/AdapterBackend";
import * as EnforcerBackend from "./backend/EnforcerBackend";
import * as ModelBackend from "./backend/ModelBackend";
import * as OrganizationBackend from "./backend/OrganizationBackend";
import * as Setting from "./Setting";
import i18next from "i18next";
class EnforcerEditPage extends React.Component {
constructor(props) {
super(props);
this.state = {
classes: props,
organizationName: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
enforcerName: props.match.params.enforcerName,
enforcer: null,
organizations: [],
models: [],
adapters: [],
mode: props.location.mode !== undefined ? props.location.mode : "edit",
};
}
UNSAFE_componentWillMount() {
this.getEnforcer();
this.getOrganizations();
}
getEnforcer() {
EnforcerBackend.getEnforcer(this.state.organizationName, this.state.enforcerName)
.then((res) => {
if (res.data === null) {
this.props.history.push("/404");
return;
}
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
enforcer: res.data,
});
this.getModels(this.state.organizationName);
this.getAdapters(this.state.organizationName);
});
}
getOrganizations() {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: res.data || [],
});
});
}
getModels(organizationName) {
ModelBackend.getModels(organizationName)
.then((res) => {
this.setState({
models: res.data || [],
});
});
}
getAdapters(organizationName) {
AdapterBackend.getAdapters(organizationName)
.then((res) => {
this.setState({
adapters: res.data || [],
});
});
}
parseEnforcerField(key, value) {
if ([""].includes(key)) {
value = Setting.myParseInt(value);
}
return value;
}
updateEnforcerField(key, value) {
value = this.parseEnforcerField(key, value);
const enforcer = this.state.enforcer;
enforcer[key] = value;
this.setState({
enforcer: enforcer,
});
}
renderEnforcer() {
return (
<Card size="small" title={
<div>
{this.state.mode === "add" ? i18next.t("enforcer:New Enforcer") : i18next.t("enforcer:Edit Enforcer")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button onClick={() => this.submitEnforcerEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitEnforcerEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteEnforcer()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
<Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account)} value={this.state.enforcer.owner} onChange={(owner => {
this.updateEnforcerField("owner", owner);
this.getModels(owner);
this.getAdapters(owner);
})}
options={this.state.organizations.map((organization) => Setting.getOption(organization.name, organization.name))
} />
</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.enforcer.name} onChange={e => {
this.updateEnforcerField("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.enforcer.displayName} onChange={e => {
this.updateEnforcerField("displayName", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Description"), i18next.t("general:Description - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.enforcer.description} onChange={e => {
this.updateEnforcerField("description", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Model"), i18next.t("general:Model - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.enforcer.model} onChange={(model => {
this.updateEnforcerField("model", model);
})}
options={this.state.models.map((model) => Setting.getOption(model.displayName, model.name))
} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Adapter"), i18next.t("general:Adapter - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.enforcer.adapter} onChange={(adapter => {
this.updateEnforcerField("adapter", adapter);
})}
options={this.state.adapters.map((adapter) => Setting.getOption(adapter.name, adapter.name))
} />
</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.enforcer.isEnabled} onChange={checked => {
this.updateEnforcerField("isEnabled", checked);
}} />
</Col>
</Row>
</Card>
);
}
submitEnforcerEdit(willExist) {
const enforcer = Setting.deepCopy(this.state.enforcer);
EnforcerBackend.updateEnforcer(this.state.organizationName, this.state.enforcerName, enforcer)
.then((res) => {
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully saved"));
this.setState({
enforcerName: this.state.enforcer.name,
});
if (willExist) {
this.props.history.push("/enforcers");
} else {
this.props.history.push(`/enforcers/${this.state.enforcer.owner}/${this.state.enforcer.name}`);
}
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
this.updateEnforcerField("name", this.state.enforcerName);
}
})
.catch(error => {
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
deleteEnforcer() {
EnforcerBackend.deleteEnforcer(this.state.enforcer)
.then((res) => {
if (res.status === "ok") {
this.props.history.push("/enforcers");
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
render() {
return (
<div>
{
this.state.enforcer !== null ? this.renderEnforcer() : null
}
<div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button size="large" onClick={() => this.submitEnforcerEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitEnforcerEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteEnforcer()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
</div>
);
}
}
export default EnforcerEditPage;

218
web/src/EnforcerListPage.js Normal file
View File

@@ -0,0 +1,218 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from "react";
import {Link} from "react-router-dom";
import {Button, Switch, Table} from "antd";
import moment from "moment";
import * as Setting from "./Setting";
import * as EnforcerBackend from "./backend/EnforcerBackend";
import i18next from "i18next";
import BaseListPage from "./BaseListPage";
import PopconfirmModal from "./common/modal/PopconfirmModal";
class EnforcerListPage extends BaseListPage {
newEnforcer() {
const randomName = Setting.getRandomName();
const owner = Setting.getRequestOrganization(this.props.account);
return {
owner: owner,
name: `enforcer_${randomName}`,
createdTime: moment().format(),
displayName: `New Enforcer - ${randomName}`,
isEnabled: true,
};
}
addEnforcer() {
const newEnforcer = this.newEnforcer();
EnforcerBackend.addEnforcer(newEnforcer)
.then((res) => {
if (res.status === "ok") {
this.props.history.push({pathname: `/enforcers/${newEnforcer.owner}/${newEnforcer.name}`, mode: "add"});
Setting.showMessage("success", i18next.t("general:Successfully added"));
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
deleteEnforcer(i) {
EnforcerBackend.deleteEnforcer(this.state.data[i])
.then((res) => {
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
this.setState({
data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1},
});
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
renderTable(enforcers) {
const columns = [
{
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={`/enforcers/${record.owner}/${text}`}>
{text}
</Link>
);
},
},
{
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:Created time"),
dataIndex: "createdTime",
key: "createdTime",
width: "160px",
sorter: true,
render: (text, record, index) => {
return Setting.getFormattedDate(text);
},
},
{
title: i18next.t("general:Display name"),
dataIndex: "displayName",
key: "displayName",
width: "200px",
sorter: true,
...this.getColumnSearchProps("displayName"),
},
{
title: i18next.t("general:Is enabled"),
dataIndex: "isEnabled",
key: "isEnabled",
width: "120px",
sorter: true,
render: (text, record, index) => {
return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
);
},
},
{
title: i18next.t("general:Action"),
dataIndex: "",
key: "op",
width: "170px",
fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => {
return (
<div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary"
onClick={() => this.props.history.push(`/enforcers/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<PopconfirmModal
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
onConfirm={() => this.deleteEnforcer(index)}
>
</PopconfirmModal>
</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={enforcers} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered
pagination={paginationProps}
title={() => (
<div>
{i18next.t("general:Enforcers")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small"
onClick={this.addEnforcer.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;
const sortField = params.sortField, sortOrder = params.sortOrder;
if (params.type !== undefined && params.type !== null) {
field = "type";
value = params.type;
}
this.setState({loading: true});
EnforcerBackend.getEnforcers(Setting.isDefaultOrganizationSelected(this.props.account) ? "" : Setting.getRequestOrganization(this.props.account), params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
this.setState({
loading: false,
});
if (res.status === "ok") {
this.setState({
data: res.data,
pagination: {
...params.pagination,
total: res.data2,
},
searchText: params.searchText,
searchedColumn: params.searchedColumn,
});
} else {
if (Setting.isResponseDenied(res)) {
this.setState({
isAuthorized: false,
});
} else {
Setting.showMessage("error", res.msg);
}
}
});
};
}
export default EnforcerListPage;

View File

@@ -79,7 +79,9 @@ class EntryPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
const themeData = res !== null ? Setting.getThemeData(res.organizationObj, res) : Conf.ThemeDefault;
const application = res.data;
const themeData = application !== null ? Setting.getThemeData(application.organizationObj, application) : Conf.ThemeDefault;
this.props.updataThemeData(themeData);
});
};

View File

@@ -67,7 +67,7 @@ class GroupEditPage extends React.Component {
.then((res) => {
if (res.status === "ok") {
this.setState({
organizations: res.data,
organizations: res.data || [],
});
}
});

View File

@@ -55,7 +55,7 @@ class LdapEditPage extends React.Component {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
organizations: res.data || [],
});
});
}

View File

@@ -46,7 +46,7 @@ class MessageEditPage extends React.Component {
getMessage() {
MessageBackend.getMessage("admin", this.state.messageName)
.then((res) => {
if (res === null) {
if (res.data === null) {
this.props.history.push("/404");
return;
}
@@ -54,11 +54,11 @@ class MessageEditPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
message: res,
});
this.getUsers(res.organization);
this.setState({
message: res.data,
});
this.getUsers(res.data.organization);
});
}
@@ -66,7 +66,7 @@ class MessageEditPage extends React.Component {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
organizations: res.data || [],
});
});
}
@@ -75,7 +75,7 @@ class MessageEditPage extends React.Component {
ChatBackend.getChats("admin")
.then((res) => {
this.setState({
chats: (res.msg === undefined) ? res : [],
chats: res.data || [],
});
});
}
@@ -87,8 +87,9 @@ class MessageEditPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
users: res,
users: res.data,
});
});
}

View File

@@ -48,7 +48,7 @@ class ModelEditPage extends React.Component {
getModel() {
ModelBackend.getModel(this.state.organizationName, this.state.modelName)
.then((res) => {
if (res === null) {
if (res.data === null) {
this.props.history.push("/404");
return;
}
@@ -59,7 +59,7 @@ class ModelEditPage extends React.Component {
}
this.setState({
model: res,
model: res.data,
});
});
}
@@ -68,7 +68,7 @@ class ModelEditPage extends React.Component {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
organizations: res.data || [],
});
});
}

View File

@@ -75,7 +75,7 @@ class OrganizationEditPage extends React.Component {
}
this.setState({
applications: res,
applications: res.data || [],
});
});
}

View File

@@ -41,14 +41,14 @@ class PaymentEditPage extends React.Component {
getPayment() {
PaymentBackend.getPayment("admin", this.state.paymentName)
.then((payment) => {
if (payment === null) {
.then((res) => {
if (res.data === null) {
this.props.history.push("/404");
return;
}
this.setState({
payment: payment,
payment: res.data,
});
Setting.scrollToDiv("invoice-area");

View File

@@ -41,12 +41,12 @@ class PaymentResultPage extends React.Component {
getPayment() {
PaymentBackend.getPayment("admin", this.state.paymentName)
.then((payment) => {
.then((res) => {
this.setState({
payment: payment,
payment: res.data,
});
if (payment.state === "Created") {
if (res.data.state === "Created") {
this.setState({timeout: setTimeout(() => this.getPayment(), 1000)});
}
});

View File

@@ -50,7 +50,9 @@ class PermissionEditPage extends React.Component {
getPermission() {
PermissionBackend.getPermission(this.state.organizationName, this.state.permissionName)
.then((res) => {
if (res === null) {
const permission = res.data;
if (permission === null) {
this.props.history.push("/404");
return;
}
@@ -61,14 +63,14 @@ class PermissionEditPage extends React.Component {
}
this.setState({
permission: res,
permission: permission,
});
this.getUsers(res.owner);
this.getRoles(res.owner);
this.getModels(res.owner);
this.getResources(res.owner);
this.getModel(res.owner, res.model);
this.getUsers(permission.owner);
this.getRoles(permission.owner);
this.getModels(permission.owner);
this.getResources(permission.owner);
this.getModel(permission.owner, permission.model);
});
}
@@ -76,7 +78,7 @@ class PermissionEditPage extends React.Component {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
organizations: res.data || [],
});
});
}
@@ -88,8 +90,9 @@ class PermissionEditPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
users: res,
users: res.data,
});
});
}
@@ -101,8 +104,9 @@ class PermissionEditPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
roles: res,
roles: res.data,
});
});
}
@@ -114,21 +118,21 @@ class PermissionEditPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
models: res,
models: res.data,
});
});
}
getModel(organizationName, modelName) {
if (modelName === "") {
return;
}
ModelBackend.getModel(organizationName, modelName)
.then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
model: res,
model: res.data,
});
});
}
@@ -137,7 +141,7 @@ class PermissionEditPage extends React.Component {
ApplicationBackend.getApplicationsByOrganization("admin", organizationName)
.then((res) => {
this.setState({
resources: (res.msg === undefined) ? res : [],
resources: res.data || [],
});
});
}

View File

@@ -46,18 +46,18 @@ class PlanEditPage extends React.Component {
getPlan() {
PlanBackend.getPlan(this.state.organizationName, this.state.planName)
.then((plan) => {
if (plan === null) {
.then((res) => {
if (res.data === null) {
this.props.history.push("/404");
return;
}
this.setState({
plan: plan,
plan: res.data,
});
this.getUsers(plan.owner);
this.getRoles(plan.owner);
this.getUsers(this.state.organizationName);
this.getRoles(this.state.organizationName);
});
}
@@ -68,8 +68,9 @@ class PlanEditPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
roles: res,
roles: res.data,
});
});
}
@@ -81,8 +82,9 @@ class PlanEditPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
users: res,
users: res.data,
});
});
}
@@ -91,7 +93,7 @@ class PlanEditPage extends React.Component {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
organizations: res.data || [],
});
});
}

View File

@@ -44,13 +44,12 @@ class PricingEditPage extends React.Component {
this.getPricing();
this.getOrganizations();
this.getApplicationsByOrganization(this.state.organizationName);
this.getUserApplication();
}
getPricing() {
PricingBackend.getPricing(this.state.organizationName, this.state.pricingName)
.then((res) => {
if (res === null) {
if (res.data === null) {
this.props.history.push("/404");
return;
}
@@ -61,9 +60,9 @@ class PricingEditPage extends React.Component {
}
this.setState({
pricing: res,
pricing: res.data,
});
this.getPlans(res.owner);
this.getPlans(this.state.organizationName);
});
}
@@ -74,8 +73,9 @@ class PricingEditPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
plans: res,
plans: res.data,
});
});
}
@@ -84,7 +84,16 @@ class PricingEditPage extends React.Component {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
organizations: res.data || [],
});
});
}
getApplicationsByOrganization(organizationName) {
ApplicationBackend.getApplicationsByOrganization("admin", organizationName)
.then((res) => {
this.setState({
applications: res.data || [],
});
});
}
@@ -107,28 +116,6 @@ class PricingEditPage extends React.Component {
});
}
getApplicationsByOrganization(organizationName) {
ApplicationBackend.getApplicationsByOrganization("admin", organizationName)
.then((res) => {
this.setState({
applications: (res.msg === undefined) ? res : [],
});
});
}
getUserApplication() {
ApplicationBackend.getUserApplication(this.state.organizationName, this.state.userName)
.then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
application: res,
});
});
}
renderPricing() {
return (
<Card size="small" title={

View File

@@ -48,7 +48,7 @@ class ProductBuyPage extends React.Component {
}
this.setState({
product: res,
product: res.data,
});
});
}

View File

@@ -46,14 +46,14 @@ class ProductEditPage extends React.Component {
getProduct() {
ProductBackend.getProduct(this.state.organizationName, this.state.productName)
.then((product) => {
if (product === null) {
.then((res) => {
if (res.data === null) {
this.props.history.push("/404");
return;
}
this.setState({
product: product,
product: res.data,
});
});
}
@@ -62,7 +62,7 @@ class ProductEditPage extends React.Component {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
organizations: res.data || [],
});
});
}

View File

@@ -50,7 +50,7 @@ class ProviderEditPage extends React.Component {
getProvider() {
ProviderBackend.getProvider(this.state.owner, this.state.providerName)
.then((res) => {
if (res === null) {
if (res.data === null) {
this.props.history.push("/404");
return;
}
@@ -72,7 +72,7 @@ class ProviderEditPage extends React.Component {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: res.msg === undefined ? res : [],
organizations: res.data || [],
});
});
}
@@ -236,7 +236,10 @@ class ProviderEditPage extends React.Component {
tooltip = i18next.t("provider:Agent ID - Tooltip");
}
} else if (provider.category === "SMS") {
if (provider.type === "Tencent Cloud SMS") {
if (provider.type === "Twilio SMS") {
text = i18next.t("provider:Sender number");
tooltip = i18next.t("provider:Sender number - Tooltip");
} else if (provider.type === "Tencent Cloud SMS") {
text = i18next.t("provider:App ID");
tooltip = i18next.t("provider:App ID - Tooltip");
} else if (provider.type === "Volc Engine SMS") {
@@ -355,6 +358,8 @@ class ProviderEditPage extends React.Component {
this.updateProviderField("type", "Default");
} else if (value === "AI") {
this.updateProviderField("type", "OpenAI API - GPT");
} else if (value === "Web3") {
this.updateProviderField("type", "MetaMask");
}
})}>
{
@@ -367,6 +372,7 @@ class ProviderEditPage extends React.Component {
{id: "SAML", name: "SAML"},
{id: "SMS", name: "SMS"},
{id: "Storage", name: "Storage"},
{id: "Web3", name: "Web3"},
]
.sort((a, b) => a.name.localeCompare(b.name))
.map((providerCategory, index) => <Option key={index} value={providerCategory.id}>{providerCategory.name}</Option>)
@@ -521,7 +527,7 @@ class ProviderEditPage extends React.Component {
)
}
{
this.state.provider.category === "Captcha" && this.state.provider.type === "Default" ? null : (
(this.state.provider.category === "Captcha" && this.state.provider.type === "Default") || (this.state.provider.category === "Web3") || (this.state.provider.category === "Storage" && this.state.provider.type === "Local File System") ? null : (
<React.Fragment>
{
this.state.provider.category === "AI" ? null : (
@@ -610,36 +616,42 @@ class ProviderEditPage extends React.Component {
}
{this.state.provider.category === "Storage" ? (
<div>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Endpoint"), i18next.t("provider:Region endpoint for Internet"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.endpoint} onChange={e => {
this.updateProviderField("endpoint", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Endpoint (Intranet)"), i18next.t("provider:Region endpoint for Intranet"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.intranetEndpoint} onChange={e => {
this.updateProviderField("intranetEndpoint", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Bucket"), i18next.t("provider:Bucket - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.bucket} onChange={e => {
this.updateProviderField("bucket", e.target.value);
}} />
</Col>
</Row>
{["Local File System"].includes(this.state.provider.type) ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Endpoint"), i18next.t("provider:Region endpoint for Internet"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.endpoint} onChange={e => {
this.updateProviderField("endpoint", e.target.value);
}} />
</Col>
</Row>
)}
{["Local File System", "MinIO", "Tencent Cloud COS"].includes(this.state.provider.type) ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Endpoint (Intranet)"), i18next.t("provider:Region endpoint for Intranet"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.intranetEndpoint} onChange={e => {
this.updateProviderField("intranetEndpoint", e.target.value);
}} />
</Col>
</Row>
)}
{["Local File System"].includes(this.state.provider.type) ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Bucket"), i18next.t("provider:Bucket - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.bucket} onChange={e => {
this.updateProviderField("bucket", e.target.value);
}} />
</Col>
</Row>
)}
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Path prefix"), i18next.t("provider:Path prefix - Tooltip"))} :
@@ -650,17 +662,19 @@ class ProviderEditPage extends React.Component {
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.domain} onChange={e => {
this.updateProviderField("domain", e.target.value);
}} />
</Col>
</Row>
{["AWS S3", "MinIO", "Tencent Cloud COS"].includes(this.state.provider.type) ? (
{["MinIO"].includes(this.state.provider.type) ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.domain} disabled={this.state.provider.type === "Local File System"} onChange={e => {
this.updateProviderField("domain", e.target.value);
}} />
</Col>
</Row>
)}
{["AWS S3", "Tencent Cloud COS"].includes(this.state.provider.type) ? (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Region ID"), i18next.t("provider:Region ID - Tooltip"))} :
@@ -674,6 +688,7 @@ class ProviderEditPage extends React.Component {
) : null}
</div>
) : null}
{this.getAppIdRow(this.state.provider)}
{
this.state.provider.category === "Email" ? (
<React.Fragment>
@@ -919,7 +934,6 @@ class ProviderEditPage extends React.Component {
</Row>
) : null
}
{this.getAppIdRow(this.state.provider)}
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Provider URL"), i18next.t("provider:Provider URL - Tooltip"))} :

View File

@@ -43,7 +43,7 @@ class RoleEditPage extends React.Component {
getRole() {
RoleBackend.getRole(this.state.organizationName, this.state.roleName)
.then((res) => {
if (res === null) {
if (res.data === null) {
this.props.history.push("/404");
return;
}
@@ -53,11 +53,11 @@ class RoleEditPage extends React.Component {
}
this.setState({
role: res,
role: res.data,
});
this.getUsers(res.owner);
this.getRoles(res.owner);
this.getUsers(this.state.organizationName);
this.getRoles(this.state.organizationName);
});
}
@@ -65,7 +65,7 @@ class RoleEditPage extends React.Component {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
organizations: res.data || [],
});
});
}
@@ -77,8 +77,9 @@ class RoleEditPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
users: res,
users: res.data,
});
});
}
@@ -90,8 +91,9 @@ class RoleEditPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
roles: res,
roles: res.data,
});
});
}

View File

@@ -216,6 +216,12 @@ export const OtherProviderInfo = {
url: "https://platform.openai.com",
},
},
Web3: {
"MetaMask": {
logo: `${StaticBaseUrl}/img/social_metamask.svg`,
url: "https://metamask.io/",
},
},
};
export function initCountries() {
@@ -288,7 +294,7 @@ export function isProviderVisible(providerItem) {
return false;
}
if (providerItem.provider.category !== "OAuth" && providerItem.provider.category !== "SAML") {
if (!["OAuth", "SAML", "Web3"].includes(providerItem.provider.category)) {
return false;
}
@@ -891,6 +897,10 @@ export function getProviderTypeOptions(category) {
return ([
{id: "OpenAI API - GPT", name: "OpenAI API - GPT"},
]);
} else if (category === "Web3") {
return ([
{id: "MetaMask", name: "MetaMask"},
]);
} else {
return [];
}

View File

@@ -47,7 +47,7 @@ class SubscriptionEditPage extends React.Component {
getSubscription() {
SubscriptionBackend.getSubscription(this.state.organizationName, this.state.subscriptionName)
.then((res) => {
if (res === null) {
if (res.data === null) {
this.props.history.push("/404");
return;
}
@@ -58,11 +58,11 @@ class SubscriptionEditPage extends React.Component {
}
this.setState({
subscription: res,
subscription: res.data,
});
this.getUsers(res.owner);
this.getPlanes(res.owner);
this.getUsers(this.state.organizationName);
this.getPlanes(this.state.organizationName);
});
}
@@ -70,7 +70,7 @@ class SubscriptionEditPage extends React.Component {
PlanBackend.getPlans(organizationName)
.then((res) => {
this.setState({
planes: res,
planes: res.data,
});
});
}
@@ -82,8 +82,9 @@ class SubscriptionEditPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
users: res,
users: res.data,
});
});
}
@@ -92,7 +93,7 @@ class SubscriptionEditPage extends React.Component {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
organizations: res.data || [],
});
});
}

View File

@@ -48,7 +48,7 @@ class SyncerEditPage extends React.Component {
getSyncer() {
SyncerBackend.getSyncer("admin", this.state.syncerName)
.then((res) => {
if (res === null) {
if (res.data === null) {
this.props.history.push("/404");
return;
}
@@ -59,7 +59,7 @@ class SyncerEditPage extends React.Component {
}
this.setState({
syncer: res,
syncer: res.data,
});
});
}
@@ -68,7 +68,7 @@ class SyncerEditPage extends React.Component {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
organizations: res.data || [],
});
});
}

View File

@@ -36,7 +36,7 @@ class TokenEditPage extends React.Component {
getToken() {
TokenBackend.getToken("admin", this.state.tokenName)
.then((res) => {
if (res === null) {
if (res.data === null) {
this.props.history.push("/404");
return;
}
@@ -47,7 +47,7 @@ class TokenEditPage extends React.Component {
}
this.setState({
token: res,
token: res.data,
});
});
}

View File

@@ -36,6 +36,7 @@ import PopconfirmModal from "./common/modal/PopconfirmModal";
import {DeleteMfa} from "./backend/MfaBackend";
import {CheckCircleOutlined, HolderOutlined, UsergroupAddOutlined} from "@ant-design/icons";
import * as MfaBackend from "./backend/MfaBackend";
import AccountAvatar from "./account/AccountAvatar";
const {Option} = Select;
@@ -74,19 +75,20 @@ class UserEditPage extends React.Component {
getUser() {
UserBackend.getUser(this.state.organizationName, this.state.userName)
.then((data) => {
if (data === null) {
.then((res) => {
if (res.data === null) {
this.props.history.push("/404");
return;
}
if (data.status === null || data.status !== "error") {
this.setState({
user: data,
multiFactorAuths: data?.multiFactorAuths ?? [],
});
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
user: res.data,
multiFactorAuths: res.data?.multiFactorAuths ?? [],
loading: false,
});
});
@@ -107,7 +109,7 @@ class UserEditPage extends React.Component {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
organizations: res.data || [],
});
});
}
@@ -116,7 +118,7 @@ class UserEditPage extends React.Component {
ApplicationBackend.getApplicationsByOrganization("admin", organizationName)
.then((res) => {
this.setState({
applications: (res.msg === undefined) ? res : [],
applications: res.data || [],
});
});
}
@@ -128,12 +130,10 @@ class UserEditPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
application: res,
});
this.setState({
isGroupsVisible: res.organizationObj.accountItems?.some((item) => item.name === "Groups" && item.visible),
application: res.data,
isGroupsVisible: res.data?.organizationObj.accountItems?.some((item) => item.name === "Groups" && item.visible),
});
});
}
@@ -791,10 +791,23 @@ class UserEditPage extends React.Component {
{
(this.state.application === null || this.state.user === null) ? null : (
this.state.application?.providers.filter(providerItem => Setting.isProviderVisible(providerItem)).map((providerItem) =>
(providerItem.provider.category === "OAuth") ? (
<OAuthWidget key={providerItem.name} labelSpan={(Setting.isMobile()) ? 10 : 3} user={this.state.user} application={this.state.application} providerItem={providerItem} account={this.props.account} onUnlinked={() => {return this.unlinked();}} />
(providerItem.provider.category === "OAuth" || providerItem.provider.category === "Web3") ? (
<OAuthWidget
key={providerItem.name}
labelSpan={(Setting.isMobile()) ? 10 : 3}
user={this.state.user}
application={this.state.application}
providerItem={providerItem}
account={this.props.account}
onUnlinked={() => {return this.unlinked();}} />
) : (
<SamlWidget key={providerItem.name} labelSpan={(Setting.isMobile()) ? 10 : 3} user={this.state.user} application={this.state.application} providerItem={providerItem} onUnlinked={() => {return this.unlinked();}} />
<SamlWidget
key={providerItem.name}
labelSpan={(Setting.isMobile()) ? 10 : 3}
user={this.state.user}
application={this.state.application}
providerItem={providerItem}
onUnlinked={() => {return this.unlinked();}} />
)
)
)
@@ -971,12 +984,12 @@ class UserEditPage extends React.Component {
{
imgUrl ?
<a target="_blank" rel="noreferrer" href={imgUrl} style={{marginBottom: "10px"}}>
<img src={imgUrl} alt={imgUrl} height={90} style={{marginBottom: "20px"}} />
<AccountAvatar src={imgUrl} alt={imgUrl} size={90} style={{marginBottom: "20px"}} />
</a>
:
<Col style={{height: "78%", border: "1px dotted grey", borderRadius: 3, marginBottom: 5}}>
<div style={{fontSize: 30, margin: 10}}>+</div>
<div style={{verticalAlign: "middle", marginBottom: 10}}>{`请上传${title}...`}</div>
<div style={{verticalAlign: "middle", marginBottom: 10}}>{`Upload ${title}...`}</div>
</Col>
}
<CropperDivModal disabled={disabled} tag={tag} setTitle={set} buttonText={`${title}...`} title={title} user={this.state.user} organization={this.state.organizations.find(organization => organization.name === this.state.organizationName)} />

View File

@@ -23,6 +23,7 @@ import * as UserBackend from "./backend/UserBackend";
import i18next from "i18next";
import BaseListPage from "./BaseListPage";
import PopconfirmModal from "./common/modal/PopconfirmModal";
import AccountAvatar from "./account/AccountAvatar";
class UserListPage extends BaseListPage {
constructor(props) {
@@ -270,7 +271,7 @@ class UserListPage extends BaseListPage {
render: (text, record, index) => {
return (
<a target="_blank" rel="noreferrer" href={text}>
<img referrerPolicy="no-referrer" src={text} alt={text} width={50} />
<AccountAvatar referrerPolicy="no-referrer" src={text} alt={text} size={50} />
</a>
);
},

View File

@@ -122,14 +122,14 @@ class WebhookEditPage extends React.Component {
getWebhook() {
WebhookBackend.getWebhook("admin", this.state.webhookName)
.then((webhook) => {
if (webhook === null) {
.then((res) => {
if (res.data === null) {
this.props.history.push("/404");
return;
}
this.setState({
webhook: webhook,
webhook: res.data,
});
});
}
@@ -138,7 +138,7 @@ class WebhookEditPage extends React.Component {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
organizations: res.data || [],
});
});
}

View File

@@ -0,0 +1,36 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from "react";
import {MetaMaskAvatar} from "react-metamask-avatar";
class AccountAvatar extends React.Component {
render() {
const {src, size} = this.props;
// The avatar for Metamask account is directly generated by an algorithm based on the address
// src = "metamask:0xC304b2cC0Be8E9ce10fF3Afd34820Ed306A23600";
const matchMetaMask = src.match(/^metamask:(\w+)$/);
if (matchMetaMask) {
const address = matchMetaMask[1];
return (
<MetaMaskAvatar address={address} size={size} />
);
}
return (
<img width={size} height={size} src={src} />
);
}
}
export default AccountAvatar;

View File

@@ -95,6 +95,12 @@ class AuthCallback extends React.Component {
if (code === null) {
code = params.get("authCode");
}
// The code for Metamask is the JSON-serialized string of Web3AuthToken
// Due to the limited length of URLs, we only pass the web3AuthTokenKey
if (code === null) {
code = params.get("web3AuthTokenKey");
code = localStorage.getItem(code);
}
// Steam don't use code, so we should use all params as code.
if (isSteam !== null && code === null) {
code = this.props.location.search;

View File

@@ -68,7 +68,7 @@ class ForgetPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
this.onUpdateApplication(res);
this.onUpdateApplication(res.data);
});
}
getApplicationObj() {

View File

@@ -14,6 +14,9 @@
import {createButton} from "react-social-login-buttons";
import {StaticBaseUrl} from "../Setting";
import {useGoogleOneTapLogin} from "react-google-one-tap-login";
import * as Setting from "../Setting";
import * as Provider from "./Provider";
function Icon({width = 24, height = 24, color}) {
return <img src={`${StaticBaseUrl}/buttons/google.svg`} alt="Sign in with Google" />;
@@ -29,4 +32,29 @@ const config = {
const GoogleLoginButton = createButton(config);
export function GoogleOneTapLoginVirtualButton(prop) {
const application = prop.application;
const providerConf = prop.providerConf;
// https://stackoverflow.com/questions/62281579/google-one-tap-sign-in-ui-not-displayed-after-clicking-the-close-button
// document.cookie = "g_state=;path=/;expires=Thu, 01 Jan 1970 00:00:01 GMT";
useGoogleOneTapLogin({
googleAccountConfigs: {
client_id: providerConf.provider.clientId,
},
onError: (error) => {
Setting.showMessage("error", error);
},
onSuccess: (response) => {
const code = "GoogleIdToken-" + JSON.stringify(response);
const authUrlParams = new URLSearchParams(Provider.getAuthUrl(application, providerConf.provider, "signup"));
const state = authUrlParams.get("state");
let redirectUri = authUrlParams.get("redirect_uri");
redirectUri = `${redirectUri}?state=${state}&code=${encodeURIComponent(code)}`;
Setting.goToLink(redirectUri);
},
disableCancelOnUnmount: true,
});
}
export default GoogleLoginButton;

View File

@@ -36,7 +36,7 @@ import {CaptchaModal} from "../common/modal/CaptchaModal";
import {CaptchaRule} from "../common/modal/CaptchaModal";
import RedirectForm from "../common/RedirectForm";
import {MfaAuthVerifyForm, NextMfa, RequiredMfa} from "./mfa/MfaAuthVerifyForm";
import {GoogleOneTapLoginVirtualButton} from "./GoogleLoginButton";
class LoginPage extends React.Component {
constructor(props) {
super(props);
@@ -170,10 +170,15 @@ class LoginPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
this.onUpdateApplication(res);
this.onUpdateApplication(res.data);
});
} else {
OrganizationBackend.getDefaultApplication("admin", this.state.owner)
let redirectUri = "";
if (this.state.type === "cas") {
const casParams = Util.getCasParameters();
redirectUri = casParams.service;
}
OrganizationBackend.getDefaultApplication("admin", this.state.owner, this.state.type, redirectUri)
.then((res) => {
if (res.status === "ok") {
const application = res.data;
@@ -183,9 +188,9 @@ class LoginPage extends React.Component {
});
} else {
this.onUpdateApplication(null);
Setting.showMessage("error", res.msg);
this.props.history.push("/404");
this.setState({
msg: res.msg,
});
}
});
}
@@ -418,6 +423,16 @@ class LoginPage extends React.Component {
}
}
renderOtherFormProvider(application) {
for (const providerConf of application.providers) {
if (providerConf.provider?.type === "Google" && providerConf.rule === "OneTap" && this.props.preview !== "auto") {
return (
<GoogleOneTapLoginVirtualButton application={application} providerConf={providerConf} />
);
}
}
}
renderForm(application) {
if (this.state.msg !== null) {
return Util.renderMessage(this.state.msg);
@@ -575,6 +590,9 @@ class LoginPage extends React.Component {
return ProviderButton.renderProviderLogo(providerItem.provider, application, 30, 5, "small", this.props.location);
})
}
{
this.renderOtherFormProvider(application)
}
</Form.Item>
</Form>
);
@@ -596,6 +614,9 @@ class LoginPage extends React.Component {
return ProviderButton.renderProviderLogo(providerItem.provider, application, 40, 10, "big", this.props.location);
})
}
{
this.renderOtherFormProvider(application)
}
<div>
<br />
{

View File

@@ -69,7 +69,7 @@ class MfaSetupPage extends React.Component {
return;
}
this.setState({
application: res,
application: res.data,
});
} else {
Setting.showMessage("error", i18next.t("mfa:Failed to get application"));

View File

@@ -63,7 +63,7 @@ class PromptPage extends React.Component {
}
this.setState({
user: res,
user: res.data,
});
});
}
@@ -80,9 +80,9 @@ class PromptPage extends React.Component {
return;
}
this.onUpdateApplication(res);
this.onUpdateApplication(res.data);
this.setState({
application: res,
application: res.data,
});
});
}

View File

@@ -317,6 +317,10 @@ const authInfo = {
scope: "user:read",
endpoint: "https://zoom.us/oauth/authorize",
},
MetaMask: {
scope: "",
endpoint: "",
},
};
export function getProviderUrl(provider) {
@@ -459,5 +463,7 @@ export function getAuthUrl(application, provider, method) {
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${scope}&state=${state}&grant_options[]=per-user`;
} else if (provider.type === "Twitter" || provider.type === "Fitbit") {
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}&code_challenge=${codeChallenge}&code_challenge_method=S256`;
} else if (provider.type === "MetaMask") {
return `${redirectUri}?state=${state}`;
}
}

View File

@@ -17,6 +17,7 @@ import i18next from "i18next";
import * as Provider from "./Provider";
import {getProviderLogoURL} from "../Setting";
import {GithubLoginButton, GoogleLoginButton} from "react-social-login-buttons";
import {authViaMetaMask} from "./Web3Auth";
import QqLoginButton from "./QqLoginButton";
import FacebookLoginButton from "./FacebookLoginButton";
import WeiboLoginButton from "./WeiboLoginButton";
@@ -117,6 +118,12 @@ function goToSamlUrl(provider, location) {
});
}
export function goToWeb3Url(application, provider, method) {
if (provider.type === "MetaMask") {
authViaMetaMask(application, provider, method);
}
}
export function renderProviderLogo(provider, application, width, margin, size, location) {
if (size === "small") {
if (provider.category === "OAuth") {
@@ -153,6 +160,12 @@ export function renderProviderLogo(provider, application, width, margin, size, l
<img width={width} height={width} src={getProviderLogoURL(provider)} alt={provider.displayName} style={{margin: margin}} />
</a>
);
} else if (provider.category === "Web3") {
return (
<a key={provider.displayName} onClick={() => goToWeb3Url(application, provider, "signup")}>
<img width={width} height={width} src={getProviderLogoURL(provider)} alt={provider.displayName} style={{margin: margin}} />
</a>
);
}
} else if (provider.type === "Custom") {
// style definition
@@ -192,6 +205,16 @@ export function renderProviderLogo(provider, application, width, margin, size, l
</a>
</div>
);
} else if (provider.category === "Web3") {
return (
<div key={provider.displayName} style={{marginBottom: "10px"}}>
<a onClick={() => goToWeb3Url(application, provider, "signup")}>
{
getSigninButton(provider)
}
</a>
</div>
);
} else {
return (
<div key={provider.displayName} style={{marginBottom: "10px"}}>

View File

@@ -48,9 +48,10 @@ class ResultPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
this.onUpdateApplication(res);
this.onUpdateApplication(res.data);
this.setState({
application: res,
application: res.data,
});
});
}

View File

@@ -114,7 +114,7 @@ class SignupPage extends React.Component {
return;
}
this.onUpdateApplication(res);
this.onUpdateApplication(res.data);
});
}

149
web/src/auth/Web3Auth.js Normal file
View File

@@ -0,0 +1,149 @@
// // Copyright 2023 The Casdoor Authors. All Rights Reserved.
// //
// // Licensed under the Apache License, Version 2.0 (the "License");
// // you may not use this file except in compliance with the License.
// // You may obtain a copy of the License at
// //
// // http://www.apache.org/licenses/LICENSE-2.0
// //
// // Unless required by applicable law or agreed to in writing, software
// // distributed under the License is distributed on an "AS IS" BASIS,
// // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// // See the License for the specific language governing permissions and
// // limitations under the License.
import {goToLink, showMessage} from "../Setting";
import i18next from "i18next";
import {v4 as uuidv4} from "uuid";
import {SignTypedDataVersion, recoverTypedSignature} from "@metamask/eth-sig-util";
import {getAuthUrl} from "./Provider";
import {Buffer} from "buffer";
// import {toChecksumAddress} from "ethereumjs-util";
global.Buffer = Buffer;
export function generateNonce() {
const nonce = uuidv4();
return nonce;
}
export function getWeb3AuthTokenKey(address) {
return `Web3AuthToken_${address}`;
}
export function setWeb3AuthToken(token) {
const key = getWeb3AuthTokenKey(token.address);
localStorage.setItem(key, JSON.stringify(token));
}
export function getWeb3AuthToken(address) {
const key = getWeb3AuthTokenKey(address);
return JSON.parse(localStorage.getItem(key));
}
export function delWeb3AuthToken(address) {
const key = getWeb3AuthTokenKey(address);
localStorage.removeItem(key);
}
export function clearWeb3AuthToken() {
const keys = Object.keys(localStorage);
keys.forEach(key => {
if (key.startsWith("Web3AuthToken_")) {
localStorage.removeItem(key);
}
});
}
export function detectMetaMaskPlugin() {
// check if ethereum extension MetaMask is installed
return window.ethereum && window.ethereum.isMetaMask;
}
export function requestEthereumAccount() {
const method = "eth_requestAccounts";
const selectedAccount = window.ethereum.request({method})
.then((accounts) => {
return accounts[0];
});
return selectedAccount;
}
export function signEthereumTypedData(from, nonce) {
// https://docs.metamask.io/wallet/how-to/sign-data/
const date = new Date();
const typedData = JSON.stringify({
domain: {
chainId: window.ethereum.chainId,
name: "Casdoor",
version: "1",
},
message: {
prompt: "In order to authenticate to this website, sign this request and your public address will be sent to the server in a verifiable way.",
nonce: nonce,
createAt: `${date.toLocaleString()}`,
},
primaryType: "AuthRequest",
types: {
EIP712Domain: [
{name: "name", type: "string"},
{name: "version", type: "string"},
{name: "chainId", type: "uint256"},
],
AuthRequest: [
{name: "prompt", type: "string"},
{name: "nonce", type: "string"},
{name: "createAt", type: "string"},
],
},
});
const method = "eth_signTypedData_v4";
const params = [from, typedData];
return window.ethereum.request({method, params})
.then((sign) => {
return {
address: from,
createAt: Math.floor(date.getTime() / 1000),
typedData: typedData,
signature: sign,
};
});
}
export function checkEthereumSignedTypedData(token) {
if (token === undefined || token === null) {
return false;
}
if (token.address && token.typedData && token.signature) {
const recoveredAddr = recoverTypedSignature({
data: JSON.parse(token.typedData),
signature: token.signature,
version: SignTypedDataVersion.V4,
});
// const recoveredAddr = token.address;
return recoveredAddr === token.address;
// return toChecksumAddress(recoveredAddr) === toChecksumAddress(token.address);
}
return false;
}
export async function authViaMetaMask(application, provider, method) {
if (!detectMetaMaskPlugin()) {
showMessage("error", `${i18next.t("login:MetaMask plugin not detected")}`);
return;
}
try {
const account = await requestEthereumAccount();
let token = getWeb3AuthToken(account);
if (!checkEthereumSignedTypedData(token)) {
const nonce = generateNonce();
token = await signEthereumTypedData(account, nonce);
setWeb3AuthToken(token);
}
const redirectUri = `${getAuthUrl(application, provider, method)}&web3AuthTokenKey=${getWeb3AuthTokenKey(account)}`;
goToLink(redirectUri);
} catch (err) {
showMessage("error", `${i18next.t("login:Failed to obtain MetaMask authorization")}: ${err.message}`);
}
}

View File

@@ -0,0 +1,71 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import * as Setting from "../Setting";
export function getEnforcers(owner, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
return fetch(`${Setting.ServerUrl}/api/get-enforcers?owner=${owner}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, {
method: "GET",
credentials: "include",
headers: {
"Accept-Language": Setting.getAcceptLanguage(),
},
}).then(res => res.json());
}
export function getEnforcer(owner, name) {
return fetch(`${Setting.ServerUrl}/api/get-enforcer?id=${owner}/${encodeURIComponent(name)}`, {
method: "GET",
credentials: "include",
headers: {
"Accept-Language": Setting.getAcceptLanguage(),
},
}).then(res => res.json());
}
export function updateEnforcer(owner, name, enforcer) {
const newEnforcer = Setting.deepCopy(enforcer);
return fetch(`${Setting.ServerUrl}/api/update-enforcer?id=${owner}/${encodeURIComponent(name)}`, {
method: "POST",
credentials: "include",
body: JSON.stringify(newEnforcer),
headers: {
"Accept-Language": Setting.getAcceptLanguage(),
},
}).then(res => res.json());
}
export function addEnforcer(enforcer) {
const newEnforcer = Setting.deepCopy(enforcer);
return fetch(`${Setting.ServerUrl}/api/add-enforcer`, {
method: "POST",
credentials: "include",
body: JSON.stringify(newEnforcer),
headers: {
"Accept-Language": Setting.getAcceptLanguage(),
},
}).then(res => res.json());
}
export function deleteEnforcer(enforcer) {
const newEnforcer = Setting.deepCopy(enforcer);
return fetch(`${Setting.ServerUrl}/api/delete-enforcer`, {
method: "POST",
credentials: "include",
body: JSON.stringify(newEnforcer),
headers: {
"Accept-Language": Setting.getAcceptLanguage(),
},
}).then(res => res.json());
}

View File

@@ -70,8 +70,8 @@ export function deleteOrganization(organization) {
}).then(res => res.json());
}
export function getDefaultApplication(owner, name) {
return fetch(`${Setting.ServerUrl}/api/get-default-application?id=${owner}/${encodeURIComponent(name)}`, {
export function getDefaultApplication(owner, name, type = "", redirectUri = "") {
return fetch(`${Setting.ServerUrl}/api/get-default-application?id=${owner}/${encodeURIComponent(name)}&type=${type}&redirectUri=${redirectUri}`, {
method: "GET",
credentials: "include",
headers: {

Some files were not shown because too many files have changed in this diff Show More