Compare commits

...

34 Commits

Author SHA1 Message Date
UsherFall
451fc9034f fix: fix bug in PayPal payment provider (#1959) 2023-06-12 13:43:37 +08:00
Yaodong Yu
0e14a2597e feat: Add tree structure to organization page (#1910)
* rebase master

* feat: add group in userEditPage

* feat: use id as the pk

* feat: add groups item in user

* feat: add tree component

* rebase

* feat: ui

* fix: fix some bug

* fix: route

* fix: ui

* fix: improve ui
2023-06-12 09:27:16 +08:00
Yang Luo
ff87c4ea33 feat: fix createDatabase arg not recognized bug 2023-06-12 01:57:58 +08:00
Yang Luo
4f5396c70e Check error for CreateDatabase() 2023-06-12 01:47:26 +08:00
Yang Luo
3c30222fce Fix payment owner issue 2023-06-12 00:34:41 +08:00
Yang Luo
2d04731622 Provide default value for logConfig 2023-06-10 15:59:56 +08:00
Yang Luo
e0d2bc3dc9 Return error in GetProviderFromContext() 2023-06-10 15:51:26 +08:00
Jiawei Chen
0bda29f143 feat: show 404 error for non-existent objects in edit pages 2023-06-10 01:56:15 +08:00
Yang Luo
05703720c5 Add Custom to resourceType 2023-06-09 21:52:30 +08:00
hsluoyz
cc566bf31f Move DoMigration() after CreateTables() 2023-06-09 09:36:20 +08:00
XDTD
e93d8c19d9 feat: resolve user pages malfunction after using tableNamePrefix (#1945) 2023-06-08 00:43:05 +08:00
Yang Luo
f2e3182a69 Fix null value in backend Translate() 2023-06-07 02:17:48 +08:00
Yang Luo
f934531083 Fix organization search in some pages 2023-06-06 20:53:45 +08:00
XDTD
e1c0af345f feat: resolve casdoor as SAML SP with keycloak login not work bug (#1937)
* fix: resolve casdoor as SAML SP with keycloak login not work

* Update provider.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-06-06 15:19:00 +08:00
Yang Luo
3b3bfe39f9 Fix user field bug 2023-06-06 14:59:50 +08:00
Alex OvsInc
18cc952f8e feat: Customization of the initialization file (#1936) 2023-06-05 21:00:28 +08:00
Yang Luo
43439bc8c6 Apply tableNamePrefix before migration 2023-06-05 00:30:48 +08:00
Yang Luo
9a2800e3b3 Add error to Enforce() 2023-06-04 17:29:34 +08:00
Yaodong Yu
fdaad2b608 chore: refactor enforce() handler and update Swagger docs (#1931)
* chore: add swaggerof enforce api

* Update enforcer.go

* Update string.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-06-04 17:19:58 +08:00
Yang Luo
2d43fe0b39 Fix cert empty issue in GetSamlMeta() 2023-06-04 01:25:18 +08:00
Yaodong Yu
5d776a3ce6 fix: handle error of list in frontend (#1930) 2023-06-04 01:21:24 +08:00
Yang Luo
5ec7a54bf8 Add description fields to objects 2023-06-04 01:21:17 +08:00
Yang Luo
0c118477e8 Add groups to UserInfo 2023-06-04 01:21:10 +08:00
Yang Luo
c858d0e0b0 Fix model page bug 2023-06-03 10:35:58 +08:00
Yang Luo
9cffb43265 Fix subscription page bugs 2023-06-03 10:15:29 +08:00
Yang Luo
51a76518ad Init adapter in getEnabledSyncerForOrganization() 2023-06-03 09:23:36 +08:00
hsluoyz
08dbbab70e feat: revert "feat: fix the bug that sycner does not initialize" (#1926)
This reverts commit ec3c24ba68.
2023-06-03 09:17:34 +08:00
Yang Luo
0ec22ae6ff Fix null bug in getLanguage() 2023-06-03 00:29:08 +08:00
Yaodong Yu
ec3c24ba68 feat: fix the bug that sycner does not initialize (#1924) 2023-06-03 00:15:28 +08:00
Yang Luo
ed688efdbb Fix bug in org user list page 2023-06-02 22:09:18 +08:00
Yang Luo
06543a01d3 Add organization to /userinfo 2023-06-02 21:51:05 +08:00
Yang Luo
70c372c3f7 Fix Provider API responses 2023-06-02 11:49:38 +08:00
Yang Luo
b1b3184e75 Speed up user pagination query 2023-06-01 22:55:44 +08:00
Yang Luo
5349fa7ff3 Speed up object.DoMigration() 2023-06-01 22:25:19 +08:00
114 changed files with 2689 additions and 411 deletions

View File

@@ -124,7 +124,6 @@ p, *, *, GET, /api/get-release, *, *
p, *, *, GET, /api/get-default-application, *, *
p, *, *, GET, /api/get-prometheus-info, *, *
p, *, *, *, /api/metrics, *, *
p, *, *, GET, /api/get-subscriptions, *, *
p, *, *, GET, /api/get-pricing, *, *
p, *, *, GET, /api/get-plan, *, *
p, *, *, GET, /api/get-organization-names, *, *

View File

@@ -21,4 +21,5 @@ isDemoMode = false
batchSize = 100
ldapServerPort = 389
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
logConfig = {"filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}
logConfig = {"filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}
initDataFile = "./init_data.json"

View File

@@ -66,6 +66,8 @@ func GetConfigString(key string) string {
if res == "" {
if key == "staticBaseUrl" {
res = "https://cdn.casbin.org"
} else if key == "logConfig" {
res = "{\"filename\": \"logs/casdoor.log\", \"maxdays\":99999, \"perm\":\"0770\"}"
}
}
@@ -108,10 +110,10 @@ func GetLanguage(language string) string {
return "en"
}
if len(language) < 2 {
if len(language) != 2 {
return "en"
} else {
return language[0:2]
return language
}
}

View File

@@ -528,7 +528,7 @@ func (c *ApiController) Login() {
}
properties := map[string]string{}
count, err := object.GetUserCount(application.Organization, "", "")
count, err := object.GetUserCount(application.Organization, "", "", "")
if err != nil {
c.ResponseError(err.Error())
return

View File

@@ -21,6 +21,16 @@ import (
"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")
@@ -34,33 +44,59 @@ func (c *ApiController) Enforce() {
}
if permissionId != "" {
c.ResponseOk(object.Enforce(permissionId, &request))
enforceResult, err := object.Enforce(permissionId, &request)
if err != nil {
c.ResponseError(err.Error())
return
}
res := []bool{}
res = append(res, enforceResult)
c.ResponseOk(res)
return
}
permissions := make([]*object.Permission, 0)
res := []bool{}
permissions := []*object.Permission{}
if modelId != "" {
owner, modelName := util.GetOwnerAndNameFromId(modelId)
permissions, err = object.GetPermissionsByModel(owner, modelName)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
} else {
} else if resourceId != "" {
permissions, err = object.GetPermissionsByResource(resourceId)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
} else {
c.ResponseError(c.T("general:Missing parameter"))
return
}
res := []bool{}
for _, permission := range permissions {
res = append(res, object.Enforce(permission.GetId(), &request))
enforceResult, err := object.Enforce(permission.GetId(), &request)
if err != nil {
c.ResponseError(err.Error())
return
}
res = append(res, enforceResult)
}
c.Data["json"] = res
c.ServeJSON()
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")
@@ -68,26 +104,47 @@ func (c *ApiController) BatchEnforce() {
var requests []object.CasbinRequest
err := json.Unmarshal(c.Ctx.Input.RequestBody, &requests)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
if permissionId != "" {
c.Data["json"] = object.BatchEnforce(permissionId, &requests)
c.ServeJSON()
} else {
owner, modelName := util.GetOwnerAndNameFromId(modelId)
permissions, err := object.GetPermissionsByModel(owner, modelName)
enforceResult, err := object.BatchEnforce(permissionId, &requests)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
res := [][]bool{}
for _, permission := range permissions {
res = append(res, object.BatchEnforce(permission.GetId(), &requests))
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{}
for _, permission := range permissions {
enforceResult, err := object.BatchEnforce(permission.GetId(), &requests)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(res)
res = append(res, enforceResult)
}
c.ResponseOk(res)
}
func (c *ApiController) GetAllObjects() {

147
controllers/group.go Normal file
View File

@@ -0,0 +1,147 @@
// 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
package controllers
import (
"encoding/json"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
// GetGroups
// @Title GetGroups
// @Tag Group API
// @Description get groups
// @Param owner query string true "The owner of groups"
// @Success 200 {array} object.Group The Response object
// @router /get-groups [get]
func (c *ApiController) GetGroups() {
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")
withTree := c.Input().Get("withTree")
if limit == "" || page == "" {
groups, err := object.GetGroups(owner)
if err != nil {
c.ResponseError(err.Error())
return
} else {
if withTree == "true" {
c.ResponseOk(object.ConvertToTreeData(groups, owner))
return
}
c.ResponseOk(groups)
}
} else {
limit := util.ParseInt(limit)
count, err := object.GetGroupCount(owner, field, value)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
groups, err := object.GetPaginationGroups(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil {
c.ResponseError(err.Error())
return
} else {
c.ResponseOk(groups, paginator.Nums())
}
}
}
// GetGroup
// @Title GetGroup
// @Tag Group API
// @Description get group
// @Param id query string true "The id ( owner/name ) of the group"
// @Success 200 {object} object.Group The Response object
// @router /get-group [get]
func (c *ApiController) GetGroup() {
id := c.Input().Get("id")
group, err := object.GetGroup(id)
if err != nil {
c.ResponseError(err.Error())
} else {
c.ResponseOk(group)
}
}
// UpdateGroup
// @Title UpdateGroup
// @Tag Group API
// @Description update group
// @Param id query string true "The id ( owner/name ) of the group"
// @Param body body object.Group true "The details of the group"
// @Success 200 {object} controllers.Response The Response object
// @router /update-group [post]
func (c *ApiController) UpdateGroup() {
id := c.Input().Get("id")
var group object.Group
err := json.Unmarshal(c.Ctx.Input.RequestBody, &group)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.UpdateGroup(id, &group))
c.ServeJSON()
}
// AddGroup
// @Title AddGroup
// @Tag Group API
// @Description add group
// @Param body body object.Group true "The details of the group"
// @Success 200 {object} controllers.Response The Response object
// @router /add-group [post]
func (c *ApiController) AddGroup() {
var group object.Group
err := json.Unmarshal(c.Ctx.Input.RequestBody, &group)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.AddGroup(&group))
c.ServeJSON()
}
// DeleteGroup
// @Title DeleteGroup
// @Tag Group API
// @Description delete group
// @Param body body object.Group true "The details of the group"
// @Success 200 {object} controllers.Response The Response object
// @router /delete-group [post]
func (c *ApiController) DeleteGroup() {
var group object.Group
err := json.Unmarshal(c.Ctx.Input.RequestBody, &group)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(wrapActionResponse(object.DeleteGroup(&group)))
}

View File

@@ -34,6 +34,7 @@ import (
// @router /get-messages [get]
func (c *ApiController) GetMessages() {
owner := c.Input().Get("owner")
organization := c.Input().Get("organization")
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
@@ -41,7 +42,7 @@ func (c *ApiController) GetMessages() {
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
chat := c.Input().Get("chat")
organization := c.Input().Get("organization")
if limit == "" || page == "" {
var messages []*object.Message
var err error

View File

@@ -180,12 +180,12 @@ func (c *ApiController) GetDefaultApplication() {
// @Title GetOrganizationNames
// @Tag Organization API
// @Param owner query string true "owner"
// @Description get all organization names
// @Description get all organization name and displayName
// @Success 200 {array} object.Organization The Response object
// @router /get-organization-names [get]
func (c *ApiController) GetOrganizationNames() {
owner := c.Input().Get("owner")
organizationNames, err := object.GetOrganizationsByFields(owner, "name")
organizationNames, err := object.GetOrganizationsByFields(owner, []string{"name", "display_name"}...)
if err != nil {
c.ResponseError(err.Error())
return

View File

@@ -31,6 +31,7 @@ import (
// @router /get-payments [get]
func (c *ApiController) GetPayments() {
owner := c.Input().Get("owner")
organization := c.Input().Get("organization")
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
@@ -48,13 +49,13 @@ func (c *ApiController) GetPayments() {
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
count, err := object.GetPaymentCount(owner, field, value)
count, err := object.GetPaymentCount(owner, organization, field, value)
if err != nil {
panic(err)
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
payments, err := object.GetPaginationPayments(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
payments, err := object.GetPaginationPayments(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil {
panic(err)
}
@@ -177,10 +178,11 @@ func (c *ApiController) NotifyPayment() {
providerName := c.Ctx.Input.Param(":provider")
productName := c.Ctx.Input.Param(":product")
paymentName := c.Ctx.Input.Param(":payment")
orderId := c.Ctx.Input.Param("order")
body := c.Ctx.Input.RequestBody
err, errorResponse := object.NotifyPayment(c.Ctx.Request, body, owner, providerName, productName, paymentName)
err, errorResponse := object.NotifyPayment(c.Ctx.Request, body, owner, providerName, productName, paymentName, orderId)
_, err2 := c.Ctx.ResponseWriter.Write([]byte(errorResponse))
if err2 != nil {

View File

@@ -180,11 +180,11 @@ func (c *ApiController) BuyProduct() {
return
}
payUrl, err := object.BuyProduct(id, providerName, user, host)
payUrl, orderId, err := object.BuyProduct(id, providerName, user, host)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(payUrl)
c.ResponseOk(payUrl, orderId)
}

View File

@@ -49,8 +49,7 @@ func (c *ApiController) GetProviders() {
panic(err)
}
c.Data["json"] = object.GetMaskedProviders(providers, isMaskEnabled)
c.ServeJSON()
c.ResponseOk(object.GetMaskedProviders(providers, isMaskEnabled))
} else {
limit := util.ParseInt(limit)
count, err := object.GetProviderCount(owner, field, value)
@@ -96,8 +95,7 @@ func (c *ApiController) GetGlobalProviders() {
panic(err)
}
c.Data["json"] = object.GetMaskedProviders(globalProviders, isMaskEnabled)
c.ServeJSON()
c.ResponseOk(object.GetMaskedProviders(globalProviders, isMaskEnabled))
} else {
limit := util.ParseInt(limit)
count, err := object.GetGlobalProviderCount(field, value)
@@ -138,8 +136,7 @@ func (c *ApiController) GetProvider() {
return
}
c.Data["json"] = object.GetMaskedProvider(provider, isMaskEnabled)
c.ServeJSON()
c.ResponseOk(object.GetMaskedProvider(provider, isMaskEnabled))
}
// UpdateProvider

View File

@@ -139,8 +139,9 @@ func (c *ApiController) DeleteResource() {
return
}
provider, _, ok := c.GetProviderFromContext("Storage")
if !ok {
provider, err := c.GetProviderFromContext("Storage")
if err != nil {
c.ResponseError(err.Error())
return
}
@@ -187,8 +188,9 @@ func (c *ApiController) UploadResource() {
return
}
provider, _, ok := c.GetProviderFromContext("Storage")
if !ok {
provider, err := c.GetProviderFromContext("Storage")
if err != nil {
c.ResponseError(err.Error())
return
}

View File

@@ -69,9 +69,9 @@ func (c *ApiController) SendEmail() {
} else {
// called by Casdoor SDK via Client ID & Client Secret, so the used Email provider will be the application' Email provider or the default Email provider
var ok bool
provider, _, ok = c.GetProviderFromContext("Email")
if !ok {
provider, err = c.GetProviderFromContext("Email")
if err != nil {
c.ResponseError(err.Error())
return
}
}
@@ -127,13 +127,14 @@ func (c *ApiController) SendEmail() {
// @Success 200 {object} Response object
// @router /api/send-sms [post]
func (c *ApiController) SendSms() {
provider, _, ok := c.GetProviderFromContext("SMS")
if !ok {
provider, err := c.GetProviderFromContext("SMS")
if err != nil {
c.ResponseError(err.Error())
return
}
var smsForm SmsForm
err := json.Unmarshal(c.Ctx.Input.RequestBody, &smsForm)
err = json.Unmarshal(c.Ctx.Input.RequestBody, &smsForm)
if err != nil {
c.ResponseError(err.Error())
return

View File

@@ -80,6 +80,7 @@ func (c *ApiController) GetGlobalUsers() {
// @router /get-users [get]
func (c *ApiController) GetUsers() {
owner := c.Input().Get("owner")
groupId := c.Input().Get("groupId")
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
@@ -88,6 +89,16 @@ func (c *ApiController) GetUsers() {
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
if groupId != "" {
maskedUsers, err := object.GetMaskedUsers(object.GetUsersByGroup(groupId))
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(maskedUsers)
return
}
maskedUsers, err := object.GetMaskedUsers(object.GetUsers(owner))
if err != nil {
panic(err)
@@ -97,14 +108,14 @@ func (c *ApiController) GetUsers() {
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
count, err := object.GetUserCount(owner, field, value)
count, err := object.GetUserCount(owner, field, value, groupId)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
users, err := object.GetPaginationUsers(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
users, err := object.GetPaginationUsers(owner, paginator.Offset(), limit, field, value, sortField, sortOrder, groupId)
if err != nil {
c.ResponseError(err.Error())
return
@@ -287,7 +298,7 @@ func (c *ApiController) AddUser() {
return
}
count, err := object.GetUserCount("", "", "")
count, err := object.GetUserCount("", "", "", "")
if err != nil {
c.ResponseError(err.Error())
return
@@ -505,7 +516,7 @@ func (c *ApiController) GetUserCount() {
var count int64
var err error
if isOnline == "" {
count, err = object.GetUserCount(owner, "", "")
count, err = object.GetUserCount(owner, "", "", "")
} else {
count, err = object.GetOnlineUserCount(owner, util.ParseInt(isOnline))
}

View File

@@ -139,47 +139,46 @@ func (c *ApiController) IsMaskedEnabled() (bool, bool) {
return true, isMaskEnabled
}
func (c *ApiController) GetProviderFromContext(category string) (*object.Provider, *object.User, bool) {
func (c *ApiController) GetProviderFromContext(category string) (*object.Provider, error) {
providerName := c.Input().Get("provider")
if providerName != "" {
provider, err := object.GetProvider(util.GetId("admin", providerName))
if err != nil {
panic(err)
return nil, err
}
if provider == nil {
c.ResponseError(fmt.Sprintf(c.T("util:The provider: %s is not found"), providerName))
return nil, nil, false
err = fmt.Errorf(c.T("util:The provider: %s is not found"), providerName)
return nil, err
}
return provider, nil, true
return provider, nil
}
userId, ok := c.RequireSignedIn()
if !ok {
return nil, nil, false
return nil, fmt.Errorf(c.T("general:Please login first"))
}
application, user, err := object.GetApplicationByUserId(userId)
application, err := object.GetApplicationByUserId(userId)
if err != nil {
panic(err)
return nil, err
}
if application == nil {
c.ResponseError(fmt.Sprintf(c.T("util:No application is found for userId: %s"), userId))
return nil, nil, false
return nil, fmt.Errorf(c.T("util:No application is found for userId: %s"), userId)
}
provider, err := application.GetProviderByCategory(category)
if err != nil {
panic(err)
return nil, err
}
if provider == nil {
c.ResponseError(fmt.Sprintf(c.T("util:No provider for category: %s is found for application: %s"), category, application.Name))
return nil, nil, false
return nil, fmt.Errorf(c.T("util:No provider for category: %s is found for application: %s"), category, application.Name)
}
return provider, user, true
return provider, nil
}
func checkQuotaForApplication(count int) error {

11
main.go
View File

@@ -30,13 +30,18 @@ import (
"github.com/casdoor/casdoor/util"
)
func main() {
createDatabase := flag.Bool("createDatabase", false, "true if you need Casdoor to create database")
func getCreateDatabaseFlag() bool {
res := flag.Bool("createDatabase", false, "true if you need Casdoor to create database")
flag.Parse()
return *res
}
func main() {
createDatabase := getCreateDatabaseFlag()
object.InitAdapter()
object.CreateTables(createDatabase)
object.DoMigration()
object.CreateTables(*createDatabase)
object.InitDb()
object.InitFromFile()

View File

@@ -41,18 +41,26 @@ func InitConfig() {
beego.BConfig.WebConfig.Session.SessionOn = true
InitAdapter()
DoMigration()
CreateTables(true)
DoMigration()
}
func InitAdapter() {
adapter = NewAdapter(conf.GetConfigString("driverName"), conf.GetConfigDataSourceName(), conf.GetConfigString("dbName"))
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
tbMapper := core.NewPrefixMapper(core.SnakeMapper{}, tableNamePrefix)
adapter.Engine.SetTableMapper(tbMapper)
}
func CreateTables(createDatabase bool) {
if createDatabase {
adapter.CreateDatabase()
err := adapter.CreateDatabase()
if err != nil {
panic(err)
}
}
adapter.createTable()
}
@@ -122,10 +130,6 @@ func (a *Adapter) createTable() {
showSql := conf.GetConfigBool("showSql")
a.Engine.ShowSQL(showSql)
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
tbMapper := core.NewPrefixMapper(core.SnakeMapper{}, tableNamePrefix)
a.Engine.SetTableMapper(tbMapper)
err := a.Engine.Sync2(new(Organization))
if err != nil {
panic(err)
@@ -136,6 +140,16 @@ func (a *Adapter) createTable() {
panic(err)
}
err = a.Engine.Sync2(new(Group))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(UserGroupRelation))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Role))
if err != nil {
panic(err)
@@ -280,3 +294,52 @@ func GetSession(owner string, offset, limit int, field, value, sortField, sortOr
}
return session
}
func GetSessionForUser(owner string, offset, limit int, field, value, sortField, sortOrder string) *xorm.Session {
session := adapter.Engine.Prepare()
if offset != -1 && limit != -1 {
session.Limit(limit, offset)
}
if owner != "" {
if offset == -1 {
session = session.And("owner=?", owner)
} else {
session = session.And("a.owner=?", owner)
}
}
if field != "" && value != "" {
if filterField(field) {
if offset != -1 {
field = fmt.Sprintf("a.%s", field)
}
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
}
}
if sortField == "" || sortOrder == "" {
sortField = "created_time"
}
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
tableName := tableNamePrefix + "user"
if offset == -1 {
if sortOrder == "ascend" {
session = session.Asc(util.SnakeString(sortField))
} else {
session = session.Desc(util.SnakeString(sortField))
}
} else {
if sortOrder == "ascend" {
session = session.Alias("a").
Join("INNER", []string{tableName, "b"}, "a.owner = b.owner and a.name = b.name").
Select("b.*").
Asc("a." + util.SnakeString(sortField))
} else {
session = session.Alias("a").
Join("INNER", []string{tableName, "b"}, "a.owner = b.owner and a.name = b.name").
Select("b.*").
Desc("a." + util.SnakeString(sortField))
}
}
return session
}

View File

@@ -235,16 +235,16 @@ func GetApplicationByUser(user *User) (*Application, error) {
}
}
func GetApplicationByUserId(userId string) (application *Application, user *User, err error) {
func GetApplicationByUserId(userId string) (application *Application, err error) {
owner, name := util.GetOwnerAndNameFromId(userId)
if owner == "app" {
application, err = getApplication("admin", name)
return
}
user, err = GetUser(userId)
user, err := GetUser(userId)
if err != nil {
return nil, nil, err
return nil, err
}
application, err = GetApplicationByUser(user)
return

193
object/group.go Normal file
View File

@@ -0,0 +1,193 @@
// 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 (
"fmt"
"github.com/casdoor/casdoor/util"
"github.com/xorm-io/core"
)
type Group struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk unique" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
Id string `xorm:"varchar(100) not null index" json:"id"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Manager string `xorm:"varchar(100)" json:"manager"`
ContactEmail string `xorm:"varchar(100)" json:"contactEmail"`
Type string `xorm:"varchar(100)" json:"type"`
ParentGroupId string `xorm:"varchar(100)" json:"parentGroupId"`
IsTopGroup bool `xorm:"bool" json:"isTopGroup"`
Users *[]string `xorm:"-" json:"users"`
Title string `json:"title,omitempty"`
Key string `json:"key,omitempty"`
Children []*Group `json:"children,omitempty"`
IsEnabled bool `json:"isEnabled"`
}
type GroupNode struct{}
func GetGroupCount(owner, field, value string) (int64, error) {
session := GetSession(owner, -1, -1, field, value, "", "")
count, err := session.Count(&Group{})
if err != nil {
return 0, err
}
return count, nil
}
func GetGroups(owner string) ([]*Group, error) {
groups := []*Group{}
err := adapter.Engine.Desc("created_time").Find(&groups, &Group{Owner: owner})
if err != nil {
return nil, err
}
return groups, nil
}
func GetPaginationGroups(owner string, offset, limit int, field, value, sortField, sortOrder string) ([]*Group, error) {
groups := []*Group{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&groups)
if err != nil {
return nil, err
}
return groups, nil
}
func getGroup(owner string, name string) (*Group, error) {
if owner == "" || name == "" {
return nil, nil
}
group := Group{Owner: owner, Name: name}
existed, err := adapter.Engine.Get(&group)
if err != nil {
return nil, err
}
if existed {
return &group, nil
} else {
return nil, nil
}
}
func getGroupById(id string) (*Group, error) {
if id == "" {
return nil, nil
}
group := Group{Id: id}
existed, err := adapter.Engine.Get(&group)
if err != nil {
return nil, err
}
if existed {
return &group, nil
} else {
return nil, nil
}
}
func GetGroup(id string) (*Group, error) {
owner, name := util.GetOwnerAndNameFromId(id)
return getGroup(owner, name)
}
func UpdateGroup(id string, group *Group) (bool, error) {
owner, name := util.GetOwnerAndNameFromId(id)
oldGroup, err := getGroup(owner, name)
if oldGroup == nil {
return false, err
}
group.UpdatedTime = util.GetCurrentTime()
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(group)
if err != nil {
return false, err
}
return affected != 0, nil
}
func AddGroup(group *Group) (bool, error) {
if group.Id == "" {
group.Id = util.GenerateId()
}
affected, err := adapter.Engine.Insert(group)
if err != nil {
return false, err
}
return affected != 0, nil
}
func AddGroups(groups []*Group) (bool, error) {
if len(groups) == 0 {
return false, nil
}
affected, err := adapter.Engine.Insert(groups)
if err != nil {
return false, err
}
return affected != 0, nil
}
func DeleteGroup(group *Group) (bool, error) {
affected, err := adapter.Engine.ID(core.PK{group.Owner, group.Name}).Delete(&Group{})
if err != nil {
return false, err
}
return affected != 0, nil
}
func (group *Group) GetId() string {
return fmt.Sprintf("%s/%s", group.Owner, group.Name)
}
func ConvertToTreeData(groups []*Group, parentGroupId string) []*Group {
treeData := []*Group{}
for _, group := range groups {
if group.ParentGroupId == parentGroupId {
node := &Group{
Title: group.DisplayName,
Key: group.Name,
Type: group.Type,
Owner: group.Owner,
Id: group.Id,
}
children := ConvertToTreeData(groups, group.Id)
if len(children) > 0 {
node.Children = children
}
treeData = append(treeData, node)
}
}
return treeData
}

View File

@@ -61,6 +61,7 @@ func getBuiltInAccountItems() []*AccountItem {
{Name: "Signup application", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "Roles", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
{Name: "Permissions", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
{Name: "Groups", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
{Name: "3rd-party logins", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
{Name: "Properties", Visible: false, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},

View File

@@ -14,7 +14,10 @@
package object
import "github.com/casdoor/casdoor/util"
import (
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util"
)
type InitData struct {
Organizations []*Organization `json:"organizations"`
@@ -35,7 +38,12 @@ type InitData struct {
}
func InitFromFile() {
initData, err := readInitDataFromFile("./init_data.json")
initDataFile := conf.GetConfigString("initDataFile")
if initDataFile == "" {
return
}
initData, err := readInitDataFromFile(initDataFile)
if err != nil {
panic(err)
}

View File

@@ -22,20 +22,13 @@ import (
type Migrator_1_314_0_PR_1841 struct{}
func (*Migrator_1_314_0_PR_1841) IsMigrationNeeded() bool {
users := []*User{}
err := adapter.Engine.Table("user").Find(&users)
count, err := adapter.Engine.Where("password_type=?", "").Count(&User{})
if err != nil {
// table doesn't exist
return false
}
for _, u := range users {
if u.PasswordType != "" {
return false
}
}
return true
return count > 100
}
func (*Migrator_1_314_0_PR_1841) DoMigration() *migrate.Migration {

View File

@@ -27,6 +27,7 @@ type Model struct {
Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Description string `xorm:"varchar(100)" json:"description"`
ModelText string `xorm:"mediumtext" json:"modelText"`
IsEnabled bool `json:"isEnabled"`

View File

@@ -334,6 +334,13 @@ func organizationChangeTrigger(oldName string, newName string) error {
return err
}
group := new(Group)
group.Owner = newName
_, err = session.Where("owner=?", oldName).Update(group)
if err != nil {
return err
}
role := new(Role)
_, err = adapter.Engine.Where("owner=?", oldName).Get(role)
if err != nil {

View File

@@ -56,9 +56,9 @@ type Payment struct {
InvoiceUrl string `xorm:"varchar(255)" json:"invoiceUrl"`
}
func GetPaymentCount(owner, field, value string) (int64, error) {
func GetPaymentCount(owner, organization, field, value string) (int64, error) {
session := GetSession(owner, -1, -1, field, value, "", "")
return session.Count(&Payment{})
return session.Count(&Payment{Organization: organization})
}
func GetPayments(owner string) ([]*Payment, error) {
@@ -81,10 +81,10 @@ func GetUserPayments(owner string, organization string, user string) ([]*Payment
return payments, nil
}
func GetPaginationPayments(owner string, offset, limit int, field, value, sortField, sortOrder string) ([]*Payment, error) {
func GetPaginationPayments(owner, organization string, offset, limit int, field, value, sortField, sortOrder string) ([]*Payment, error) {
payments := []*Payment{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&payments)
err := session.Find(&payments, &Payment{Organization: organization})
if err != nil {
return nil, err
}
@@ -149,7 +149,7 @@ func DeletePayment(payment *Payment) (bool, error) {
return affected != 0, nil
}
func notifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string) (*Payment, error, string) {
func notifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string, orderId string) (*Payment, error, string) {
provider, err := getProvider(owner, providerName)
if err != nil {
panic(err)
@@ -180,7 +180,7 @@ func notifyPayment(request *http.Request, body []byte, owner string, providerNam
return payment, err, pProvider.GetResponseError(err)
}
productDisplayName, paymentName, price, productName, providerName, err := pProvider.Notify(request, body, cert.AuthorityPublicKey)
productDisplayName, paymentName, price, productName, providerName, err := pProvider.Notify(request, body, cert.AuthorityPublicKey, orderId)
if err != nil {
return payment, err, pProvider.GetResponseError(err)
}
@@ -199,8 +199,8 @@ func notifyPayment(request *http.Request, body []byte, owner string, providerNam
return payment, err, pProvider.GetResponseError(err)
}
func NotifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string) (error, string) {
payment, err, errorResponse := notifyPayment(request, body, owner, providerName, productName, paymentName)
func NotifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string, orderId string) (error, string) {
payment, err, errorResponse := notifyPayment(request, body, owner, providerName, productName, paymentName, orderId)
if payment != nil {
if err != nil {
payment.State = "Error"

View File

@@ -27,6 +27,7 @@ type Permission struct {
Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Description string `xorm:"varchar(100)" json:"description"`
Users []string `xorm:"mediumtext" json:"users"`
Roles []string `xorm:"mediumtext" json:"roles"`

View File

@@ -241,33 +241,29 @@ func removePolicies(permission *Permission) {
type CasbinRequest = []interface{}
func Enforce(permissionId string, request *CasbinRequest) bool {
func Enforce(permissionId string, request *CasbinRequest) (bool, error) {
permission, err := GetPermission(permissionId)
if err != nil {
panic(err)
return false, err
}
enforcer := getEnforcer(permission)
allow, err := enforcer.Enforce(*request...)
if err != nil {
panic(err)
}
return allow
return enforcer.Enforce(*request...)
}
func BatchEnforce(permissionId string, requests *[]CasbinRequest) []bool {
func BatchEnforce(permissionId string, requests *[]CasbinRequest) ([]bool, error) {
permission, err := GetPermission(permissionId)
if err != nil {
panic(err)
res := []bool{}
for i := 0; i < len(*requests); i++ {
res = append(res, false)
}
return res, err
}
enforcer := getEnforcer(permission)
allow, err := enforcer.BatchEnforce(*requests)
if err != nil {
panic(err)
}
return allow
return enforcer.BatchEnforce(*requests)
}
func getAllValues(userId string, fn func(enforcer *casbin.Enforcer) []string) []string {

View File

@@ -31,7 +31,6 @@ type Pricing struct {
Plans []string `xorm:"mediumtext" json:"plans"`
IsEnabled bool `json:"isEnabled"`
HasTrial bool `json:"hasTrial"`
TrialDuration int `json:"trialDuration"`
Application string `xorm:"varchar(100)" json:"application"`

View File

@@ -156,24 +156,24 @@ func (product *Product) getProvider(providerId string) (*Provider, error) {
return provider, nil
}
func BuyProduct(id string, providerName string, user *User, host string) (string, error) {
func BuyProduct(id string, providerName string, user *User, host string) (string, string, error) {
product, err := GetProduct(id)
if err != nil {
return "", err
return "", "", err
}
if product == nil {
return "", fmt.Errorf("the product: %s does not exist", id)
return "", "", fmt.Errorf("the product: %s does not exist", id)
}
provider, err := product.getProvider(providerName)
if err != nil {
return "", err
return "", "", err
}
pProvider, _, err := provider.getPaymentProvider()
if err != nil {
return "", err
return "", "", err
}
owner := product.Owner
@@ -186,9 +186,9 @@ func BuyProduct(id string, providerName string, user *User, host string) (string
returnUrl := fmt.Sprintf("%s/payments/%s/result", originFrontend, paymentName)
notifyUrl := fmt.Sprintf("%s/api/notify-payment/%s/%s/%s/%s", originBackend, owner, providerName, productName, paymentName)
payUrl, err := pProvider.Pay(providerName, productName, payerName, paymentName, productDisplayName, product.Price, returnUrl, notifyUrl)
payUrl, orderId, err := pProvider.Pay(providerName, productName, payerName, paymentName, productDisplayName, product.Price, product.Currency, returnUrl, notifyUrl)
if err != nil {
return "", err
return "", "", err
}
payment := Payment{
@@ -217,14 +217,14 @@ func BuyProduct(id string, providerName string, user *User, host string) (string
affected, err := AddPayment(&payment)
if err != nil {
return "", err
return "", "", err
}
if !affected {
return "", fmt.Errorf("failed to add payment: %s", util.StructToJson(payment))
return "", "", fmt.Errorf("failed to add payment: %s", util.StructToJson(payment))
}
return payUrl, err
return payUrl, orderId, err
}
func ExtendProductWithProviders(product *Product) error {

View File

@@ -38,7 +38,7 @@ func TestProduct(t *testing.T) {
paymentName := util.GenerateTimeId()
returnUrl := ""
notifyUrl := ""
payUrl, err := pProvider.Pay(provider.Name, product.Name, "alice", paymentName, product.DisplayName, product.Price, returnUrl, notifyUrl)
payUrl, _, err := pProvider.Pay(provider.Name, product.Name, "alice", paymentName, product.DisplayName, product.Price, product.Currency, returnUrl, notifyUrl)
if err != nil {
panic(err)
}

View File

@@ -225,8 +225,10 @@ func UpdateProvider(id string, provider *Provider) (bool, error) {
session = session.Omit("client_secret2")
}
provider.Endpoint = util.GetEndPoint(provider.Endpoint)
provider.IntranetEndpoint = util.GetEndPoint(provider.IntranetEndpoint)
if provider.Type != "Keycloak" {
provider.Endpoint = util.GetEndPoint(provider.Endpoint)
provider.IntranetEndpoint = util.GetEndPoint(provider.IntranetEndpoint)
}
affected, err := session.Update(provider)
if err != nil {
@@ -237,8 +239,10 @@ func UpdateProvider(id string, provider *Provider) (bool, error) {
}
func AddProvider(provider *Provider) (bool, error) {
provider.Endpoint = util.GetEndPoint(provider.Endpoint)
provider.IntranetEndpoint = util.GetEndPoint(provider.IntranetEndpoint)
if provider.Type != "Keycloak" {
provider.Endpoint = util.GetEndPoint(provider.Endpoint)
provider.IntranetEndpoint = util.GetEndPoint(provider.IntranetEndpoint)
}
affected, err := adapter.Engine.Insert(provider)
if err != nil {

View File

@@ -29,6 +29,7 @@ type Role struct {
Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Description string `xorm:"varchar(100)" json:"description"`
Users []string `xorm:"mediumtext" json:"users"`
Roles []string `xorm:"mediumtext" json:"roles"`

View File

@@ -23,6 +23,7 @@ import (
"encoding/json"
"encoding/pem"
"encoding/xml"
"errors"
"fmt"
"io"
"time"
@@ -195,6 +196,10 @@ func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, e
return nil, err
}
if cert == nil {
return nil, errors.New("please set a cert for the application first")
}
block, _ := pem.Decode([]byte(cert.Certificate))
certificate := base64.StdEncoding.EncodeToString(block.Bytes)

View File

@@ -38,6 +38,7 @@ func getEnabledSyncerForOrganization(organization string) (*Syncer, error) {
for _, syncer := range syncers {
if syncer.Organization == organization && syncer.IsEnabled {
syncer.initAdapter()
return syncer, nil
}
}

View File

@@ -34,7 +34,7 @@ const (
type User 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"`
CreatedTime string `xorm:"varchar(100) index" json:"createdTime"`
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
Id string `xorm:"varchar(100) index" json:"id"`
@@ -77,6 +77,7 @@ type User struct {
SignupApplication string `xorm:"varchar(100)" json:"signupApplication"`
Hash string `xorm:"varchar(100)" json:"hash"`
PreHash string `xorm:"varchar(100)" json:"preHash"`
Groups []string `xorm:"varchar(1000)" json:"groups"`
CreatedIp string `xorm:"varchar(100)" json:"createdIp"`
LastSigninTime string `xorm:"varchar(100)" json:"lastSigninTime"`
@@ -173,15 +174,16 @@ type User struct {
}
type Userinfo struct {
Sub string `json:"sub"`
Iss string `json:"iss"`
Aud string `json:"aud"`
Name string `json:"preferred_username,omitempty"`
DisplayName string `json:"name,omitempty"`
Email string `json:"email,omitempty"`
Avatar string `json:"picture,omitempty"`
Address string `json:"address,omitempty"`
Phone string `json:"phone,omitempty"`
Sub string `json:"sub"`
Iss string `json:"iss"`
Aud string `json:"aud"`
Name string `json:"preferred_username,omitempty"`
DisplayName string `json:"name,omitempty"`
Email string `json:"email,omitempty"`
Avatar string `json:"picture,omitempty"`
Address string `json:"address,omitempty"`
Phone string `json:"phone,omitempty"`
Groups []string `json:"groups,omitempty"`
}
type ManagedAccount struct {
@@ -208,7 +210,7 @@ func GetGlobalUsers() ([]*User, error) {
func GetPaginationGlobalUsers(offset, limit int, field, value, sortField, sortOrder string) ([]*User, error) {
users := []*User{}
session := GetSession("", offset, limit, field, value, sortField, sortOrder)
session := GetSessionForUser("", offset, limit, field, value, sortField, sortOrder)
err := session.Find(&users)
if err != nil {
return nil, err
@@ -217,8 +219,20 @@ func GetPaginationGlobalUsers(offset, limit int, field, value, sortField, sortOr
return users, nil
}
func GetUserCount(owner, field, value string) (int64, error) {
func GetUserCount(owner, field, value string, groupId string) (int64, error) {
session := GetSession(owner, -1, -1, field, value, "", "")
if groupId != "" {
group, err := GetGroup(groupId)
if group == nil || err != nil {
return 0, err
}
// users count in group
return adapter.Engine.Table("user_group_relation").Join("INNER", "user AS u", "user_group_relation.user_id = u.id").
Where("user_group_relation.group_id = ?", group.Id).
Count(&UserGroupRelation{})
}
return session.Count(&User{})
}
@@ -256,13 +270,47 @@ func GetSortedUsers(owner string, sorter string, limit int) ([]*User, error) {
return users, nil
}
func GetPaginationUsers(owner string, offset, limit int, field, value, sortField, sortOrder string) ([]*User, error) {
func GetPaginationUsers(owner string, offset, limit int, field, value, sortField, sortOrder string, groupId string) ([]*User, error) {
users := []*User{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
if groupId != "" {
group, err := GetGroup(groupId)
if group == nil || err != nil {
return []*User{}, err
}
session := adapter.Engine.Prepare()
if offset != -1 && limit != -1 {
session.Limit(limit, offset)
}
err = session.Table("user_group_relation").Join("INNER", "user AS u", "user_group_relation.user_id = u.id").
Where("user_group_relation.group_id = ?", group.Id).
Find(&users)
return users, err
}
session := GetSessionForUser(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&users)
if err != nil {
return nil, err
}
return users, nil
}
func GetUsersByGroup(groupId string) ([]*User, error) {
group, err := GetGroup(groupId)
if group == nil || err != nil {
return []*User{}, err
}
users := []*User{}
err = adapter.Engine.Table("user_group_relation").Join("INNER", "user AS u", "user_group_relation.user_id = u.id").
Where("user_group_relation.group_id = ?", group.Id).
Find(&users)
if err != nil {
return nil, err
}
return users, nil
}
@@ -478,7 +526,7 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
"owner", "display_name", "avatar",
"location", "address", "country_code", "region", "language", "affiliation", "title", "homepage", "bio", "tag", "language", "gender", "birthday", "education", "score", "karma", "ranking", "signup_application",
"is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts",
"signin_wrong_times", "last_signin_wrong_time",
"signin_wrong_times", "last_signin_wrong_time", "groups",
"github", "google", "qq", "wechat", "facebook", "dingtalk", "weibo", "gitee", "linkedin", "wecom", "lark", "gitlab", "adfs",
"baidu", "alipay", "casdoor", "infoflow", "apple", "azuread", "slack", "steam", "bilibili", "okta", "douyin", "line", "amazon",
"auth0", "battlenet", "bitbucket", "box", "cloudfoundry", "dailymotion", "deezer", "digitalocean", "discord", "dropbox",
@@ -492,7 +540,7 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
columns = append(columns, "name", "email", "phone", "country_code")
}
affected, err := adapter.Engine.ID(core.PK{owner, name}).Cols(columns...).Update(user)
affected, err := updateUser(oldUser, user, columns)
if err != nil {
return false, err
}
@@ -500,6 +548,35 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
return affected != 0, nil
}
func updateUser(oldUser, user *User, columns []string) (int64, error) {
session := adapter.Engine.NewSession()
defer session.Close()
session.Begin()
if util.ContainsString(columns, "groups") {
affected, err := updateGroupRelation(session, user)
if err != nil {
session.Rollback()
return affected, err
}
}
affected, err := session.ID(core.PK{oldUser.Owner, oldUser.Name}).Cols(columns...).Update(user)
if err != nil {
session.Rollback()
return affected, err
}
err = session.Commit()
if err != nil {
session.Rollback()
return 0, err
}
return affected, nil
}
func UpdateUserForAllFields(id string, user *User) (bool, error) {
var err error
owner, name := util.GetOwnerAndNameFromId(id)
@@ -554,6 +631,10 @@ func AddUser(user *User) (bool, error) {
return false, nil
}
if user.PasswordType == "" && organization.PasswordType != "" {
user.PasswordType = organization.PasswordType
}
user.UpdateUserPassword(organization)
err = user.UpdateUserHash()
@@ -575,7 +656,7 @@ func AddUser(user *User) (bool, error) {
}
}
count, err := GetUserCount(user.Owner, "", "")
count, err := GetUserCount(user.Owner, "", "", "")
if err != nil {
return false, err
}
@@ -678,6 +759,7 @@ func GetUserInfo(user *User, scope string, aud string, host string) *Userinfo {
resp.Name = user.Name
resp.DisplayName = user.DisplayName
resp.Avatar = user.Avatar
resp.Groups = []string{user.Owner}
}
if strings.Contains(scope, "email") {
resp.Email = user.Email

52
object/user_group.go Normal file
View File

@@ -0,0 +1,52 @@
package object
import (
"errors"
"github.com/xorm-io/xorm"
)
type UserGroupRelation struct {
UserId string `xorm:"varchar(100) notnull pk" json:"userId"`
GroupId string `xorm:"varchar(100) notnull pk" json:"groupId"`
CreatedTime string `xorm:"created" json:"createdTime"`
UpdatedTime string `xorm:"updated" json:"updatedTime"`
}
func updateGroupRelation(session *xorm.Session, user *User) (int64, error) {
groupIds := user.Groups
physicalGroupCount, err := session.Where("type = ?", "Physical").In("id", user.Groups).Count(Group{})
if err != nil {
return 0, err
}
if physicalGroupCount > 1 {
return 0, errors.New("user can only be in one physical group")
}
groups := []*Group{}
err = session.In("id", groupIds).Find(&groups)
if err != nil {
return 0, err
}
if len(groups) == 0 || len(groups) != len(groupIds) {
return 0, nil
}
_, err = session.Delete(&UserGroupRelation{UserId: user.Id})
if err != nil {
return 0, err
}
relations := []*UserGroupRelation{}
for _, group := range groups {
relations = append(relations, &UserGroupRelation{UserId: user.Id, GroupId: group.Id})
}
_, err = session.Insert(relations)
if err != nil {
return 0, err
}
return 1, nil
}

View File

@@ -295,6 +295,13 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
itemsChanged = append(itemsChanged, item)
}
oldUserGroupsJson, _ := json.Marshal(oldUser.Groups)
newUserGroupsJson, _ := json.Marshal(newUser.Groups)
if string(oldUserGroupsJson) != string(newUserGroupsJson) {
item := GetAccountItemByName("Groups", organization)
itemsChanged = append(itemsChanged, item)
}
if oldUser.IsAdmin != newUser.IsAdmin {
item := GetAccountItemByName("Is admin", organization)
itemsChanged = append(itemsChanged, item)

View File

@@ -45,7 +45,7 @@ func NewAlipayPaymentProvider(appId string, appCertificate string, appPrivateKey
return pp, nil
}
func (pp *AlipayPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) {
func (pp *AlipayPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
// pp.Client.DebugSwitch = gopay.DebugOn
bm := gopay.BodyMap{}
@@ -62,12 +62,12 @@ func (pp *AlipayPaymentProvider) Pay(providerName string, productName string, pa
payUrl, err := pp.Client.TradePagePay(context.Background(), bm)
if err != nil {
return "", err
return "", "", err
}
return payUrl, nil
return payUrl, "", nil
}
func (pp *AlipayPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) {
func (pp *AlipayPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (string, string, float64, string, string, error) {
bm, err := alipay.ParseNotifyToBodyMap(request)
if err != nil {
return "", "", 0, "", "", err

View File

@@ -26,12 +26,12 @@ func NewDummyPaymentProvider() (*DummyPaymentProvider, error) {
return pp, nil
}
func (pp *DummyPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) {
func (pp *DummyPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
payUrl := fmt.Sprintf("/payments/%s/result", paymentName)
return payUrl, nil
return payUrl, "", nil
}
func (pp *DummyPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) {
func (pp *DummyPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (string, string, float64, string, string, error) {
return "", "", 0, "", "", nil
}

View File

@@ -153,7 +153,7 @@ func (pp *GcPaymentProvider) doPost(postBytes []byte) ([]byte, error) {
return respBytes, nil
}
func (pp *GcPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) {
func (pp *GcPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
payReqInfo := GcPayReqInfo{
OrderDate: util.GenerateSimpleTimeId(),
OrderNo: paymentName,
@@ -168,7 +168,7 @@ func (pp *GcPaymentProvider) Pay(providerName string, productName string, payerN
b, err := json.Marshal(payReqInfo)
if err != nil {
return "", err
return "", "", err
}
body := GcRequestBody{
@@ -184,39 +184,39 @@ func (pp *GcPaymentProvider) Pay(providerName string, productName string, payerN
bodyBytes, err := json.Marshal(body)
if err != nil {
return "", err
return "", "", err
}
respBytes, err := pp.doPost(bodyBytes)
if err != nil {
return "", err
return "", "", err
}
var respBody GcResponseBody
err = json.Unmarshal(respBytes, &respBody)
if err != nil {
return "", err
return "", "", err
}
if respBody.ReturnCode != "SUCCESS" {
return "", fmt.Errorf("%s: %s", respBody.ReturnCode, respBody.ReturnMsg)
return "", "", fmt.Errorf("%s: %s", respBody.ReturnCode, respBody.ReturnMsg)
}
payRespInfoBytes, err := base64.StdEncoding.DecodeString(respBody.Data)
if err != nil {
return "", err
return "", "", err
}
var payRespInfo GcPayRespInfo
err = json.Unmarshal(payRespInfoBytes, &payRespInfo)
if err != nil {
return "", err
return "", "", err
}
return payRespInfo.PayUrl, nil
return payRespInfo.PayUrl, "", nil
}
func (pp *GcPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) {
func (pp *GcPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (string, string, float64, string, string, error) {
reqBody := GcRequestBody{}
m, err := url.ParseQuery(string(body))
if err != nil {

View File

@@ -16,10 +16,13 @@ package pp
import (
"context"
"fmt"
"errors"
"net/http"
"strconv"
"github.com/plutov/paypal/v4"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/paypal"
"github.com/go-pay/gopay/pkg/util"
)
type PaypalPaymentProvider struct {
@@ -29,7 +32,7 @@ type PaypalPaymentProvider struct {
func NewPaypalPaymentProvider(clientID string, secret string) (*PaypalPaymentProvider, error) {
pp := &PaypalPaymentProvider{}
client, err := paypal.NewClient(clientID, secret, paypal.APIBaseSandBox)
client, err := paypal.NewClient(clientID, secret, false)
if err != nil {
return nil, err
}
@@ -38,51 +41,62 @@ func NewPaypalPaymentProvider(clientID string, secret string) (*PaypalPaymentPro
return pp, nil
}
func (pp *PaypalPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) {
// pp.Client.SetLog(os.Stdout) // Set log to terminal stdout
func (pp *PaypalPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
// pp.Client.DebugSwitch = gopay.DebugOn // Set log to terminal stdout
receiverEmail := "sb-tmsqa26118644@business.example.com"
amount := paypal.AmountPayout{
Value: fmt.Sprintf("%.2f", price),
Currency: "USD",
}
description := fmt.Sprintf("%s-%s", providerName, productName)
payout := paypal.Payout{
SenderBatchHeader: &paypal.SenderBatchHeader{
EmailSubject: description,
},
Items: []paypal.PayoutItem{
{
RecipientType: "EMAIL",
Receiver: receiverEmail,
Amount: &amount,
Note: description,
SenderItemID: description,
},
priceStr := strconv.FormatFloat(price, 'f', 2, 64)
var pus []*paypal.PurchaseUnit
item := &paypal.PurchaseUnit{
ReferenceId: util.GetRandomString(16),
Amount: &paypal.Amount{
CurrencyCode: currency,
Value: priceStr,
},
Description: joinAttachString([]string{productDisplayName, productName, providerName}),
}
pus = append(pus, item)
_, err := pp.Client.GetAccessToken(context.Background())
bm := make(gopay.BodyMap)
bm.Set("intent", "CAPTURE")
bm.Set("purchase_units", pus)
bm.SetBodyMap("payment_source", func(b1 gopay.BodyMap) {
b1.SetBodyMap("paypal", func(b2 gopay.BodyMap) {
b2.Set("brand_name", "Casdoor")
b2.Set("return_url", returnUrl)
})
})
ppRsp, err := pp.Client.CreateOrder(context.Background(), bm)
if err != nil {
return "", err
return "", "", err
}
if ppRsp.Code != paypal.Success {
return "", "", errors.New(ppRsp.Error)
}
payoutResponse, err := pp.Client.CreatePayout(context.Background(), payout)
if err != nil {
return "", err
}
payUrl := payoutResponse.Links[0].Href
return payUrl, nil
return ppRsp.Response.Links[1].Href, ppRsp.Response.Id, nil
}
func (pp *PaypalPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) {
// The PayPal SDK does not directly support IPN verification.
// So, you need to implement this part according to PayPal's IPN guide.
return "", "", 0, "", "", nil
func (pp *PaypalPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (string, string, float64, string, string, error) {
ppRsp, err := pp.Client.OrderCapture(context.Background(), orderId, nil)
if err != nil {
return "", "", 0, "", "", err
}
if ppRsp.Code != paypal.Success {
return "", "", 0, "", "", errors.New(ppRsp.Error)
}
paymentName := ppRsp.Response.Id
price, err := strconv.ParseFloat(ppRsp.Response.PurchaseUnits[0].Amount.Value, 64)
if err != nil {
return "", "", 0, "", "", err
}
productDisplayName, productName, providerName, err := parseAttachString(ppRsp.Response.PurchaseUnits[0].Description)
if err != nil {
return "", "", 0, "", "", err
}
return productDisplayName, paymentName, price, productName, providerName, nil
}
func (pp *PaypalPaymentProvider) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) {

View File

@@ -17,8 +17,8 @@ package pp
import "net/http"
type PaymentProvider interface {
Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error)
Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error)
Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error)
Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (string, string, float64, string, string, error)
GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error)
GetResponseError(err error) string
}

View File

@@ -56,7 +56,7 @@ func NewWechatPaymentProvider(mchId string, apiV3Key string, appId string, mchCe
return pp, nil
}
func (pp *WechatPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) {
func (pp *WechatPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
// pp.Client.DebugSwitch = gopay.DebugOn
bm := gopay.BodyMap{}
@@ -73,17 +73,17 @@ func (pp *WechatPaymentProvider) Pay(providerName string, productName string, pa
wxRsp, err := pp.Client.V3TransactionNative(context.Background(), bm)
if err != nil {
return "", err
return "", "", err
}
if wxRsp.Code != wechat.Success {
return "", errors.New(wxRsp.Error)
return "", "", errors.New(wxRsp.Error)
}
return wxRsp.Response.CodeUrl, nil
return wxRsp.Response.CodeUrl, "", nil
}
func (pp *WechatPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) {
func (pp *WechatPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (string, string, float64, string, string, error) {
notifyReq, err := wechat.V3ParseNotify(request)
if err != nil {
panic(err)

View File

@@ -49,7 +49,7 @@ func AutoSigninFilter(ctx *context.Context) {
}
userId := util.GetId(token.Organization, token.User)
application, _, err := object.GetApplicationByUserId(fmt.Sprintf("app/%s", token.Application))
application, err := object.GetApplicationByUserId(fmt.Sprintf("app/%s", token.Application))
if err != nil {
panic(err)
}

View File

@@ -77,6 +77,12 @@ func initAPI() {
beego.Router("/api/delete-user", &controllers.ApiController{}, "POST:DeleteUser")
beego.Router("/api/upload-users", &controllers.ApiController{}, "POST:UploadUsers")
beego.Router("/api/get-groups", &controllers.ApiController{}, "GET:GetGroups")
beego.Router("/api/get-group", &controllers.ApiController{}, "GET:GetGroup")
beego.Router("/api/update-group", &controllers.ApiController{}, "POST:UpdateGroup")
beego.Router("/api/add-group", &controllers.ApiController{}, "POST:AddGroup")
beego.Router("/api/delete-group", &controllers.ApiController{}, "POST:DeleteGroup")
beego.Router("/api/get-roles", &controllers.ApiController{}, "GET:GetRoles")
beego.Router("/api/get-role", &controllers.ApiController{}, "GET:GetRole")
beego.Router("/api/update-role", &controllers.ApiController{}, "POST:UpdateRole")

View File

@@ -777,6 +777,46 @@
"operationId": "ApiController.HandleOfficialAccountEvent"
}
},
"/api/batch-enforce": {
"post": {
"tags": [
"Enforce API"
],
"description": "perform enforce",
"operationId": "ApiController.BatchEnforce",
"parameters": [
{
"in": "body",
"name": "body",
"description": "casbin request array",
"required": true,
"schema": {
"$ref": "#/definitions/object.CasbinRequest"
}
},
{
"in": "query",
"name": "permissionId",
"description": "permission id",
"type": "string"
},
{
"in": "query",
"name": "modelId",
"description": "model id",
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/buy-product": {
"post": {
"tags": [
@@ -1384,6 +1424,52 @@
}
}
},
"/api/enforce": {
"post": {
"tags": [
"Enforce API"
],
"description": "perform enforce",
"operationId": "ApiController.Enforce",
"parameters": [
{
"in": "body",
"name": "body",
"description": "casbin request",
"required": true,
"schema": {
"$ref": "#/definitions/object.CasbinRequest"
}
},
{
"in": "query",
"name": "permissionId",
"description": "permission id",
"type": "string"
},
{
"in": "query",
"name": "modelId",
"description": "model id",
"type": "string"
},
{
"in": "query",
"name": "resourceId",
"description": "resource id",
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/get-account": {
"get": {
"tags": [
@@ -1954,6 +2040,35 @@
}
}
},
"/api/get-organization-names": {
"get": {
"tags": [
"Organization API"
],
"description": "get all organization names",
"operationId": "ApiController.GetOrganizationNames",
"parameters": [
{
"in": "query",
"name": "owner",
"description": "owner",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/object.Organization"
}
}
}
}
}
},
"/api/get-organizations": {
"get": {
"tags": [
@@ -2826,7 +2941,6 @@
"in": "query",
"name": "id",
"description": "The id ( owner/name ) of the user",
"required": true,
"type": "string"
},
{
@@ -3062,6 +3176,23 @@
}
}
},
"/api/health": {
"get": {
"tags": [
"System API"
],
"description": "check if the system is live",
"operationId": "ApiController.Health",
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/invoice-payment": {
"post": {
"tags": [
@@ -4501,11 +4632,11 @@
}
},
"definitions": {
"1183.0x1400042eb70.false": {
"1225.0xc0002e2ae0.false": {
"title": "false",
"type": "object"
},
"1217.0x1400042eba0.false": {
"1260.0xc0002e2b10.false": {
"title": "false",
"type": "object"
},
@@ -4554,10 +4685,10 @@
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/1183.0x1400042eb70.false"
"$ref": "#/definitions/1225.0xc0002e2ae0.false"
},
"data2": {
"$ref": "#/definitions/1217.0x1400042eba0.false"
"$ref": "#/definitions/1260.0xc0002e2b10.false"
},
"msg": {
"type": "string"
@@ -4595,6 +4726,10 @@
"title": "JSONWebKey",
"type": "object"
},
"object.\u0026{179844 0xc000a02f90 false}": {
"title": "\u0026{179844 0xc000a02f90 false}",
"type": "object"
},
"object.AccountItem": {
"title": "AccountItem",
"type": "object",
@@ -4693,6 +4828,9 @@
"formCss": {
"type": "string"
},
"formCssMobile": {
"type": "string"
},
"formOffset": {
"type": "integer",
"format": "int64"
@@ -4715,6 +4853,9 @@
"name": {
"type": "string"
},
"orgChoiceMode": {
"type": "string"
},
"organization": {
"type": "string"
},
@@ -4772,6 +4913,13 @@
}
}
},
"object.CasbinRequest": {
"title": "CasbinRequest",
"type": "array",
"items": {
"$ref": "#/definitions/object.\u0026{179844 0xc000a02f90 false}"
}
},
"object.Cert": {
"title": "Cert",
"type": "object",
@@ -5008,6 +5156,18 @@
}
}
},
"object.MfaItem": {
"title": "MfaItem",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"rule": {
"type": "string"
}
}
},
"object.MfaProps": {
"title": "MfaProps",
"type": "object",
@@ -5190,6 +5350,12 @@
"masterPassword": {
"type": "string"
},
"mfaItems": {
"type": "array",
"items": {
"$ref": "#/definitions/object.MfaItem"
}
},
"name": {
"type": "string"
},
@@ -5395,9 +5561,18 @@
"displayName": {
"type": "string"
},
"isEnabled": {
"type": "boolean"
},
"name": {
"type": "string"
},
"options": {
"type": "array",
"items": {
"type": "string"
}
},
"owner": {
"type": "string"
},
@@ -5411,9 +5586,6 @@
},
"role": {
"type": "string"
},
"options": {
"type": "array"
}
}
},
@@ -5737,6 +5909,9 @@
"name": {
"type": "string"
},
"object": {
"type": "string"
},
"organization": {
"type": "string"
},
@@ -6341,6 +6516,9 @@
"passwordSalt": {
"type": "string"
},
"passwordType": {
"type": "string"
},
"patreon": {
"type": "string"
},
@@ -6505,6 +6683,9 @@
"name": {
"type": "string"
},
"organization": {
"type": "string"
},
"phone": {
"type": "string"
},
@@ -6634,4 +6815,4 @@
"type": "object"
}
}
}
}

View File

@@ -502,6 +502,32 @@ paths:
tags:
- HandleOfficialAccountEvent API
operationId: ApiController.HandleOfficialAccountEvent
/api/batch-enforce:
post:
tags:
- Enforce API
description: perform enforce
operationId: ApiController.BatchEnforce
parameters:
- in: body
name: body
description: casbin request array
required: true
schema:
$ref: '#/definitions/object.CasbinRequest'
- in: query
name: permissionId
description: permission id
type: string
- in: query
name: modelId
description: model id
type: string
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/buy-product:
post:
tags:
@@ -893,6 +919,36 @@ paths:
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/enforce:
post:
tags:
- Enforce API
description: perform enforce
operationId: ApiController.Enforce
parameters:
- in: body
name: body
description: casbin request
required: true
schema:
$ref: '#/definitions/object.CasbinRequest'
- in: query
name: permissionId
description: permission id
type: string
- in: query
name: modelId
description: model id
type: string
- in: query
name: resourceId
description: resource id
type: string
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/get-account:
get:
tags:
@@ -1267,6 +1323,25 @@ paths:
type: array
items:
$ref: '#/definitions/object.Application'
/api/get-organization-names:
get:
tags:
- Organization API
description: get all organization names
operationId: ApiController.GetOrganizationNames
parameters:
- in: query
name: owner
description: owner
required: true
type: string
responses:
"200":
description: The Response object
schema:
type: array
items:
$ref: '#/definitions/object.Organization'
/api/get-organizations:
get:
tags:
@@ -1841,7 +1916,6 @@ paths:
- in: query
name: id
description: The id ( owner/name ) of the user
required: true
type: string
- in: query
name: owner
@@ -1994,6 +2068,17 @@ paths:
type: array
items:
$ref: '#/definitions/object.Webhook'
/api/health:
get:
tags:
- System API
description: check if the system is live
operationId: ApiController.Health
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/invoice-payment:
post:
tags:
@@ -2940,10 +3025,10 @@ paths:
schema:
$ref: '#/definitions/Response'
definitions:
1183.0x1400042eb70.false:
1225.0xc0002e2ae0.false:
title: "false"
type: object
1217.0x1400042eba0.false:
1260.0xc0002e2b10.false:
title: "false"
type: object
LaravelResponse:
@@ -2979,9 +3064,9 @@ definitions:
type: object
properties:
data:
$ref: '#/definitions/1183.0x1400042eb70.false'
$ref: '#/definitions/1225.0xc0002e2ae0.false'
data2:
$ref: '#/definitions/1217.0x1400042eba0.false'
$ref: '#/definitions/1260.0xc0002e2b10.false'
msg:
type: string
name:
@@ -3005,6 +3090,9 @@ definitions:
jose.JSONWebKey:
title: JSONWebKey
type: object
object.&{179844 0xc000a02f90 false}:
title: '&{179844 0xc000a02f90 false}'
type: object
object.AccountItem:
title: AccountItem
type: object
@@ -3072,6 +3160,8 @@ definitions:
type: string
formCss:
type: string
formCssMobile:
type: string
formOffset:
type: integer
format: int64
@@ -3087,6 +3177,8 @@ definitions:
type: string
name:
type: string
orgChoiceMode:
type: string
organization:
type: string
organizationObj:
@@ -3124,6 +3216,11 @@ definitions:
$ref: '#/definitions/object.ThemeData'
tokenFormat:
type: string
object.CasbinRequest:
title: CasbinRequest
type: array
items:
$ref: '#/definitions/object.&{179844 0xc000a02f90 false}'
object.Cert:
title: Cert
type: object
@@ -3284,6 +3381,14 @@ definitions:
type: string
text:
type: string
object.MfaItem:
title: MfaItem
type: object
properties:
name:
type: string
rule:
type: string
object.MfaProps:
title: MfaProps
type: object
@@ -3407,6 +3512,10 @@ definitions:
type: string
masterPassword:
type: string
mfaItems:
type: array
items:
$ref: '#/definitions/object.MfaItem'
name:
type: string
owner:
@@ -3544,8 +3653,14 @@ definitions:
type: string
displayName:
type: string
isEnabled:
type: boolean
name:
type: string
options:
type: array
items:
type: string
owner:
type: string
pricePerMonth:
@@ -3556,8 +3671,6 @@ definitions:
format: double
role:
type: string
options:
type: array
object.Pricing:
title: Pricing
type: object
@@ -3775,6 +3888,8 @@ definitions:
type: string
name:
type: string
object:
type: string
organization:
type: string
owner:
@@ -4181,6 +4296,8 @@ definitions:
type: string
passwordSalt:
type: string
passwordType:
type: string
patreon:
type: string
paypal:
@@ -4291,6 +4408,8 @@ definitions:
type: string
name:
type: string
organization:
type: string
phone:
type: string
picture:

View File

@@ -50,6 +50,11 @@ class AdapterEditPage extends React.Component {
AdapterBackend.getAdapter("admin", this.state.adapterName)
.then((res) => {
if (res.status === "ok") {
if (res.data === null) {
this.props.history.push("/404");
return;
}
this.setState({
adapter: res.data,
});

View File

@@ -249,9 +249,11 @@ class AdapterListPage extends BaseListPage {
this.setState({loading: true});
AdapterBackend.getAdapters("admin", Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
this.setState({
loading: false,
});
if (res.status === "ok") {
this.setState({
loading: false,
data: res.data,
pagination: {
...params.pagination,
@@ -263,9 +265,10 @@ class AdapterListPage extends BaseListPage {
} else {
if (Setting.isResponseDenied(res)) {
this.setState({
loading: false,
isAuthorized: false,
});
} else {
Setting.showMessage("error", res.msg);
}
}
});

View File

@@ -15,6 +15,9 @@
import React, {Component} from "react";
import "./App.less";
import {Helmet} from "react-helmet";
import GroupTreePage from "./GroupTreePage";
import GroupEditPage from "./GroupEdit";
import GroupListPage from "./GroupList";
import * as Setting from "./Setting";
import {StyleProvider, legacyLogicalPropertiesTransformer} from "@ant-design/cssinjs";
import {BarsOutlined, CommentOutlined, DownOutlined, InfoCircleFilled, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
@@ -132,6 +135,8 @@ class App extends Component {
this.setState({selectedMenuKey: "/organizations"});
} else if (uri.includes("/users")) {
this.setState({selectedMenuKey: "/users"});
} else if (uri.includes("/groups")) {
this.setState({selectedMenuKey: "/groups"});
} else if (uri.includes("/roles")) {
this.setState({selectedMenuKey: "/roles"});
} else if (uri.includes("/permissions")) {
@@ -223,7 +228,7 @@ class App extends Component {
setLanguage(account) {
const language = account?.language;
if (language !== "" && language !== i18next.language) {
if (language !== null && language !== "" && language !== i18next.language) {
Setting.setLanguage(language);
}
}
@@ -408,6 +413,9 @@ class App extends Component {
if (Setting.isAdminUser(this.state.account)) {
res.push(Setting.getItem(<Link to="/organizations">{i18next.t("general:Organizations")}</Link>,
"/organizations"));
res.push(Setting.getItem(<Link to="/groups">{i18next.t("general:Groups")}</Link>,
"/groups"));
}
if (Setting.isLocalAdminUser(this.state.account)) {
@@ -552,6 +560,10 @@ class App extends Component {
<Route exact path="/organizations" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationListPage account={this.state.account} {...props} />)} />
<Route exact path="/organizations/:organizationName" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationEditPage account={this.state.account} onChangeTheme={this.setTheme} {...props} />)} />
<Route exact path="/organizations/:organizationName/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} />
<Route exact path="/group-tree/:organizationName" render={(props) => this.renderLoginIfNotLoggedIn(<GroupTreePage account={this.state.account} {...props} />)} />
<Route exact path="/group-tree/:organizationName/:groupName" render={(props) => this.renderLoginIfNotLoggedIn(<GroupTreePage account={this.state.account} {...props} />)} />
<Route exact path="/groups" render={(props) => this.renderLoginIfNotLoggedIn(<GroupListPage account={this.state.account} {...props} />)} />
<Route exact path="/groups/:organizationName/:groupName" render={(props) => this.renderLoginIfNotLoggedIn(<GroupEditPage account={this.state.account} {...props} />)} />
<Route exact path="/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} />
<Route exact path="/users/:organizationName/:userName" render={(props) => <UserEditPage account={this.state.account} {...props} />} />
<Route exact path="/roles" render={(props) => this.renderLoginIfNotLoggedIn(<RoleListPage account={this.state.account} {...props} />)} />
@@ -618,6 +630,11 @@ class App extends Component {
});
};
isWithoutCard() {
return Setting.isMobile() || window.location.pathname === "/chat" ||
window.location.pathname.startsWith("/group-tree");
}
renderContent() {
const onClick = ({key}) => {
if (key === "/swagger") {
@@ -628,7 +645,6 @@ class App extends Component {
};
return (
<Layout id="parent-area">
{/* https://github.com/ant-design/ant-design/issues/40394 ant design bug. If it will be fixed, we can delete the code for control the color of Header*/}
<Header style={{padding: "0", marginBottom: "3px", backgroundColor: this.state.themeAlgorithm.includes("dark") ? "black" : "white"}}>
{Setting.isMobile() ? null : (
<Link to={"/"}>
@@ -664,7 +680,7 @@ class App extends Component {
}
</Header>
<Content style={{display: "flex", flexDirection: "column"}} >
{(Setting.isMobile() || window.location.pathname === "/chat") ?
{this.isWithoutCard() ?
this.renderRouter() :
<Card className="content-warp-card">
{this.renderRouter()}

View File

@@ -74,7 +74,6 @@ img {
.content-warp-card {
box-shadow: 0 1px 5px 0 rgb(51 51 51 / 14%);
margin: 5px;
flex: 1;
align-items: stretch;
}

View File

@@ -119,6 +119,11 @@ class ApplicationEditPage extends React.Component {
getApplication() {
ApplicationBackend.getApplication("admin", this.state.applicationName)
.then((application) => {
if (application === null) {
this.props.history.push("/404");
return;
}
if (application.grantTypes === null || application.grantTypes === undefined || application.grantTypes.length === 0) {
application.grantTypes = ["authorization_code"];
}
@@ -155,11 +160,16 @@ class ApplicationEditPage extends React.Component {
}
getProviders() {
ProviderBackend.getProviders(this.state.owner).then((res => {
this.setState({
providers: res,
ProviderBackend.getProviders(this.state.owner)
.then((res) => {
if (res.status === "ok") {
this.setState({
providers: res.data,
});
} else {
Setting.showMessage("error", res.msg);
}
});
}));
}
getSamlMetadata() {

View File

@@ -276,9 +276,11 @@ class ApplicationListPage extends BaseListPage {
(Setting.isAdminUser(this.props.account) ? ApplicationBackend.getApplications("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) :
ApplicationBackend.getApplicationsByOrganization("admin", this.props.account.organization.name, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder))
.then((res) => {
this.setState({
loading: false,
});
if (res.status === "ok") {
this.setState({
loading: false,
data: res.data,
pagination: {
...params.pagination,
@@ -290,9 +292,10 @@ class ApplicationListPage extends BaseListPage {
} else {
if (Setting.isResponseDenied(res)) {
this.setState({
loading: false,
isAuthorized: false,
});
} else {
Setting.showMessage("error", res.msg);
}
}
});

View File

@@ -45,6 +45,11 @@ class CertEditPage extends React.Component {
getCert() {
CertBackend.getCert(this.state.owner, this.state.certName)
.then((cert) => {
if (cert === null) {
this.props.history.push("/404");
return;
}
this.setState({
cert: cert,
});

View File

@@ -108,7 +108,7 @@ class CertListPage extends BaseListPage {
key: "owner",
width: "150px",
sorter: true,
...this.getColumnSearchProps("organization"),
...this.getColumnSearchProps("owner"),
render: (text, record, index) => {
return (text !== "admin") ? text : i18next.t("provider:admin (Shared)");
},
@@ -239,9 +239,11 @@ class CertListPage extends BaseListPage {
(Setting.isAdminUser(this.props.account) ? CertBackend.getGlobleCerts(params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
: CertBackend.getCerts(this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder))
.then((res) => {
this.setState({
loading: false,
});
if (res.status === "ok") {
this.setState({
loading: false,
data: res.data,
pagination: {
...params.pagination,
@@ -253,9 +255,10 @@ class CertListPage extends BaseListPage {
} else {
if (Setting.isResponseDenied(res)) {
this.setState({
loading: false,
isAuthorized: false,
});
} else {
Setting.showMessage("error", res.msg);
}
}
});

View File

@@ -41,6 +41,11 @@ class ChatEditPage extends React.Component {
getChat() {
ChatBackend.getChat("admin", this.state.chatName)
.then((chat) => {
if (chat === null) {
this.props.history.push("/404");
return;
}
this.setState({
chat: chat,
});

View File

@@ -267,9 +267,11 @@ class ChatListPage extends BaseListPage {
this.setState({loading: true});
ChatBackend.getChats("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
this.setState({
loading: false,
});
if (res.status === "ok") {
this.setState({
loading: false,
data: res.data,
pagination: {
...params.pagination,
@@ -281,9 +283,10 @@ class ChatListPage extends BaseListPage {
} else {
if (Setting.isResponseDenied(res)) {
this.setState({
loading: false,
isAuthorized: false,
});
} else {
Setting.showMessage("error", res.msg);
}
}
});

View File

@@ -278,9 +278,10 @@ class ChatPage extends BaseListPage {
} else {
if (Setting.isResponseDenied(res)) {
this.setState({
loading: false,
isAuthorized: false,
});
} else {
Setting.showMessage("error", res.msg);
}
}
});

263
web/src/GroupEdit.js Normal file
View File

@@ -0,0 +1,263 @@
// 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 GroupBackend from "./backend/GroupBackend";
import * as OrganizationBackend from "./backend/OrganizationBackend";
import * as Setting from "./Setting";
import i18next from "i18next";
class GroupEditPage extends React.Component {
constructor(props) {
super(props);
this.state = {
classes: props,
groupName: props.match.params.groupName,
organizationName: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
group: null,
users: [],
groups: [],
organizations: [],
mode: props.location.mode !== undefined ? props.location.mode : "edit",
};
}
UNSAFE_componentWillMount() {
this.getGroup();
this.getGroups(this.state.organizationName);
this.getOrganizations();
}
getGroup() {
GroupBackend.getGroup(this.state.organizationName, this.state.groupName)
.then((res) => {
if (res.status === "ok") {
this.setState({
group: res.data,
});
}
});
}
getGroups(organizationName) {
GroupBackend.getGroups(organizationName)
.then((res) => {
if (res.status === "ok") {
this.setState({
groups: res.data,
});
}
});
}
getOrganizations() {
OrganizationBackend.getOrganizationNames("admin")
.then((res) => {
if (res.status === "ok") {
this.setState({
organizations: res.data,
});
}
});
}
parseGroupField(key, value) {
if ([""].includes(key)) {
value = Setting.myParseInt(value);
}
return value;
}
updateGroupField(key, value) {
value = this.parseGroupField(key, value);
const group = this.state.group;
group[key] = value;
this.setState({
group: group,
});
}
getParentIdOptions() {
const groups = this.state.groups.filter((group) => group.id !== this.state.group.id);
const organization = this.state.organizations.find((organization) => organization.name === this.state.group.owner);
if (organization !== undefined) {
groups.push({id: organization.name, displayName: organization.displayName});
}
return groups.map((group) => ({label: group.displayName, value: group.id}));
}
renderGroup() {
return (
<Card size="small" title={
<div>
{this.state.mode === "add" ? i18next.t("group:New Group") : i18next.t("group:Edit Group")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button onClick={() => this.submitGroupEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitGroupEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteGroup()}>{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.group.owner}
onChange={(value => {
this.updateGroupField("owner", value);
this.getGroups(value);
})}
options={this.state.organizations.map((organization) => Setting.getOption(organization.displayName, 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.group.name} onChange={e => {
this.updateGroupField("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.group.displayName} onChange={e => {
this.updateGroupField("displayName", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Type"), i18next.t("general:Type - Tooltip"))} :
</Col>
<Col span={22} >
<Select style={{width: "100%"}}
options={
[
{label: i18next.t("group:Virtual"), value: "Virtual"},
{label: i18next.t("group:Physical"), value: "Physical"},
].map((item) => ({label: item.label, value: item.value}))
}
value={this.state.group.type} onChange={(value => {
this.updateGroupField("type", value);
}
)} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("group:Parent group"), i18next.t("group:Parent group - Tooltip"))} :
</Col>
<Col span={22} >
<Select style={{width: "100%"}}
options={this.getParentIdOptions()}
value={this.state.group.parentGroupId} onChange={(value => {
this.updateGroupField("parentGroupId", value);
}
)} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.group.isEnabled} onChange={checked => {
this.updateGroupField("isEnabled", checked);
}} />
</Col>
</Row>
</Card>
);
}
submitGroupEdit(willExist) {
const group = Setting.deepCopy(this.state.group);
group["isTopGroup"] = this.state.organizations.some((organization) => organization.name === group.parentGroupId);
GroupBackend.updateGroup(this.state.organizationName, this.state.groupName, group)
.then((res) => {
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully saved"));
this.setState({
groupName: this.state.group.name,
});
if (willExist) {
const groupTreeUrl = sessionStorage.getItem("groupTreeUrl");
if (groupTreeUrl !== null) {
sessionStorage.removeItem("groupTreeUrl");
this.props.history.push(groupTreeUrl);
} else {
this.props.history.push("/groups");
}
} else {
this.props.history.push(`/groups/${this.state.group.owner}/${this.state.group.name}`);
}
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
this.updateGroupField("name", this.state.groupName);
}
})
.catch(error => {
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
deleteGroup() {
GroupBackend.deleteGroup(this.state.group)
.then((res) => {
if (res.status === "ok") {
const groupTreeUrl = sessionStorage.getItem("groupTreeUrl");
if (groupTreeUrl !== null) {
sessionStorage.removeItem("groupTreeUrl");
this.props.history.push(groupTreeUrl);
} else {
this.props.history.push("/groups");
}
} 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.group !== null ? this.renderGroup() : null
}
<div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button size="large" onClick={() => this.submitGroupEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitGroupEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteGroup()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
</div>
);
}
}
export default GroupEditPage;

284
web/src/GroupList.js Normal file
View File

@@ -0,0 +1,284 @@
// 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, Table} from "antd";
import moment from "moment";
import * as Setting from "./Setting";
import * as GroupBackend from "./backend/GroupBackend";
import i18next from "i18next";
import BaseListPage from "./BaseListPage";
import PopconfirmModal from "./common/modal/PopconfirmModal";
class GroupListPage extends BaseListPage {
constructor(props) {
super(props);
this.state = {
...this.state,
owner: Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner,
groups: [],
};
}
UNSAFE_componentWillMount() {
super.UNSAFE_componentWillMount();
this.getGroups(this.state.owner);
}
getGroups(organizationName) {
GroupBackend.getGroups(organizationName)
.then((res) => {
if (res.status === "ok") {
this.setState({
groups: res.data,
});
}
});
}
newGroup() {
const randomName = Setting.getRandomName();
return {
owner: this.props.account.owner,
name: `group_${randomName}`,
createdTime: moment().format(),
updatedTime: moment().format(),
displayName: `New Group - ${randomName}`,
type: "Virtual",
parentGroupId: this.props.account.owner,
isTopGroup: true,
isEnabled: true,
};
}
addGroup() {
const newGroup = this.newGroup();
GroupBackend.addGroup(newGroup)
.then((res) => {
if (res.status === "ok") {
this.props.history.push({pathname: `/groups/${newGroup.owner}/${newGroup.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}`);
});
}
deleteGroup(i) {
GroupBackend.deleteGroup(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(groups) {
const columns = [
{
title: i18next.t("general:Name"),
dataIndex: "name",
key: "name",
width: "120px",
fixed: "left",
sorter: true,
...this.getColumnSearchProps("name"),
render: (text, record, index) => {
return (
<Link to={`/groups/${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: "150px",
sorter: true,
render: (text, record, index) => {
return Setting.getFormattedDate(text);
},
},
{
title: i18next.t("general:Updated time"),
dataIndex: "updatedTime",
key: "updatedTime",
width: "150px",
sorter: true,
render: (text, record, index) => {
return Setting.getFormattedDate(text);
},
},
{
title: i18next.t("general:Display name"),
dataIndex: "displayName",
key: "displayName",
width: "100px",
sorter: true,
...this.getColumnSearchProps("displayName"),
},
{
title: i18next.t("general:Type"),
dataIndex: "type",
key: "type",
width: "110px",
sorter: true,
filterMultiple: false,
filters: [
{text: i18next.t("group:Virtual"), value: "Virtual"},
{text: i18next.t("group:Physical"), value: "Physical"},
],
render: (text, record, index) => {
return i18next.t("group:" + text);
},
},
{
title: i18next.t("group:Parent group"),
dataIndex: "parentGroupId",
key: "parentGroupId",
width: "110px",
sorter: true,
...this.getColumnSearchProps("parentGroupId"),
render: (text, record, index) => {
if (record.isTopGroup) {
return <Link to={`/organizations/${record.parentGroupId}`}>
{record.parentGroupId}
</Link>;
}
const parentGroup = this.state.groups.find((group) => group.id === text);
if (parentGroup === undefined) {
return "";
}
return <Link to={`/groups/${parentGroup.owner}/${parentGroup.name}`}>
{parentGroup?.displayName}
</Link>;
},
},
{
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(`/groups/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<PopconfirmModal
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
onConfirm={() => this.deleteGroup(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={groups} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
title={() => (
<div>
{i18next.t("general:Groups")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addGroup.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.category !== undefined && params.category !== null) {
field = "category";
value = params.category;
} else if (params.type !== undefined && params.type !== null) {
field = "type";
value = params.type;
}
this.setState({loading: true});
GroupBackend.getGroups(this.state.owner, false, 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,
});
}
}
})
.catch(error => {
this.setState({
loading: false,
});
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
};
}
export default GroupListPage;

312
web/src/GroupTreePage.js Normal file
View File

@@ -0,0 +1,312 @@
// 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 {DeleteOutlined, EditOutlined, HolderOutlined, PlusOutlined, UsergroupAddOutlined} from "@ant-design/icons";
import {Button, Col, Empty, Row, Space, Tree} from "antd";
import i18next from "i18next";
import moment from "moment/moment";
import React from "react";
import * as GroupBackend from "./backend/GroupBackend";
import * as Setting from "./Setting";
import OrganizationSelect from "./common/select/OrganizationSelect";
import UserListPage from "./UserListPage";
class GroupTreePage extends React.Component {
constructor(props) {
super(props);
this.state = {
classes: props,
organizationName: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
groupName: this.props.match?.params.groupName,
groupId: "",
treeData: [],
selectedKeys: [],
};
}
UNSAFE_componentWillMount() {
this.getTreeData();
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (this.state.organizationName !== prevState.organizationName) {
this.getTreeData();
}
if (prevState.treeData !== this.state.treeData) {
this.setTreeExpandedKeys();
}
}
getTreeData() {
GroupBackend.getGroups(this.state.organizationName, true).then((res) => {
if (res.status === "ok") {
const tree = res.data;
this.setState({
treeData: tree,
});
} else {
Setting.showMessage("error", res.msg);
}
});
}
setTreeTitle(treeData) {
const haveChildren = Array.isArray(treeData.children) && treeData.children.length > 0;
const isSelected = this.state.groupName === treeData.key;
return {
id: treeData.id,
key: treeData.key,
title: <Space>
{treeData.type === "Physical" ? <UsergroupAddOutlined /> : <HolderOutlined />}
<span>{treeData.title}</span>
{isSelected && (
<React.Fragment>
<PlusOutlined
style={{
visibility: "visible",
color: "inherit",
transition: "color 0.3s",
}}
onMouseEnter={(e) => {
e.currentTarget.style.color = "rgba(89,54,213,0.6)";
}}
onMouseLeave={(e) => {
e.currentTarget.style.color = "inherit";
}}
onMouseDown={(e) => {
e.currentTarget.style.color = "rgba(89,54,213,0.4)";
}}
onMouseUp={(e) => {
e.currentTarget.style.color = "rgba(89,54,213,0.6)";
}}
onClick={(e) => {
e.stopPropagation();
sessionStorage.setItem("groupTreeUrl", window.location.pathname);
this.addGroup();
}}
/>
<EditOutlined
style={{
visibility: "visible",
color: "inherit",
transition: "color 0.3s",
}}
onMouseEnter={(e) => {
e.currentTarget.style.color = "rgba(89,54,213,0.6)";
}}
onMouseLeave={(e) => {
e.currentTarget.style.color = "inherit";
}}
onMouseDown={(e) => {
e.currentTarget.style.color = "rgba(89,54,213,0.4)";
}}
onMouseUp={(e) => {
e.currentTarget.style.color = "rgba(89,54,213,0.6)";
}}
onClick={(e) => {
e.stopPropagation();
sessionStorage.setItem("groupTreeUrl", window.location.pathname);
this.props.history.push(`/groups/${this.state.organizationName}/${treeData.key}`);
}}
/>
<DeleteOutlined
style={{
visibility: "visible",
color: "inherit",
transition: "color 0.3s",
}}
onMouseEnter={(e) => {
e.currentTarget.style.color = "rgba(89,54,213,0.6)";
}}
onMouseLeave={(e) => {
e.currentTarget.style.color = "inherit";
}}
onMouseDown={(e) => {
e.currentTarget.style.color = "rgba(89,54,213,0.4)";
}}
onMouseUp={(e) => {
e.currentTarget.style.color = "rgba(89,54,213,0.6)";
}}
onClick={(e) => {
e.stopPropagation();
GroupBackend.deleteGroup({owner: treeData.owner, name: treeData.key})
.then((res) => {
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
this.getTreeData();
} 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}`);
});
}}
/>
</React.Fragment>
)}
</Space>,
children: haveChildren ? treeData.children.map(i => this.setTreeTitle(i)) : [],
};
}
setTreeExpandedKeys = () => {
const expandedKeys = [];
const setExpandedKeys = (nodes) => {
for (const node of nodes) {
expandedKeys.push(node.key);
if (node.children) {
setExpandedKeys(node.children);
}
}
};
setExpandedKeys(this.state.treeData);
this.setState({
expandedKeys: expandedKeys,
});
};
renderTree() {
const onSelect = (selectedKeys, info) => {
this.setState({
selectedKeys: selectedKeys,
groupName: info.node.key,
groupId: info.node.id,
});
this.props.history.push(`/group-tree/${this.state.organizationName}/${info.node.key}`);
};
const onExpand = (expandedKeysValue) => {
this.setState({
expandedKeys: expandedKeysValue,
});
};
if (this.state.treeData.length === 0) {
return <Empty />;
}
const treeData = this.state.treeData.map(i => this.setTreeTitle(i));
return (
<Tree
blockNode={true}
defaultSelectedKeys={[this.state.groupName]}
defaultExpandAll={true}
expandedKeys={this.state.expandedKeys}
onSelect={onSelect}
onExpand={onExpand}
showIcon={true}
treeData={treeData}
/>
);
}
renderOrganizationSelect() {
return <OrganizationSelect
initValue={this.state.organizationName}
style={{width: "100%"}}
onChange={(value) => {
this.setState({
organizationName: value,
});
this.props.history.push(`/group-tree/${value}`);
}}
/>;
}
newGroup(isRoot) {
const randomName = Setting.getRandomName();
return {
owner: this.state.organizationName,
name: `group_${randomName}`,
createdTime: moment().format(),
updatedTime: moment().format(),
displayName: `New Group - ${randomName}`,
type: "Virtual",
parentGroupId: isRoot ? this.state.organizationName : this.state.groupId,
isTopGroup: isRoot,
isEnabled: true,
};
}
addGroup(isRoot = false) {
const newGroup = this.newGroup(isRoot);
GroupBackend.addGroup(newGroup)
.then((res) => {
if (res.status === "ok") {
sessionStorage.setItem("groupTreeUrl", window.location.pathname);
this.props.history.push({pathname: `/groups/${newGroup.owner}/${newGroup.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}`);
});
}
render() {
return (
<div style={{
flex: 1,
backgroundColor: "white",
padding: "5px 5px 2px 5px",
}}>
<Row>
<Col span={5}>
<Row>
<Col span={24} style={{textAlign: "left"}}>
{this.renderOrganizationSelect()}
</Col>
</Row>
<Row>
<Col span={24} style={{marginTop: "10px", textAlign: "left"}}>
<Button
onClick={() => {
this.setState({
groupName: null,
groupId: null,
});
this.props.history.push(`/group-tree/${this.state.organizationName}`);
}}>
{i18next.t("group:Show organization users")}
</Button>
<Button size={"small"} type={"primary"} style={{marginLeft: "10px"}}
onClick={() => this.addGroup(true)}
>
{i18next.t("general:Add")}
</Button>
</Col>
</Row>
<Row style={{marginTop: 10}}>
<Col span={24} style={{textAlign: "left"}}>
{this.renderTree()}
</Col>
</Row>
</Col>
<Col span={19}>
<UserListPage
organizationName={this.state.organizationName}
groupName={this.state.groupName}
groupId={this.state.groupId}
{...this.props} />
</Col>
</Row>
</div>
);
}
}
export default GroupTreePage;

View File

@@ -46,6 +46,11 @@ class MessageEditPage extends React.Component {
getMessage() {
MessageBackend.getMessage("admin", this.state.messageName)
.then((message) => {
if (message === null) {
this.props.history.push("/404");
return;
}
this.setState({
message: message,
});

View File

@@ -210,9 +210,11 @@ class MessageListPage extends BaseListPage {
this.setState({loading: true});
MessageBackend.getMessages("admin", Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
this.setState({
loading: false,
});
if (res.status === "ok") {
this.setState({
loading: false,
data: res.data,
pagination: {
...params.pagination,
@@ -224,9 +226,10 @@ class MessageListPage extends BaseListPage {
} else {
if (Setting.isResponseDenied(res)) {
this.setState({
loading: false,
isAuthorized: false,
});
} else {
Setting.showMessage("error", res.msg);
}
}
});

View File

@@ -36,7 +36,6 @@ class ModelEditPage extends React.Component {
model: null,
organizations: [],
users: [],
models: [],
mode: props.location.mode !== undefined ? props.location.mode : "edit",
};
}
@@ -49,11 +48,14 @@ class ModelEditPage extends React.Component {
getModel() {
ModelBackend.getModel(this.state.organizationName, this.state.modelName)
.then((model) => {
if (model === null) {
this.props.history.push("/404");
return;
}
this.setState({
model: model,
});
this.getModels(model.organization);
});
}
@@ -66,15 +68,6 @@ class ModelEditPage extends React.Component {
});
}
getModels(organizationName) {
ModelBackend.getModels(organizationName)
.then((res) => {
this.setState({
models: res,
});
});
}
parseModelField(key, value) {
if ([""].includes(key)) {
value = Setting.myParseInt(value);
@@ -134,6 +127,16 @@ class ModelEditPage extends React.Component {
}} />
</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.model.description} onChange={e => {
this.updateModelField("description", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("model:Model text"), i18next.t("model:Model text - Tooltip"))} :

View File

@@ -204,9 +204,11 @@ class ModelListPage extends BaseListPage {
this.setState({loading: true});
ModelBackend.getModels(Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
this.setState({
loading: false,
});
if (res.status === "ok") {
this.setState({
loading: false,
data: res.data,
pagination: {
...params.pagination,
@@ -218,9 +220,10 @@ class ModelListPage extends BaseListPage {
} else {
if (Setting.isResponseDenied(res)) {
this.setState({
loading: false,
isAuthorized: false,
});
} else {
Setting.showMessage("error", res.msg);
}
}
});

View File

@@ -50,6 +50,11 @@ class OrganizationEditPage extends React.Component {
getOrganization() {
OrganizationBackend.getOrganization("admin", this.state.organizationName)
.then((organization) => {
if (organization === null) {
this.props.history.push("/404");
return;
}
this.setState({
organization: organization,
});

View File

@@ -62,6 +62,7 @@ class OrganizationListPage extends BaseListPage {
{name: "Signup application", visible: true, viewRule: "Public", modifyRule: "Admin"},
{name: "Roles", visible: true, viewRule: "Public", modifyRule: "Immutable"},
{name: "Permissions", visible: true, viewRule: "Public", modifyRule: "Immutable"},
{name: "Groups", visible: true, viewRule: "Public", modifyRule: "Immutable"},
{name: "3rd-party logins", visible: true, viewRule: "Self", modifyRule: "Self"},
{Name: "Multi-factor authentication", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
{name: "Properties", visible: false, viewRule: "Admin", modifyRule: "Admin"},
@@ -221,11 +222,12 @@ class OrganizationListPage extends BaseListPage {
title: i18next.t("general:Action"),
dataIndex: "",
key: "op",
width: "240px",
width: "320px",
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(`/group-tree/${record.name}`)}>{i18next.t("general:Groups")}</Button>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/organizations/${record.name}/users`)}>{i18next.t("general:Users")}</Button>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} onClick={() => this.props.history.push(`/organizations/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<PopconfirmModal
@@ -273,9 +275,11 @@ class OrganizationListPage extends BaseListPage {
this.setState({loading: true});
OrganizationBackend.getOrganizations("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
this.setState({
loading: false,
});
if (res.status === "ok") {
this.setState({
loading: false,
data: res.data,
pagination: {
...params.pagination,
@@ -287,9 +291,10 @@ class OrganizationListPage extends BaseListPage {
} else {
if (Setting.isResponseDenied(res)) {
this.setState({
loading: false,
isAuthorized: false,
});
} else {
Setting.showMessage("error", res.msg);
}
}
});

View File

@@ -40,8 +40,13 @@ class PaymentEditPage extends React.Component {
}
getPayment() {
PaymentBackend.getPayment(this.props.account.owner, this.state.paymentName)
PaymentBackend.getPayment("admin", this.state.paymentName)
.then((payment) => {
if (payment === null) {
this.props.history.push("/404");
return;
}
this.setState({
payment: payment,
});

View File

@@ -27,7 +27,7 @@ class PaymentListPage extends BaseListPage {
newPayment() {
const randomName = Setting.getRandomName();
return {
owner: this.props.account.owner,
owner: "admin",
name: `payment_${randomName}`,
createdTime: moment().format(),
displayName: `New Payment - ${randomName}`,
@@ -265,11 +265,13 @@ class PaymentListPage extends BaseListPage {
value = params.type;
}
this.setState({loading: true});
PaymentBackend.getPayments(Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
PaymentBackend.getPayments("admin", Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
this.setState({
loading: false,
});
if (res.status === "ok") {
this.setState({
loading: false,
data: res.data,
pagination: {
...params.pagination,
@@ -281,9 +283,10 @@ class PaymentListPage extends BaseListPage {
} else {
if (Setting.isResponseDenied(res)) {
this.setState({
loading: false,
isAuthorized: false,
});
} else {
Setting.showMessage("error", res.msg);
}
}
});

View File

@@ -33,7 +33,7 @@ class PaymentResultPage extends React.Component {
}
getPayment() {
PaymentBackend.getPayment(this.props.account.owner, this.state.paymentName)
PaymentBackend.getPayment("admin", this.state.paymentName)
.then((payment) => {
this.setState({
payment: payment,

View File

@@ -50,6 +50,11 @@ class PermissionEditPage extends React.Component {
getPermission() {
PermissionBackend.getPermission(this.state.organizationName, this.state.permissionName)
.then((permission) => {
if (permission === null) {
this.props.history.push("/404");
return;
}
this.setState({
permission: permission,
});
@@ -190,6 +195,16 @@ class PermissionEditPage extends React.Component {
}} />
</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.permission.description} onChange={e => {
this.updatePermissionField("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"))} :
@@ -254,10 +269,12 @@ class PermissionEditPage extends React.Component {
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.permission.resourceType} onChange={(value => {
this.updatePermissionField("resourceType", value);
this.updatePermissionField("resources", []);
})}
options={[
{value: "Application", name: i18next.t("general:Application")},
{value: "TreeNode", name: i18next.t("permission:TreeNode")},
{value: "Custom", name: i18next.t("general:Custom")},
].map((item) => Setting.getOption(item.name, item.value))}
/>
</Col>
@@ -267,7 +284,7 @@ class PermissionEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Resources"), i18next.t("permission:Resources - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} mode="multiple" style={{width: "100%"}} value={this.state.permission.resources}
<Select virtual={false} mode={(this.state.permission.resourceType === "Custom") ? "tags" : "multiple"} style={{width: "100%"}} value={this.state.permission.resources}
onChange={(value => {this.updatePermissionField("resources", value);})}
options={this.state.resources.map((resource) => Setting.getOption(`${resource.name}`, `${resource.name}`))
} />

View File

@@ -385,9 +385,11 @@ class PermissionListPage extends BaseListPage {
const getPermissions = Setting.isLocalAdminUser(this.props.account) ? PermissionBackend.getPermissions : PermissionBackend.getPermissionsBySubmitter;
getPermissions(Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
this.setState({
loading: false,
});
if (res.status === "ok") {
this.setState({
loading: false,
data: res.data,
pagination: {
...params.pagination,
@@ -399,9 +401,10 @@ class PermissionListPage extends BaseListPage {
} else {
if (Setting.isResponseDenied(res)) {
this.setState({
loading: false,
isAuthorized: false,
});
} else {
Setting.showMessage("error", res.msg);
}
}
});

View File

@@ -47,6 +47,11 @@ class PlanEditPage extends React.Component {
getPlan() {
PlanBackend.getPlan(this.state.organizationName, this.state.planName)
.then((plan) => {
if (plan === null) {
this.props.history.push("/404");
return;
}
this.setState({
plan: plan,
});

View File

@@ -31,10 +31,14 @@ class PlanListPage extends BaseListPage {
owner: owner,
name: `plan_${randomName}`,
createdTime: moment().format(),
displayName: `New Plan - ${randomName}`,
description: "",
pricePerMonth: 10,
pricePerYear: 100,
currency: "USD",
displayName: `New Plan - ${randomName}`,
isEnabled: true,
role: "",
options: [],
};
}
@@ -215,11 +219,13 @@ class PlanListPage extends BaseListPage {
value = params.type;
}
this.setState({loading: true});
PlanBackend.getPlans("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
PlanBackend.getPlans(Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
this.setState({
loading: false,
});
if (res.status === "ok") {
this.setState({
loading: false,
data: res.data,
pagination: {
...params.pagination,
@@ -231,9 +237,10 @@ class PlanListPage extends BaseListPage {
} else {
if (Setting.isResponseDenied(res)) {
this.setState({
loading: false,
isAuthorized: false,
});
} else {
Setting.showMessage("error", res.msg);
}
}
});

View File

@@ -50,6 +50,11 @@ class PricingEditPage extends React.Component {
getPricing() {
PricingBackend.getPricing(this.state.organizationName, this.state.pricingName)
.then((pricing) => {
if (pricing === null) {
this.props.history.push("/404");
return;
}
this.setState({
pricing: pricing,
});
@@ -189,22 +194,12 @@ class PricingEditPage extends React.Component {
/>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("pricing:Has trial"), i18next.t("pricing:Has trial - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.pricing.hasTrial} onChange={checked => {
this.updatePricingField("hasTrial", checked);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("pricing:Trial duration"), i18next.t("pricing:Trial duration - Tooltip"))} :
</Col>
<Col span={22} >
<InputNumber min={1} value={this.state.pricing.trialDuration} onChange={value => {
<InputNumber min={0} value={this.state.pricing.trialDuration} onChange={value => {
this.updatePricingField("trialDuration", value);
}} />
</Col>

View File

@@ -33,9 +33,8 @@ class PricingListPage extends BaseListPage {
createdTime: moment().format(),
plans: [],
displayName: `New Pricing - ${randomName}`,
hasTrial: false,
isEnabled: true,
trialDuration: 14,
trialDuration: 7,
};
}
@@ -189,11 +188,13 @@ class PricingListPage extends BaseListPage {
value = params.type;
}
this.setState({loading: true});
PricingBackend.getPricings("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
PricingBackend.getPricings(Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
this.setState({
loading: false,
});
if (res.status === "ok") {
this.setState({
loading: false,
data: res.data,
pagination: {
...params.pagination,
@@ -205,9 +206,10 @@ class PricingListPage extends BaseListPage {
} else {
if (Setting.isResponseDenied(res)) {
this.setState({
loading: false,
isAuthorized: false,
});
} else {
Setting.showMessage("error", res.msg);
}
}
});

View File

@@ -45,6 +45,11 @@ class ProductEditPage extends React.Component {
getProduct() {
ProductBackend.getProduct(this.props.account.owner, this.state.productName)
.then((product) => {
if (product === null) {
this.props.history.push("/404");
return;
}
this.setState({
product: product,
});
@@ -54,9 +59,13 @@ class ProductEditPage extends React.Component {
getPaymentProviders() {
ProviderBackend.getProviders(this.props.account.owner)
.then((res) => {
this.setState({
providers: res.filter(provider => provider.category === "Payment"),
});
if (res.status === "ok") {
this.setState({
providers: res.data.filter(provider => provider.category === "Payment"),
});
} else {
Setting.showMessage("error", res.msg);
}
});
}

View File

@@ -100,7 +100,7 @@ class ProductListPage extends BaseListPage {
key: "owner",
width: "150px",
sorter: true,
...this.getColumnSearchProps("organization"),
...this.getColumnSearchProps("owner"),
},
{
title: i18next.t("general:Created time"),
@@ -292,9 +292,11 @@ class ProductListPage extends BaseListPage {
this.setState({loading: true});
ProductBackend.getProducts(Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
this.setState({
loading: false,
});
if (res.status === "ok") {
this.setState({
loading: false,
data: res.data,
pagination: {
...params.pagination,
@@ -306,9 +308,10 @@ class ProductListPage extends BaseListPage {
} else {
if (Setting.isResponseDenied(res)) {
this.setState({
loading: false,
isAuthorized: false,
});
} else {
Setting.showMessage("error", res.msg);
}
}
});

View File

@@ -49,10 +49,19 @@ class ProviderEditPage extends React.Component {
getProvider() {
ProviderBackend.getProvider(this.state.owner, this.state.providerName)
.then((provider) => {
this.setState({
provider: provider,
});
.then((res) => {
if (res === null) {
this.props.history.push("/404");
return;
}
if (res.status === "ok") {
this.setState({
provider: res.data,
});
} else {
Setting.showMessage("error", res.msg);
}
});
}

View File

@@ -111,7 +111,7 @@ class ProviderListPage extends BaseListPage {
key: "owner",
width: "150px",
sorter: true,
...this.getColumnSearchProps("organization"),
...this.getColumnSearchProps("owner"),
render: (text, record, index) => {
return (text !== "admin") ? text : i18next.t("provider:admin (Shared)");
},
@@ -259,9 +259,11 @@ class ProviderListPage extends BaseListPage {
(Setting.isAdminUser(this.props.account) ? ProviderBackend.getGlobalProviders(params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
: ProviderBackend.getProviders(this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder))
.then((res) => {
this.setState({
loading: false,
});
if (res.status === "ok") {
this.setState({
loading: false,
data: res.data,
pagination: {
...params.pagination,
@@ -273,9 +275,10 @@ class ProviderListPage extends BaseListPage {
} else {
if (Setting.isResponseDenied(res)) {
this.setState({
loading: false,
isAuthorized: false,
});
} else {
Setting.showMessage("error", res.msg);
}
}
});

View File

@@ -211,9 +211,11 @@ class RecordListPage extends BaseListPage {
this.setState({loading: true});
RecordBackend.getRecords(params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
this.setState({
loading: false,
});
if (res.status === "ok") {
this.setState({
loading: false,
data: res.data,
pagination: {
...params.pagination,

View File

@@ -311,9 +311,11 @@ class ResourceListPage extends BaseListPage {
this.setState({loading: true});
ResourceBackend.getResources(Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, this.props.account.name, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
this.setState({
loading: false,
});
if (res.status === "ok") {
this.setState({
loading: false,
data: res.data,
pagination: {
...params.pagination,

View File

@@ -43,6 +43,11 @@ class RoleEditPage extends React.Component {
getRole() {
RoleBackend.getRole(this.state.organizationName, this.state.roleName)
.then((role) => {
if (role === null) {
this.props.history.push("/404");
return;
}
this.setState({
role: role,
});
@@ -136,6 +141,16 @@ class RoleEditPage extends React.Component {
}} />
</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.role.description} onChange={e => {
this.updateRoleField("description", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("role:Sub users"), i18next.t("role:Sub users - Tooltip"))} :

View File

@@ -260,9 +260,11 @@ class RoleListPage extends BaseListPage {
this.setState({loading: true});
RoleBackend.getRoles(Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
this.setState({
loading: false,
});
if (res.status === "ok") {
this.setState({
loading: false,
data: res.data,
pagination: {
...params.pagination,
@@ -274,9 +276,10 @@ class RoleListPage extends BaseListPage {
} else {
if (Setting.isResponseDenied(res)) {
this.setState({
loading: false,
isAuthorized: false,
});
} else {
Setting.showMessage("error", res.msg);
}
}
});

View File

@@ -55,10 +55,10 @@ class SessionListPage extends BaseListPage {
{
title: i18next.t("general:Organization"),
dataIndex: "owner",
key: "organization",
key: "owner",
width: "110px",
sorter: true,
...this.getColumnSearchProps("organization"),
...this.getColumnSearchProps("owner"),
render: (text, record, index) => {
return (
<Link to={`/organizations/${text}`}>
@@ -136,9 +136,11 @@ class SessionListPage extends BaseListPage {
this.setState({loading: true});
SessionBackend.getSessions(Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
this.setState({
loading: false,
});
if (res.status === "ok") {
this.setState({
loading: false,
data: res.data,
pagination: {
...params.pagination,
@@ -150,9 +152,10 @@ class SessionListPage extends BaseListPage {
} else {
if (Setting.isResponseDenied(res)) {
this.setState({
loading: false,
isAuthorized: false,
});
} else {
Setting.showMessage("error", res.msg);
}
}
});

View File

@@ -683,7 +683,7 @@ export function getLanguageText(text) {
}
export function getLanguage() {
return i18next.language ?? Conf.DefaultLanguage;
return (i18next.language !== undefined && i18next.language !== null && i18next.language !== "" && i18next.language !== "null") ? i18next.language : Conf.DefaultLanguage;
}
export function setLanguage(language) {

View File

@@ -47,6 +47,11 @@ class SubscriptionEditPage extends React.Component {
getSubscription() {
SubscriptionBackend.getSubscription(this.state.organizationName, this.state.subscriptionName)
.then((subscription) => {
if (subscription === null) {
this.props.history.push("/404");
return;
}
this.setState({
subscription: subscription,
});

View File

@@ -237,11 +237,13 @@ class SubscriptionListPage extends BaseListPage {
value = params.type;
}
this.setState({loading: true});
SubscriptionBackend.getSubscriptions("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
SubscriptionBackend.getSubscriptions(Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
this.setState({
loading: false,
});
if (res.status === "ok") {
this.setState({
loading: false,
data: res.data,
pagination: {
...params.pagination,
@@ -253,9 +255,10 @@ class SubscriptionListPage extends BaseListPage {
} else {
if (Setting.isResponseDenied(res)) {
this.setState({
loading: false,
isAuthorized: false,
});
} else {
Setting.showMessage("error", res.msg);
}
}
});

View File

@@ -48,6 +48,11 @@ class SyncerEditPage extends React.Component {
getSyncer() {
SyncerBackend.getSyncer("admin", this.state.syncerName)
.then((syncer) => {
if (syncer === null) {
this.props.history.push("/404");
return;
}
this.setState({
syncer: syncer,
});

View File

@@ -277,9 +277,11 @@ class SyncerListPage extends BaseListPage {
this.setState({loading: true});
SyncerBackend.getSyncers("admin", Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
this.setState({
loading: false,
});
if (res.status === "ok") {
this.setState({
loading: false,
data: res.data,
pagination: {
...params.pagination,
@@ -291,9 +293,10 @@ class SyncerListPage extends BaseListPage {
} else {
if (Setting.isResponseDenied(res)) {
this.setState({
loading: false,
isAuthorized: false,
});
} else {
Setting.showMessage("error", res.msg);
}
}
});

View File

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

View File

@@ -242,9 +242,11 @@ class TokenListPage extends BaseListPage {
this.setState({loading: true});
TokenBackend.getTokens("admin", Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
this.setState({
loading: false,
});
if (res.status === "ok") {
this.setState({
loading: false,
data: res.data,
pagination: {
...params.pagination,
@@ -256,9 +258,10 @@ class TokenListPage extends BaseListPage {
} else {
if (Setting.isResponseDenied(res)) {
this.setState({
loading: false,
isAuthorized: false,
});
} else {
Setting.showMessage("error", res.msg);
}
}
});

View File

@@ -13,7 +13,8 @@
// limitations under the License.
import React from "react";
import {Button, Card, Col, Input, InputNumber, List, Result, Row, Select, Spin, Switch, Tag} from "antd";
import {Button, Card, Col, Input, InputNumber, List, Result, Row, Select, Space, Spin, Switch, Tag} from "antd";
import * as GroupBackend from "./backend/GroupBackend";
import * as UserBackend from "./backend/UserBackend";
import * as OrganizationBackend from "./backend/OrganizationBackend";
import * as Setting from "./Setting";
@@ -32,7 +33,7 @@ import PropertyTable from "./table/propertyTable";
import {CountryCodeSelect} from "./common/select/CountryCodeSelect";
import PopconfirmModal from "./common/modal/PopconfirmModal";
import {DeleteMfa} from "./backend/MfaBackend";
import {CheckCircleOutlined} from "@ant-design/icons";
import {CheckCircleOutlined, HolderOutlined, UsergroupAddOutlined} from "@ant-design/icons";
import {SmsMfaType} from "./auth/MfaSetupPage";
import * as MfaBackend from "./backend/MfaBackend";
@@ -47,6 +48,7 @@ class UserEditPage extends React.Component {
userName: props.userName !== undefined ? props.userName : props.match.params.userName,
user: null,
application: null,
groups: null,
organizations: [],
applications: [],
mode: props.location.mode !== undefined ? props.location.mode : "edit",
@@ -63,9 +65,20 @@ class UserEditPage extends React.Component {
this.setReturnUrl();
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (prevState.application !== this.state.application) {
this.getGroups(this.state.organizationName);
}
}
getUser() {
UserBackend.getUser(this.state.organizationName, this.state.userName)
.then((data) => {
if (data === null) {
this.props.history.push("/404");
return;
}
if (data.status === null || data.status !== "error") {
this.setState({
user: data,
@@ -102,9 +115,26 @@ class UserEditPage extends React.Component {
this.setState({
application: application,
});
this.setState({
isGroupsVisible: application.organizationObj.accountItems?.some((item) => item.name === "Groups" && item.visible),
});
});
}
getGroups(organizationName) {
if (this.state.isGroupsVisible) {
GroupBackend.getGroups(organizationName)
.then((res) => {
if (res.status === "ok") {
this.setState({
groups: res.data,
});
}
});
}
}
setReturnUrl() {
const searchParams = new URLSearchParams(this.props.location.search);
const returnUrl = searchParams.get("returnUrl");
@@ -207,14 +237,6 @@ class UserEditPage extends React.Component {
const isAdmin = Setting.isAdminUser(this.props.account);
// return (
// <div>
// {
// JSON.stringify({accountItem: accountItem, isSelf: isSelf, isAdmin: isAdmin})
// }
// </div>
// )
if (accountItem.viewRule === "Self") {
if (!this.isSelfOrAdmin()) {
return null;
@@ -254,6 +276,7 @@ class UserEditPage extends React.Component {
<Select virtual={false} style={{width: "100%"}} disabled={disabled} value={this.state.user.owner} onChange={(value => {
this.getApplicationsByOrganization(value);
this.updateUserField("owner", value);
this.getGroups(value);
})}>
{
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
@@ -262,6 +285,35 @@ class UserEditPage extends React.Component {
</Col>
</Row>
);
} else if (accountItem.name === "Groups") {
return (
<Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Groups"), i18next.t("general:Groups - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} mode="multiple" style={{width: "100%"}} disabled={disabled} value={this.state.user.groups ?? []} onChange={(value => {
if (this.state.groups?.filter(group => value.includes(group.id))
.filter(group => group.type === "Physical").length > 1) {
Setting.showMessage("error", i18next.t("general:You can only select one physical group"));
return;
}
this.updateUserField("groups", value);
})}
>
{
this.state.groups?.map((group) => <Option key={group.id} value={group.id}>
<Space>
{group.type === "Physical" ? <UsergroupAddOutlined /> : <HolderOutlined />}
{group.displayName}
</Space>
</Option>)
}
</Select>
</Col>
</Row>
);
} else if (accountItem.name === "ID") {
return (
<Row style={{marginTop: "20px"}} >
@@ -920,7 +972,12 @@ class UserEditPage extends React.Component {
UserBackend.deleteUser(this.state.user)
.then((res) => {
if (res.status === "ok") {
this.props.history.push("/users");
const userListUrl = sessionStorage.getItem("userListUrl");
if (userListUrl !== null) {
this.props.history.push(userListUrl);
} else {
this.props.history.push("/users");
}
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}

View File

@@ -27,18 +27,41 @@ import PopconfirmModal from "./common/modal/PopconfirmModal";
class UserListPage extends BaseListPage {
constructor(props) {
super(props);
this.state = {
...this.state,
organizationName: this.props.organizationName ?? this.props.match?.params.organizationName ?? this.props.account.owner,
organization: null,
};
}
componentDidMount() {
this.setState({
organizationName: this.props.match.params.organizationName,
organization: null,
});
UNSAFE_componentWillMount() {
super.UNSAFE_componentWillMount();
this.getOrganization(this.state.organizationName);
}
componentDidUpdate(prevProps, prevState) {
if (this.props.match.path !== prevProps.match.path || this.props.organizationName !== prevProps.organizationName) {
this.setState({
organizationName: this.props.organizationName ?? this.props.match?.params.organizationName,
});
}
if (this.state.organizationName !== prevState.organizationName) {
this.getOrganization(this.state.organizationName);
}
if (prevProps.groupName !== this.props.groupName || this.state.organizationName !== prevState.organizationName) {
this.fetch({
pagination: this.state.pagination,
searchText: this.state.searchText,
searchedColumn: this.state.searchedColumn,
});
}
}
newUser() {
const randomName = Setting.getRandomName();
const owner = (this.state.organizationName !== undefined) ? this.state.organizationName : this.props.account.owner;
const owner = this.state.organizationName;
return {
owner: owner,
name: `user_${randomName}`,
@@ -52,6 +75,7 @@ class UserListPage extends BaseListPage {
phone: Setting.getRandomNumber(),
countryCode: this.state.organization.countryCodes?.length > 0 ? this.state.organization.countryCodes[0] : "",
address: [],
groups: this.props.groupId !== undefined ? [this.props.groupId] : [],
affiliation: "Example Inc.",
tag: "staff",
region: "",
@@ -116,6 +140,15 @@ class UserListPage extends BaseListPage {
}
}
getOrganization(organizationName) {
OrganizationBackend.getOrganization("admin", organizationName)
.then((organization) => {
this.setState({
organization: organization,
});
});
}
renderUpload() {
const props = {
name: "file",
@@ -388,12 +421,14 @@ class UserListPage extends BaseListPage {
const field = params.searchedColumn, value = params.searchText;
const sortField = params.sortField, sortOrder = params.sortOrder;
this.setState({loading: true});
if (this.props.match.params.organizationName === undefined) {
if (this.props.match?.path === "/users") {
(Setting.isAdminUser(this.props.account) ? UserBackend.getGlobalUsers(params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) : UserBackend.getUsers(this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder))
.then((res) => {
this.setState({
loading: false,
});
if (res.status === "ok") {
this.setState({
loading: false,
data: res.data,
pagination: {
...params.pagination,
@@ -402,28 +437,26 @@ class UserListPage extends BaseListPage {
searchText: params.searchText,
searchedColumn: params.searchedColumn,
});
const users = res.data;
if (users.length > 0) {
this.getOrganization(users[0].owner);
} else {
this.getOrganization(this.state.organizationName);
}
} else {
if (Setting.isResponseDenied(res)) {
this.setState({
loading: false,
isAuthorized: false,
});
} else {
Setting.showMessage("error", res.msg);
}
}
});
} else {
UserBackend.getUsers(this.props.match.params.organizationName, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
(this.props.groupName ?
UserBackend.getUsers(this.state.organizationName, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder, `${this.state.organizationName}/${this.props.groupName}`) :
UserBackend.getUsers(this.state.organizationName, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder))
.then((res) => {
this.setState({
loading: false,
});
if (res.status === "ok") {
this.setState({
loading: false,
data: res.data,
pagination: {
...params.pagination,
@@ -432,33 +465,18 @@ class UserListPage extends BaseListPage {
searchText: params.searchText,
searchedColumn: params.searchedColumn,
});
const users = res.data;
if (users.length > 0) {
this.getOrganization(users[0].owner);
} else {
this.getOrganization(this.state.organizationName);
}
} else {
if (Setting.isResponseDenied(res)) {
this.setState({
loading: false,
isAuthorized: false,
});
} else {
Setting.showMessage("error", res.msg);
}
}
});
}
};
getOrganization(organizationName) {
OrganizationBackend.getOrganization("admin", organizationName)
.then((organization) => {
this.setState({
organization: organization,
});
});
}
}
export default UserListPage;

View File

@@ -123,6 +123,11 @@ class WebhookEditPage extends React.Component {
getWebhook() {
WebhookBackend.getWebhook("admin", this.state.webhookName)
.then((webhook) => {
if (webhook === null) {
this.props.history.push("/404");
return;
}
this.setState({
webhook: webhook,
});

View File

@@ -242,9 +242,11 @@ class WebhookListPage extends BaseListPage {
this.setState({loading: true});
WebhookBackend.getWebhooks("admin", Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
this.setState({
loading: false,
});
if (res.status === "ok") {
this.setState({
loading: false,
data: res.data,
pagination: {
...params.pagination,
@@ -256,9 +258,10 @@ class WebhookListPage extends BaseListPage {
} else {
if (Setting.isResponseDenied(res)) {
this.setState({
loading: false,
isAuthorized: false,
});
} else {
Setting.showMessage("error", res.msg);
}
}
});

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 getGroups(owner = "", withTree = false, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
return fetch(`${Setting.ServerUrl}/api/get-groups?owner=${owner}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}&withTree=${withTree}`, {
method: "GET",
credentials: "include",
headers: {
"Accept-Language": Setting.getAcceptLanguage(),
},
}).then(res => res.json());
}
export function getGroup(owner, name) {
return fetch(`${Setting.ServerUrl}/api/get-group?id=${owner}/${encodeURIComponent(name)}`, {
method: "GET",
credentials: "include",
headers: {
"Accept-Language": Setting.getAcceptLanguage(),
},
}).then(res => res.json());
}
export function updateGroup(owner, name, group) {
const newGroup = Setting.deepCopy(group);
return fetch(`${Setting.ServerUrl}/api/update-group?id=${owner}/${encodeURIComponent(name)}`, {
method: "POST",
credentials: "include",
body: JSON.stringify(newGroup),
headers: {
"Accept-Language": Setting.getAcceptLanguage(),
},
}).then(res => res.json());
}
export function addGroup(group) {
const newGroup = Setting.deepCopy(group);
return fetch(`${Setting.ServerUrl}/api/add-group`, {
method: "POST",
credentials: "include",
body: JSON.stringify(newGroup),
headers: {
"Accept-Language": Setting.getAcceptLanguage(),
},
}).then(res => res.json());
}
export function deleteGroup(group) {
const newGroup = Setting.deepCopy(group);
return fetch(`${Setting.ServerUrl}/api/delete-group`, {
method: "POST",
credentials: "include",
body: JSON.stringify(newGroup),
headers: {
"Accept-Language": Setting.getAcceptLanguage(),
},
}).then(res => res.json());
}

View File

@@ -14,8 +14,8 @@
import * as Setting from "../Setting";
export function getPayments(owner, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
return fetch(`${Setting.ServerUrl}/api/get-payments?owner=${owner}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, {
export function getPayments(owner, organization, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
return fetch(`${Setting.ServerUrl}/api/get-payments?owner=${owner}&organization=${organization}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, {
method: "GET",
credentials: "include",
headers: {

View File

@@ -25,8 +25,8 @@ export function getGlobalUsers(page, pageSize, field = "", value = "", sortField
}).then(res => res.json());
}
export function getUsers(owner, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
return fetch(`${Setting.ServerUrl}/api/get-users?owner=${owner}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, {
export function getUsers(owner, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "", groupId = "") {
return fetch(`${Setting.ServerUrl}/api/get-users?owner=${owner}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}&groupId=${groupId}`, {
method: "GET",
credentials: "include",
headers: {

View File

@@ -29,9 +29,6 @@ export const CaptchaPreview = (props) => {
provider.providerUrl = providerUrl;
if (clientSecret !== "***") {
provider.clientSecret = clientSecret;
// ProviderBackend.updateProvider(owner, providerName, provider).then(() => {
// setOpen(true);
// });
setVisible(true);
} else {
setVisible(true);

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