Compare commits

...

26 Commits

Author SHA1 Message Date
1a9d02be46 feat: use the casbin model to store relationships between users and groups (#2178)
* fix:reslove conflict

* fix: remove interface
2023-08-11 10:59:18 +08:00
eafaa135b4 Change builtInAvailableField back to 5 2023-08-11 02:45:11 +08:00
6746551447 Improve error message in InitEnforcer() 2023-08-11 02:36:29 +08:00
3cb46c3628 Add isKey to syncer's table 2023-08-09 00:33:04 +08:00
558bcf95d6 feat: save policy in adapter edit page (#2190)
* fix: save policy in adapter

* fix: disable edit for builtin adapter
2023-08-09 00:12:53 +08:00
bb937c30c1 Fix empty cert in getPaymentProvider() 2023-08-08 22:37:48 +08:00
8dfdf7f767 ci: add GoogleCloud and QiNiu in Storage (#2188)
* feat: add GoogleCloud and QiNiu in Storage

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

* Update qiniu_cloud.go

* Update storage.go

---------

Signed-off-by: baihhh <2542274498@qq.com>
Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-08-08 22:34:55 +08:00
62b2082e82 Add getUserOrganization() to user edit page 2023-08-08 21:58:27 +08:00
a1806439f8 Add UserPrincipalName and MemberOf to get-ldap-users API 2023-08-08 20:18:47 +08:00
01e58158b7 feat: Remove useless code 2023-08-08 19:16:55 +08:00
15427ad9d6 fix: fix add provider error (#2184) 2023-08-07 17:22:32 +08:00
d058f78dc6 fix: fix broken links (#2181) 2023-08-07 01:02:03 +08:00
fd9dbf8251 feat: add multiple SMS providers (#2182)
* feat: add amazon sns and azure acs provider

* feat: add msg91 sms provider

* feat: add infobip sms provider

* feat: add ucloud sms provider

* feat: add baidu cloud sms provider

* fix: fix logo and azure acs
2023-08-07 00:59:17 +08:00
3220a04fa9 fix: use org/groupName replace groupName (#2180) 2023-08-06 20:16:44 +08:00
f06a4990bd fix: rename in init.go (#2179)
* fix: rename in init.go

* fix: remove blank line

* fix: remove blank line

* Update init.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-08-06 13:07:30 +08:00
9df7de5f27 Improve menu icons 2023-08-05 18:00:24 +08:00
56c808c091 Improve menu 2023-08-05 17:41:35 +08:00
9fd2421564 Update @ant-design/cssinjs dependency to avoid build error 2023-08-04 01:22:57 +08:00
689d45c7fa feat: fix org name cannot be changed bug 2023-08-03 18:48:37 +08:00
c24343bd53 Fix XxxChangeTrigger() doesn't return error bug 2023-08-03 18:45:49 +08:00
979f43638d Change builtInAvailableField to 10 2023-08-03 18:17:15 +08:00
685a4514cd fix: revert adapter port vartype to int (#2174) 2023-08-03 09:35:16 +08:00
a05ca3af24 feat: use role ID to search in GetPermissionsAndRolesByUser() (#2170) 2023-08-02 20:58:06 +08:00
c6f301ff9e Support svg in downloadImage() 2023-07-31 20:23:28 +08:00
d7b2bcf288 feat: support payment cancel state (#2165) 2023-07-31 15:24:13 +08:00
67ac3d6d21 Fix typo 2023-07-31 15:23:44 +08:00
45 changed files with 2045 additions and 404 deletions

View File

@ -90,7 +90,7 @@ func (c *ApiController) GetUsers() {
if limit == "" || page == "" {
if groupName != "" {
maskedUsers, err := object.GetMaskedUsers(object.GetGroupUsers(groupName))
maskedUsers, err := object.GetMaskedUsers(object.GetGroupUsers(util.GetId(owner, groupName)))
if err != nil {
c.ResponseError(err.Error())
return
@ -567,6 +567,22 @@ func (c *ApiController) RemoveUserFromGroup() {
name := c.Ctx.Request.Form.Get("name")
groupName := c.Ctx.Request.Form.Get("groupName")
c.Data["json"] = wrapActionResponse(object.RemoveUserFromGroup(owner, name, groupName))
c.ServeJSON()
organization, err := object.GetOrganization(util.GetId("admin", owner))
if err != nil {
return
}
item := object.GetAccountItemByName("Groups", organization)
res, msg := object.CheckAccountItemModifyRule(item, c.IsAdmin(), c.GetAcceptLanguage())
if !res {
c.ResponseError(msg)
return
}
affected, err := object.DeleteGroupForUser(util.GetId(owner, name), groupName)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(affected)
}

20
go.mod
View File

@ -12,9 +12,9 @@ require (
github.com/beevik/etree v1.1.0
github.com/casbin/casbin v1.9.1 // indirect
github.com/casbin/casbin/v2 v2.30.1
github.com/casdoor/go-sms-sender v0.6.1
github.com/casdoor/go-sms-sender v0.12.0
github.com/casdoor/gomail/v2 v2.0.1
github.com/casdoor/oss v1.2.1
github.com/casdoor/oss v1.3.0
github.com/casdoor/xorm-adapter/v3 v3.0.4
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
github.com/denisenkom/go-mssqldb v0.9.0
@ -28,9 +28,6 @@ require (
github.com/go-sql-driver/mysql v1.6.0
github.com/go-webauthn/webauthn v0.6.0
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.3.0
github.com/gorilla/mux v1.7.3 // indirect
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
@ -43,7 +40,7 @@ require (
github.com/nyaruka/phonenumbers v1.1.5
github.com/pquerna/otp v1.4.0
github.com/prometheus/client_golang v1.11.1
github.com/prometheus/client_model v0.2.0
github.com/prometheus/client_model v0.3.0
github.com/qiangmzsx/string-adapter/v2 v2.1.0
github.com/robfig/cron/v3 v3.0.1
github.com/russellhaering/gosaml2 v0.9.0
@ -53,7 +50,7 @@ require (
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/siddontang/go-log v0.0.0-20190221022429-1e957dd83bed
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/stretchr/testify v1.8.2
github.com/stretchr/testify v1.8.3
github.com/tealeg/xlsx v1.0.5
github.com/thanhpk/randstr v1.0.4
github.com/tklauser/go-sysconf v0.3.10 // indirect
@ -61,12 +58,11 @@ require (
github.com/xorm-io/core v0.7.4
github.com/xorm-io/xorm v1.1.6
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/crypto v0.6.0
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
golang.org/x/net v0.7.0
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
golang.org/x/crypto v0.11.0
golang.org/x/net v0.13.0
golang.org/x/oauth2 v0.10.0
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0
gopkg.in/yaml.v2 v2.4.0 // indirect
modernc.org/sqlite v1.10.1-0.20210314190707-798bbeb9bb84
modernc.org/sqlite v1.18.2
)

1420
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -132,7 +132,7 @@ func (idp *GiteeIdProvider) GetToken(code string) (*oauth2.Token, error) {
"type": "User",
"blog": null,
"weibo": null,
"bio": "个人博客https://gitee.com/xxx/xxx/pages",
"bio": "bio",
"public_repos": 2,
"public_gists": 0,
"followers": 0,

View File

@ -49,6 +49,7 @@ func main() {
object.InitLdapAutoSynchronizer()
proxy.InitHttpClient()
authz.InitApi()
object.InitUserManager()
util.SafeGoroutine(func() { object.RunSyncUsersJob() })

View File

@ -23,6 +23,7 @@ import (
"github.com/casdoor/casdoor/util"
xormadapter "github.com/casdoor/xorm-adapter/v3"
"github.com/xorm-io/core"
"github.com/xorm-io/xorm"
)
type Adapter struct {
@ -33,7 +34,7 @@ type Adapter struct {
Type string `xorm:"varchar(100)" json:"type"`
DatabaseType string `xorm:"varchar(100)" json:"databaseType"`
Host string `xorm:"varchar(100)" json:"host"`
Port string `xorm:"varchar(20)" json:"port"`
Port int `json:"port"`
User string `xorm:"varchar(100)" json:"user"`
Password string `xorm:"varchar(100)" json:"password"`
Database string `xorm:"varchar(100)" json:"database"`
@ -153,21 +154,24 @@ func (adapter *Adapter) initAdapter() error {
if adapter.Adapter == nil {
var dataSourceName string
if adapter.buildInAdapter() {
if adapter.builtInAdapter() {
dataSourceName = conf.GetConfigString("dataSourceName")
if adapter.DatabaseType == "mysql" {
dataSourceName = dataSourceName + adapter.Database
}
} else {
switch adapter.DatabaseType {
case "mssql":
dataSourceName = fmt.Sprintf("sqlserver://%s:%s@%s:%s?database=%s", adapter.User,
dataSourceName = fmt.Sprintf("sqlserver://%s:%s@%s:%d?database=%s", adapter.User,
adapter.Password, adapter.Host, adapter.Port, adapter.Database)
case "mysql":
dataSourceName = fmt.Sprintf("%s:%s@tcp(%s:%s)/", adapter.User,
adapter.Password, adapter.Host, adapter.Port)
dataSourceName = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", adapter.User,
adapter.Password, adapter.Host, adapter.Port, adapter.Database)
case "postgres":
dataSourceName = fmt.Sprintf("user=%s password=%s host=%s port=%s sslmode=disable dbname=%s", adapter.User,
dataSourceName = fmt.Sprintf("user=%s password=%s host=%s port=%d sslmode=disable dbname=%s", adapter.User,
adapter.Password, adapter.Host, adapter.Port, adapter.Database)
case "CockroachDB":
dataSourceName = fmt.Sprintf("user=%s password=%s host=%s port=%s sslmode=disable dbname=%s serial_normalization=virtual_sequence",
dataSourceName = fmt.Sprintf("user=%s password=%s host=%s port=%d sslmode=disable dbname=%s serial_normalization=virtual_sequence",
adapter.User, adapter.Password, adapter.Host, adapter.Port, adapter.Database)
case "sqlite3":
dataSourceName = fmt.Sprintf("file:%s", adapter.Host)
@ -181,7 +185,8 @@ func (adapter *Adapter) initAdapter() error {
}
var err error
adapter.Adapter, err = xormadapter.NewAdapterByEngineWithTableName(NewAdapter(adapter.DatabaseType, dataSourceName, adapter.Database).Engine, adapter.getTable(), adapter.TableNamePrefix)
engine, err := xorm.NewEngine(adapter.DatabaseType, dataSourceName)
adapter.Adapter, err = xormadapter.NewAdapterByEngineWithTableName(engine, adapter.getTable(), adapter.TableNamePrefix)
if err != nil {
return err
}
@ -269,6 +274,11 @@ func UpdatePolicy(oldPolicy, newPolicy []string, adapter *Adapter) (bool, error)
if err != nil {
return affected, err
}
err = adapter.SavePolicy(casbinModel)
if err != nil {
return false, err
}
return affected, nil
}
@ -285,6 +295,10 @@ func AddPolicy(policy []string, adapter *Adapter) (bool, error) {
}
casbinModel.AddPolicy("p", "p", policy)
err = adapter.SavePolicy(casbinModel)
if err != nil {
return false, err
}
return true, nil
}
@ -305,15 +319,20 @@ func RemovePolicy(policy []string, adapter *Adapter) (bool, error) {
if err != nil {
return affected, err
}
err = adapter.SavePolicy(casbinModel)
if err != nil {
return false, err
}
return affected, nil
}
func (adapter *Adapter) buildInAdapter() bool {
func (adapter *Adapter) builtInAdapter() bool {
if adapter.Owner != "built-in" {
return false
}
return adapter.Name == "permission-adapter-built-in" || adapter.Name == "api-adapter-built-in"
return adapter.Name == "user-adapter-built-in" || adapter.Name == "api-adapter-built-in"
}
func getModelDef() model.Model {

View File

@ -162,7 +162,7 @@ func UpdateCert(id string, cert *Cert) (bool, error) {
if name != cert.Name {
err := certChangeTrigger(name, cert.Name)
if err != nil {
return false, nil
return false, err
}
}
affected, err := ormer.Engine.ID(core.PK{owner, name}).AllCols().Update(cert)

View File

@ -15,7 +15,7 @@
package object
import (
"errors"
"fmt"
"github.com/casbin/casbin/v2"
"github.com/casdoor/casdoor/util"
@ -120,45 +120,50 @@ func DeleteEnforcer(enforcer *Enforcer) (bool, error) {
return affected != 0, nil
}
func (p *Enforcer) GetId() string {
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
}
func (enforcer *Enforcer) InitEnforcer() error {
if enforcer.Enforcer == nil {
if enforcer == nil {
return errors.New("enforcer is nil")
}
if enforcer.Model == "" || enforcer.Adapter == "" {
return errors.New("missing model or adapter")
}
var err error
var m *Model
var a *Adapter
if m, err = GetModel(enforcer.Model); err != nil {
return err
} else if m == nil {
return errors.New("model not found")
}
if a, err = GetAdapter(enforcer.Adapter); err != nil {
return err
} else if a == nil {
return errors.New("adapter not found")
}
err = m.initModel()
if err != nil {
return err
}
err = a.initAdapter()
if err != nil {
return err
}
casbinEnforcer, err := casbin.NewEnforcer(m.Model, a.Adapter)
if err != nil {
return err
}
enforcer.Enforcer = casbinEnforcer
if enforcer.Enforcer != nil {
return nil
}
if enforcer.Model == "" {
return fmt.Errorf("the model for enforcer: %s should not be empty", enforcer.GetId())
}
if enforcer.Adapter == "" {
return fmt.Errorf("the adapter for enforcer: %s should not be empty", enforcer.GetId())
}
m, err := GetModel(enforcer.Model)
if err != nil {
return err
} else if m == nil {
return fmt.Errorf("the model: %s for enforcer: %s is not found", enforcer.Model, enforcer.GetId())
}
a, err := GetAdapter(enforcer.Adapter)
if err != nil {
return err
} else if a == nil {
return fmt.Errorf("the adapter: %s for enforcer: %s is not found", enforcer.Adapter, enforcer.GetId())
}
err = m.initModel()
if err != nil {
return err
}
err = a.initAdapter()
if err != nil {
return err
}
casbinEnforcer, err := casbin.NewEnforcer(m.Model, a.Adapter)
if err != nil {
return err
}
enforcer.Enforcer = casbinEnforcer
return nil
}

View File

@ -164,7 +164,7 @@ func DeleteGroup(group *Group) (bool, error) {
return false, errors.New("group has children group")
}
if count, err := GetGroupUserCount(group.Name, "", ""); err != nil {
if count, err := GetGroupUserCount(group.GetId(), "", ""); err != nil {
return false, err
} else if count > 0 {
return false, errors.New("group has users")
@ -214,39 +214,33 @@ func ConvertToTreeData(groups []*Group, parentId string) []*Group {
return treeData
}
func RemoveUserFromGroup(owner, name, groupName string) (bool, error) {
user, err := getUser(owner, name)
func GetGroupUserCount(groupId string, field, value string) (int64, error) {
owner, _ := util.GetOwnerAndNameFromId(groupId)
names, err := userEnforcer.GetUserNamesByGroupName(groupId)
if err != nil {
return false, err
}
if user == nil {
return false, errors.New("user not exist")
return 0, err
}
user.Groups = util.DeleteVal(user.Groups, groupName)
affected, err := updateUser(user.GetId(), user, []string{"groups"})
if err != nil {
return false, err
}
return affected != 0, err
}
func GetGroupUserCount(groupName string, field, value string) (int64, error) {
if field == "" && value == "" {
return ormer.Engine.Where(builder.Like{"`groups`", groupName}).
Count(&User{})
return int64(len(names)), nil
} else {
return ormer.Engine.Table("user").
Where(builder.Like{"`groups`", groupName}).
Where("owner = ?", owner).In("name", names).
And(fmt.Sprintf("user.%s LIKE ?", util.CamelToSnakeCase(field)), "%"+value+"%").
Count()
}
}
func GetPaginationGroupUsers(groupName string, offset, limit int, field, value, sortField, sortOrder string) ([]*User, error) {
func GetPaginationGroupUsers(groupId string, offset, limit int, field, value, sortField, sortOrder string) ([]*User, error) {
users := []*User{}
owner, _ := util.GetOwnerAndNameFromId(groupId)
names, err := userEnforcer.GetUserNamesByGroupName(groupId)
if err != nil {
return nil, err
}
session := ormer.Engine.Table("user").
Where(builder.Like{"`groups`", groupName + "\""})
Where("owner = ?", owner).In("name", names)
if offset != -1 && limit != -1 {
session.Limit(limit, offset)
@ -265,7 +259,7 @@ func GetPaginationGroupUsers(groupName string, offset, limit int, field, value,
session = session.Desc(fmt.Sprintf("user.%s", util.SnakeString(sortField)))
}
err := session.Find(&users)
err = session.Find(&users)
if err != nil {
return nil, err
}
@ -273,15 +267,15 @@ func GetPaginationGroupUsers(groupName string, offset, limit int, field, value,
return users, nil
}
func GetGroupUsers(groupName string) ([]*User, error) {
func GetGroupUsers(groupId string) ([]*User, error) {
users := []*User{}
err := ormer.Engine.Table("user").
Where(builder.Like{"`groups`", groupName + "\""}).
Find(&users)
owner, _ := util.GetOwnerAndNameFromId(groupId)
names, err := userEnforcer.GetUserNamesByGroupName(groupId)
err = ormer.Engine.Where("owner = ?", owner).In("name", names).Find(&users)
if err != nil {
return nil, err
}
return users, nil
}

View File

@ -37,11 +37,11 @@ func InitDb() {
existed = initBuiltInApiModel()
if !existed {
initBuildInApiAdapter()
initBuiltInApiAdapter()
initBuiltInApiEnforcer()
initBuiltInPermissionModel()
initBuildInPermissionAdapter()
initBuiltInPermissionEnforcer()
initBuiltInUserModel()
initBuiltInUserAdapter()
initBuiltInUserEnforcer()
}
initWebAuthn()
@ -303,8 +303,8 @@ func initWebAuthn() {
gob.Register(webauthn.SessionData{})
}
func initBuiltInPermissionModel() {
model, err := GetModel("built-in/permission-model-built-in")
func initBuiltInUserModel() {
model, err := GetModel("built-in/user-model-built-in")
if err != nil {
panic(err)
}
@ -315,7 +315,7 @@ func initBuiltInPermissionModel() {
model = &Model{
Owner: "built-in",
Name: "permission-model-built-in",
Name: "user-model-built-in",
CreatedTime: util.GetCurrentTime(),
DisplayName: "Built-in Model",
IsEnabled: true,
@ -325,11 +325,14 @@ r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act`,
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act`,
}
_, err = AddModel(model)
if err != nil {
@ -347,8 +350,7 @@ func initBuiltInApiModel() bool {
return true
}
modelText := `
[request_definition]
modelText := `[request_definition]
r = subOwner, subName, method, urlPath, objOwner, objName
[policy_definition]
@ -367,8 +369,7 @@ m = (r.subOwner == p.subOwner || p.subOwner == "*") && \
(r.urlPath == p.urlPath || p.urlPath == "*") && \
(r.objOwner == p.objOwner || p.objOwner == "*") && \
(r.objName == p.objName || p.objName == "*") || \
(r.subOwner == r.objOwner && r.subName == r.objName)
`
(r.subOwner == r.objOwner && r.subName == r.objName)`
model = &Model{
Owner: "built-in",
@ -415,19 +416,19 @@ func initBuiltInPermission() {
}
}
func initBuildInPermissionAdapter() {
permissionAdapter, err := GetAdapter("built-in/permission-adapter-built-in")
func initBuiltInUserAdapter() {
adapter, err := GetAdapter("built-in/user-adapter-built-in")
if err != nil {
panic(err)
}
if permissionAdapter != nil {
if adapter != nil {
return
}
permissionAdapter = &Adapter{
adapter = &Adapter{
Owner: "built-in",
Name: "permission-adapter-built-in",
Name: "user-adapter-built-in",
CreatedTime: util.GetCurrentTime(),
Type: "Database",
DatabaseType: conf.GetConfigString("driverName"),
@ -436,23 +437,23 @@ func initBuildInPermissionAdapter() {
Table: "casbin_user_rule",
IsEnabled: true,
}
_, err = AddAdapter(permissionAdapter)
_, err = AddAdapter(adapter)
if err != nil {
panic(err)
}
}
func initBuildInApiAdapter() {
apiAdapter, err := GetAdapter("built-in/api-adapter-built-in")
func initBuiltInApiAdapter() {
adapter, err := GetAdapter("built-in/api-adapter-built-in")
if err != nil {
panic(err)
}
if apiAdapter != nil {
if adapter != nil {
return
}
apiAdapter = &Adapter{
adapter = &Adapter{
Owner: "built-in",
Name: "api-adapter-built-in",
CreatedTime: util.GetCurrentTime(),
@ -463,49 +464,49 @@ func initBuildInApiAdapter() {
Table: "casbin_api_rule",
IsEnabled: true,
}
_, err = AddAdapter(apiAdapter)
_, err = AddAdapter(adapter)
if err != nil {
panic(err)
}
}
func initBuiltInPermissionEnforcer() {
permissionEnforcer, err := GetEnforcer("built-in/permission-enforcer-built-in")
func initBuiltInUserEnforcer() {
enforcer, err := GetEnforcer("built-in/user-enforcer-built-in")
if err != nil {
panic(err)
}
if permissionEnforcer != nil {
if enforcer != nil {
return
}
permissionEnforcer = &Enforcer{
enforcer = &Enforcer{
Owner: "built-in",
Name: "permission-enforcer-built-in",
Name: "user-enforcer-built-in",
CreatedTime: util.GetCurrentTime(),
DisplayName: "Permission Enforcer",
Model: "built-in/permission-model-built-in",
Adapter: "built-in/permission-adapter-built-in",
Model: "built-in/user-model-built-in",
Adapter: "built-in/user-adapter-built-in",
IsEnabled: true,
}
_, err = AddEnforcer(permissionEnforcer)
_, err = AddEnforcer(enforcer)
if err != nil {
panic(err)
}
}
func initBuiltInApiEnforcer() {
apiEnforcer, err := GetEnforcer("built-in/api-enforcer-built-in")
enforcer, err := GetEnforcer("built-in/api-enforcer-built-in")
if err != nil {
panic(err)
}
if apiEnforcer != nil {
if enforcer != nil {
return
}
apiEnforcer = &Enforcer{
enforcer = &Enforcer{
Owner: "built-in",
Name: "api-enforcer-built-in",
CreatedTime: util.GetCurrentTime(),
@ -515,7 +516,7 @@ func initBuiltInApiEnforcer() {
IsEnabled: true,
}
_, err = AddEnforcer(apiEnforcer)
_, err = AddEnforcer(enforcer)
if err != nil {
panic(err)
}

View File

@ -41,6 +41,7 @@ type LdapUser struct {
GidNumber string `json:"gidNumber"`
// Gcn string
Uuid string `json:"uuid"`
UserPrincipalName string `json:"userPrincipalName"`
DisplayName string `json:"displayName"`
Mail string
Email string `json:"email"`
@ -51,9 +52,10 @@ type LdapUser struct {
RegisteredAddress string
PostalAddress string
GroupId string `json:"groupId"`
Phone string `json:"phone"`
Address string `json:"address"`
GroupId string `json:"groupId"`
Phone string `json:"phone"`
Address string `json:"address"`
MemberOf string `json:"memberOf"`
}
func (ldap *Ldap) GetLdapConn() (c *LdapConn, err error) {
@ -168,6 +170,8 @@ func (l *LdapConn) GetLdapUsers(ldapServer *Ldap) ([]LdapUser, error) {
user.Uuid = attribute.Values[0]
case "objectGUID":
user.Uuid = attribute.Values[0]
case "userPrincipalName":
user.UserPrincipalName = attribute.Values[0]
case "displayName":
user.DisplayName = attribute.Values[0]
case "mail":
@ -186,6 +190,8 @@ func (l *LdapConn) GetLdapUsers(ldapServer *Ldap) ([]LdapUser, error) {
user.RegisteredAddress = attribute.Values[0]
case "postalAddress":
user.PostalAddress = attribute.Values[0]
case "memberOf":
user.MemberOf = attribute.Values[0]
}
}
ldapUsers = append(ldapUsers, user)

View File

@ -189,7 +189,7 @@ func UpdateOrganization(id string, organization *Organization) (bool, error) {
if name != organization.Name {
err := organizationChangeTrigger(name, organization.Name)
if err != nil {
return false, nil
return false, err
}
}
@ -432,7 +432,7 @@ func organizationChangeTrigger(oldName string, newName string) error {
payment := new(Payment)
payment.Owner = newName
_, err = session.Where("organization=?", oldName).Update(payment)
_, err = session.Where("owner=?", oldName).Update(payment)
if err != nil {
return err
}

View File

@ -186,20 +186,23 @@ func notifyPayment(request *http.Request, body []byte, owner string, paymentName
notifyResult, err := pProvider.Notify(request, body, cert.AuthorityPublicKey, orderId)
if err != nil {
return payment, notifyResult, err
return payment, nil, err
}
if notifyResult.PaymentStatus != pp.PaymentStatePaid {
return payment, notifyResult, nil
}
// Only check paid payment
if notifyResult.ProductDisplayName != "" && notifyResult.ProductDisplayName != product.DisplayName {
err = fmt.Errorf("the payment's product name: %s doesn't equal to the expected product name: %s", notifyResult.ProductDisplayName, product.DisplayName)
return payment, notifyResult, err
return payment, nil, err
}
if notifyResult.Price != product.Price {
err = fmt.Errorf("the payment's price: %f doesn't equal to the expected price: %f", notifyResult.Price, product.Price)
return payment, notifyResult, err
return payment, nil, err
}
return payment, notifyResult, err
return payment, notifyResult, nil
}
func NotifyPayment(request *http.Request, body []byte, owner string, paymentName string, orderId string) (*Payment, error) {
@ -210,6 +213,7 @@ func NotifyPayment(request *http.Request, body []byte, owner string, paymentName
payment.Message = err.Error()
} else {
payment.State = notifyResult.PaymentStatus
payment.Message = notifyResult.NotifyMessage
}
_, err = UpdatePayment(payment.GetId(), payment)
if err != nil {

View File

@ -58,10 +58,7 @@ type PermissionRule struct {
Id string `xorm:"varchar(100) index not null default ''" json:"id"`
}
const (
builtInAvailableField = 5 // Casdoor built-in adapter, use V5 to filter permission, so has 5 available field
builtInAdapter = "permission_rule"
)
const builtInAvailableField = 5 // Casdoor built-in adapter, use V5 to filter permission, so has 5 available field
func (p *Permission) GetId() string {
return util.GetId(p.Owner, p.Name)
@ -290,7 +287,7 @@ func GetPermissionsAndRolesByUser(userId string) ([]*Permission, []*Role, error)
for _, role := range roles {
perms := []*Permission{}
err := ormer.Engine.Where("roles like ?", "%"+role.Name+"\"%").Find(&perms)
err := ormer.Engine.Where("roles like ?", "%"+role.GetId()+"\"%").Find(&perms)
if err != nil {
return nil, nil, err
}

View File

@ -335,7 +335,7 @@ m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act`
policyDefinition := strings.Split(cfg.String("policy_definition::p"), ",")
fieldsNum := len(policyDefinition)
if fieldsNum > builtInAvailableField {
panic(fmt.Errorf("the maximum policy_definition field number cannot exceed %d", builtInAvailableField))
panic(fmt.Errorf("the maximum policy_definition field number cannot exceed %d, got %d", builtInAvailableField, fieldsNum))
}
// filled empty field with "" and V5 with "permissionId"
for i := builtInAvailableField - fieldsNum; i > 0; i-- {

View File

@ -203,7 +203,7 @@ func UpdateProvider(id string, provider *Provider) (bool, error) {
if name != provider.Name {
err := providerChangeTrigger(name, provider.Name)
if err != nil {
return false, nil
return false, err
}
}
@ -254,7 +254,8 @@ func DeleteProvider(provider *Provider) (bool, error) {
func (p *Provider) getPaymentProvider() (pp.PaymentProvider, *Cert, error) {
cert := &Cert{}
if p.Cert != "" {
cert, err := getCert(p.Owner, p.Cert)
var err error
cert, err = getCert(p.Owner, p.Cert)
if err != nil {
return nil, nil, err
}

View File

@ -133,7 +133,7 @@ func UpdateRole(id string, role *Role) (bool, error) {
if name != role.Name {
err := roleChangeTrigger(name, role.Name)
if err != nil {
return false, nil
return false, err
}
}

View File

@ -24,7 +24,7 @@ func getSmsClient(provider *Provider) (sender.SmsClient, error) {
var client sender.SmsClient
var err error
if provider.Type == sender.HuaweiCloud {
if provider.Type == sender.HuaweiCloud || provider.Type == sender.AzureACS {
client, err = sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.ProviderUrl, provider.AppId)
} else {
client, err = sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.AppId)

View File

@ -25,6 +25,7 @@ type TableColumn struct {
Name string `json:"name"`
Type string `json:"type"`
CasdoorName string `json:"casdoorName"`
IsKey bool `json:"isKey"`
IsHashed bool `json:"isHashed"`
Values []string `json:"values"`
}

View File

@ -29,6 +29,23 @@ const (
UserPropertiesWechatOpenId = "wechatOpenId"
)
const UserEnforcerId = "built-in/user-enforcer-built-in"
var userEnforcer *UserGroupEnforcer
func InitUserManager() {
enforcer, err := GetEnforcer(UserEnforcerId)
if err != nil {
panic(err)
}
err = enforcer.InitEnforcer()
if err != nil {
panic(err)
}
userEnforcer = NewUserGroupEnforcer(enforcer.Enforcer)
}
type User struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"`
@ -230,7 +247,7 @@ func GetUserCount(owner, field, value string, groupName string) (int64, error) {
session := GetSession(owner, -1, -1, field, value, "", "")
if groupName != "" {
return GetGroupUserCount(groupName, field, value)
return GetGroupUserCount(util.GetId(owner, groupName), field, value)
}
return session.Count(&User{})
@ -274,7 +291,7 @@ func GetPaginationUsers(owner string, offset, limit int, field, value, sortField
users := []*User{}
if groupName != "" {
return GetPaginationGroupUsers(groupName, offset, limit, field, value, sortField, sortOrder)
return GetPaginationGroupUsers(util.GetId(owner, groupName), offset, limit, field, value, sortField, sortOrder)
}
session := GetSessionForUser(owner, offset, limit, field, value, sortField, sortOrder)
@ -531,6 +548,13 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
columns = append(columns, "name", "email", "phone", "country_code")
}
if util.ContainsString(columns, "groups") {
_, err := userEnforcer.UpdateGroupsForUser(user.GetId(), user.Groups)
if err != nil {
return false, err
}
}
affected, err := updateUser(id, user, columns)
if err != nil {
return false, err
@ -568,7 +592,7 @@ func UpdateUserForAllFields(id string, user *User) (bool, error) {
if name != user.Name {
err := userChangeTrigger(name, user.Name)
if err != nil {
return false, nil
return false, err
}
}
@ -778,6 +802,10 @@ func ExtendUserWithRolesAndPermissions(user *User) (err error) {
return
}
func DeleteGroupForUser(user string, group string) (bool, error) {
return userEnforcer.DeleteGroupForUser(user, group)
}
func userChangeTrigger(oldName string, newName string) error {
session := ormer.Engine.NewSession()
defer session.Close()

View File

@ -69,6 +69,8 @@ func downloadImage(client *http.Client, url string) (*bytes.Buffer, string, erro
fileExtension = ".ico"
case "image/x-icon":
fileExtension = ".ico"
case "image/svg+xml":
fileExtension = ".svg"
default:
return nil, "", fmt.Errorf("unsupported content type: %s", contentType)
}

95
object/user_enforcer.go Normal file
View File

@ -0,0 +1,95 @@
package object
import (
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/errors"
"github.com/casdoor/casdoor/util"
)
type UserGroupEnforcer struct {
// use rbac model implement use group, the enforcer can also implement user role
enforcer *casbin.Enforcer
}
func NewUserGroupEnforcer(enforcer *casbin.Enforcer) *UserGroupEnforcer {
return &UserGroupEnforcer{
enforcer: enforcer,
}
}
func (e *UserGroupEnforcer) AddGroupForUser(user string, group string) (bool, error) {
return e.enforcer.AddRoleForUser(user, GetGroupWithPrefix(group))
}
func (e *UserGroupEnforcer) AddGroupsForUser(user string, groups []string) (bool, error) {
g := make([]string, len(groups))
for i, group := range groups {
g[i] = GetGroupWithPrefix(group)
}
return e.enforcer.AddRolesForUser(user, g)
}
func (e *UserGroupEnforcer) DeleteGroupForUser(user string, group string) (bool, error) {
return e.enforcer.DeleteRoleForUser(user, GetGroupWithPrefix(group))
}
func (e *UserGroupEnforcer) DeleteGroupsForUser(user string) (bool, error) {
return e.enforcer.DeleteRolesForUser(user)
}
func (e *UserGroupEnforcer) GetGroupsForUser(user string) ([]string, error) {
groups, err := e.enforcer.GetRolesForUser(user)
for i, group := range groups {
groups[i] = GetGroupWithoutPrefix(group)
}
return groups, err
}
func (e *UserGroupEnforcer) GetAllUsersByGroup(group string) ([]string, error) {
users, err := e.enforcer.GetUsersForRole(GetGroupWithPrefix(group))
if err != nil {
if err == errors.ERR_NAME_NOT_FOUND {
return []string{}, nil
}
return nil, err
}
return users, nil
}
func GetGroupWithPrefix(group string) string {
return "group:" + group
}
func GetGroupWithoutPrefix(group string) string {
return group[len("group:"):]
}
func (e *UserGroupEnforcer) GetUserNamesByGroupName(groupName string) ([]string, error) {
var names []string
userIds, err := e.GetAllUsersByGroup(groupName)
if err != nil {
return nil, err
}
for _, userId := range userIds {
_, name := util.GetOwnerAndNameFromIdNoCheck(userId)
names = append(names, name)
}
return names, nil
}
func (e *UserGroupEnforcer) UpdateGroupsForUser(user string, groups []string) (bool, error) {
_, err := e.DeleteGroupsForUser(user)
if err != nil {
return false, err
}
affected, err := e.AddGroupsForUser(user, groups)
if err != nil {
return false, err
}
return affected, nil
}

View File

@ -17,6 +17,7 @@ package pp
import (
"context"
"errors"
"fmt"
"net/http"
"strconv"
@ -89,14 +90,24 @@ func (pp *PaypalPaymentProvider) Pay(providerName string, productName string, pa
}
func (pp *PaypalPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) {
notifyResult := &NotifyResult{}
captureRsp, err := pp.Client.OrderCapture(context.Background(), orderId, nil)
if err != nil {
return nil, err
}
if captureRsp.Code != paypal.Success {
errDetail := captureRsp.ErrorResponse.Details[0]
switch errDetail.Issue {
// If order is already captured, just skip this type of error and check the order detail
if !(len(captureRsp.ErrorResponse.Details) == 1 && captureRsp.ErrorResponse.Details[0].Issue == "ORDER_ALREADY_CAPTURED") {
return nil, errors.New(captureRsp.ErrorResponse.Message)
case "ORDER_ALREADY_CAPTURED":
// skip
case "ORDER_NOT_APPROVED":
notifyResult.PaymentStatus = PaymentStateCanceled
notifyResult.NotifyMessage = errDetail.Description
return notifyResult, nil
default:
err = fmt.Errorf(errDetail.Description)
return nil, err
}
}
// Check the order detail
@ -105,7 +116,16 @@ func (pp *PaypalPaymentProvider) Notify(request *http.Request, body []byte, auth
return nil, err
}
if captureRsp.Code != paypal.Success {
return nil, errors.New(captureRsp.ErrorResponse.Message)
errDetail := captureRsp.ErrorResponse.Details[0]
switch errDetail.Issue {
case "ORDER_NOT_APPROVED":
notifyResult.PaymentStatus = PaymentStateCanceled
notifyResult.NotifyMessage = errDetail.Description
return notifyResult, nil
default:
err = fmt.Errorf(errDetail.Description)
return nil, err
}
}
paymentName := detailRsp.Response.Id
@ -126,7 +146,7 @@ func (pp *PaypalPaymentProvider) Notify(request *http.Request, body []byte, auth
default:
paymentStatus = PaymentStateError
}
notifyResult := &NotifyResult{
notifyResult = &NotifyResult{
PaymentStatus: paymentStatus,
PaymentName: paymentName,

View File

@ -21,16 +21,18 @@ import (
type PaymentState string
const (
PaymentStatePaid PaymentState = "Paid"
PaymentStateCreated PaymentState = "Created"
PaymentStateError PaymentState = "Error"
PaymentStatePaid PaymentState = "Paid"
PaymentStateCreated PaymentState = "Created"
PaymentStateCanceled PaymentState = "Canceled"
PaymentStateError PaymentState = "Error"
)
type NotifyResult struct {
PaymentName string
PaymentStatus PaymentState
ProviderName string
NotifyMessage string
ProviderName string
ProductName string
ProductDisplayName string
Price float64

View File

@ -55,12 +55,6 @@ func StaticFilter(ctx *context.Context) {
path += urlPath
}
path2 := strings.TrimPrefix(path, "web/build/images/")
if util.FileExist(path2) {
makeGzipResponse(ctx.ResponseWriter, ctx.Request, path2)
return
}
if !util.FileExist(path) {
path = "web/build/index.html"
}

31
storage/google_cloud.go Normal file
View File

@ -0,0 +1,31 @@
// 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 storage
import (
"github.com/casdoor/oss"
"github.com/casdoor/oss/googlecloud"
)
func NewGoogleCloudStorageProvider(clientId string, clientSecret string, bucket string, endpoint string) oss.StorageInterface {
sp, _ := googlecloud.New(&googlecloud.Config{
AccessID: clientId,
AccessKey: clientSecret,
Bucket: bucket,
Endpoint: endpoint,
})
return sp
}

32
storage/qiniu_cloud.go Normal file
View File

@ -0,0 +1,32 @@
// 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 storage
import (
"github.com/casdoor/oss"
"github.com/casdoor/oss/qiniu"
)
func NewQiniuCloudKodoStorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
sp := qiniu.New(&qiniu.Config{
AccessID: clientId,
AccessKey: clientSecret,
Region: region,
Bucket: bucket,
Endpoint: endpoint,
})
return sp
}

View File

@ -30,6 +30,10 @@ func GetStorageProvider(providerType string, clientId string, clientSecret strin
return NewTencentCloudCosStorageProvider(clientId, clientSecret, region, bucket, endpoint)
case "Azure Blob":
return NewAzureBlobStorageProvider(clientId, clientSecret, region, bucket, endpoint)
case "Qiniu Cloud Kodo":
return NewQiniuCloudKodoStorageProvider(clientId, clientSecret, region, bucket, endpoint)
case "Google Cloud Storage":
return NewGoogleCloudStorageProvider(clientId, clientSecret, bucket, endpoint)
}
return nil

View File

@ -25,5 +25,12 @@ func CasbinToSlice(casbinRule xormadapter.CasbinRule) []string {
casbinRule.V4,
casbinRule.V5,
}
// remove empty strings from end, for update model policy map
for i := len(s) - 1; i >= 0; i-- {
if s[i] != "" {
s = s[:i+1]
break
}
}
return s
}

View File

@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@ant-design/cssinjs": "^1.8.1",
"@ant-design/cssinjs": "1.16.1",
"@ant-design/icons": "^4.7.0",
"@craco/craco": "^6.4.5",
"@crowdin/cli": "^3.7.10",

View File

@ -13,7 +13,7 @@
// limitations under the License.
import React from "react";
import {Button, Card, Col, Input, Row, Select, Switch} from "antd";
import {Button, Card, Col, Input, InputNumber, Row, Select, Switch} from "antd";
import * as AdapterBackend from "./backend/AdapterBackend";
import * as OrganizationBackend from "./backend/OrganizationBackend";
import * as Setting from "./Setting";
@ -107,8 +107,8 @@ class AdapterEditPage extends React.Component {
{Setting.getLabel(i18next.t("provider:Port"), i18next.t("provider:Port - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.adapter.port} onChange={e => {
this.updateAdapterField("port", e.target.value);
<InputNumber value={this.state.adapter.port} min={0} max={65535} onChange={value => {
this.updateAdapterField("port", value);
}} />
</Col>
</Row>

View File

@ -32,7 +32,7 @@ class AdapterListPage extends BaseListPage {
createdTime: moment().format(),
type: "Database",
host: "localhost",
port: "3306",
port: 3306,
user: "root",
password: "123456",
databaseType: "mysql",

View File

@ -18,7 +18,7 @@ import {Helmet} from "react-helmet";
import {MfaRuleRequired} from "./Setting";
import * as Setting from "./Setting";
import {StyleProvider, legacyLogicalPropertiesTransformer} from "@ant-design/cssinjs";
import {BarsOutlined, DownOutlined, InfoCircleFilled, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
import {AppstoreTwoTone, BarsOutlined, DollarTwoTone, DownOutlined, HomeTwoTone, InfoCircleFilled, LockTwoTone, LogoutOutlined, SafetyCertificateTwoTone, SettingOutlined, SettingTwoTone, WalletTwoTone} from "@ant-design/icons";
import {Alert, Avatar, Button, Card, ConfigProvider, Drawer, Dropdown, FloatButton, Layout, Menu, Result} from "antd";
import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom";
import OrganizationListPage from "./OrganizationListPage";
@ -91,6 +91,10 @@ import AccountAvatar from "./account/AccountAvatar";
const {Header, Footer, Content} = Layout;
import {setTwoToneColor} from "@ant-design/icons";
setTwoToneColor("rgb(87,52,211)");
class App extends Component {
constructor(props) {
super(props);
@ -147,58 +151,24 @@ class App extends Component {
});
if (uri === "/") {
this.setState({selectedMenuKey: "/"});
} else if (uri.includes("/organizations") || uri.includes("/trees")) {
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")) {
this.setState({selectedMenuKey: "/permissions"});
} else if (uri.includes("/models")) {
this.setState({selectedMenuKey: "/models"});
} else if (uri.includes("/adapters")) {
this.setState({selectedMenuKey: "/adapters"});
} else if (uri.includes("/enforcers")) {
this.setState({selectedMenuKey: "/enforcers"});
} else if (uri.includes("/providers")) {
this.setState({selectedMenuKey: "/providers"});
} else if (uri.includes("/applications")) {
this.setState({selectedMenuKey: "/applications"});
} else if (uri.includes("/resources")) {
this.setState({selectedMenuKey: "/resources"});
} else if (uri.includes("/records")) {
this.setState({selectedMenuKey: "/records"});
} else if (uri.includes("/tokens")) {
this.setState({selectedMenuKey: "/tokens"});
} else if (uri.includes("/sessions")) {
this.setState({selectedMenuKey: "/sessions"});
} else if (uri.includes("/webhooks")) {
this.setState({selectedMenuKey: "/webhooks"});
} else if (uri.includes("/syncers")) {
this.setState({selectedMenuKey: "/syncers"});
} else if (uri.includes("/certs")) {
this.setState({selectedMenuKey: "/certs"});
} else if (uri.includes("/products")) {
this.setState({selectedMenuKey: "/products"});
} else if (uri.includes("/payments")) {
this.setState({selectedMenuKey: "/payments"});
} else if (uri.includes("/organizations") || uri.includes("/trees") || uri.includes("/users") || uri.includes("/groups")) {
this.setState({selectedMenuKey: "/orgs"});
} else if (uri.includes("/roles") || uri.includes("/permissions") || uri.includes("/models") || uri.includes("/adapters") || uri.includes("/enforcers")) {
this.setState({selectedMenuKey: "/auth"});
} else if (uri.includes("/applications") || uri.includes("/providers") || uri.includes("/resources") || uri.includes("/certs")) {
this.setState({selectedMenuKey: "/identity"});
} else if (uri.includes("/records") || uri.includes("/tokens") || uri.includes("/sessions")) {
this.setState({selectedMenuKey: "/logs"});
} else if (uri.includes("/products") || uri.includes("/payments") || uri.includes("/plans") || uri.includes("/pricings") || uri.includes("/subscriptions")) {
this.setState({selectedMenuKey: "/business"});
} else if (uri.includes("/sysinfo") || uri.includes("/syncers") || uri.includes("/webhooks")) {
this.setState({selectedMenuKey: "/admin"});
} else if (uri.includes("/signup")) {
this.setState({selectedMenuKey: "/signup"});
} else if (uri.includes("/login")) {
this.setState({selectedMenuKey: "/login"});
} else if (uri.includes("/result")) {
this.setState({selectedMenuKey: "/result"});
} else if (uri.includes("/sysinfo")) {
this.setState({selectedMenuKey: "/sysinfo"});
} else if (uri.includes("/subscriptions")) {
this.setState({selectedMenuKey: "/subscriptions"});
} else if (uri.includes("/plans")) {
this.setState({selectedMenuKey: "/plans"});
} else if (uri.includes("/pricings")) {
this.setState({selectedMenuKey: "/pricings"});
} else {
this.setState({selectedMenuKey: -1});
}
@ -431,7 +401,7 @@ class App extends Component {
return [];
}
res.push(Setting.getItem(<Link to="/">{i18next.t("general:Home")}</Link>, "/"));
res.push(Setting.getItem(<Link to="/">{i18next.t("general:Home")}</Link>, "/", <HomeTwoTone />));
if (Setting.isLocalAdminUser(this.state.account)) {
if (Conf.ShowGithubCorner) {
@ -442,109 +412,54 @@ class App extends Component {
</a>, "#"));
}
res.push(Setting.getItem(<Link to="/organizations">{i18next.t("general:Organizations")}</Link>,
"/organizations"));
res.push(Setting.getItem(<Link style={{color: "black"}} to="/organizations">{i18next.t("general:User Management")}</Link>, "/orgs", <AppstoreTwoTone />, [
Setting.getItem(<Link to="/organizations">{i18next.t("general:Organizations")}</Link>, "/organizations"),
Setting.getItem(<Link to="/groups">{i18next.t("general:Groups")}</Link>, "/groups"),
Setting.getItem(<Link to="/users">{i18next.t("general:Users")}</Link>, "/users"),
]));
res.push(Setting.getItem(<Link to="/groups">{i18next.t("general:Groups")}</Link>,
"/groups"));
res.push(Setting.getItem(<Link to="/users">{i18next.t("general:Users")}</Link>,
"/users"
));
res.push(Setting.getItem(<Link to="/roles">{i18next.t("general:Roles")}</Link>,
"/roles"
));
res.push(Setting.getItem(<Link to="/permissions">{i18next.t("general:Permissions")}</Link>,
"/permissions"
));
res.push(Setting.getItem(<Link style={{color: "black"}} to="/roles">{i18next.t("general:Authorization")}</Link>, "/auth", <SafetyCertificateTwoTone />, [
Setting.getItem(<Link to="/roles">{i18next.t("general:Roles")}</Link>, "/roles"),
Setting.getItem(<Link to="/permissions">{i18next.t("general:Permissions")}</Link>, "/permissions"),
Setting.getItem(<Link to="/models">{i18next.t("general:Models")}</Link>, "/models"),
Setting.getItem(<Link to="/adapters">{i18next.t("general:Adapters")}</Link>, "/adapters"),
Setting.getItem(<Link to="/enforcers">{i18next.t("general:Enforcers")}</Link>, "/enforcers"),
].filter(item => {
if (!Setting.isLocalAdminUser(this.state.account) && ["/models", "/adapters", "/enforcers"].includes(item.key)) {
return false;
} else {
return true;
}
})));
}
if (Setting.isLocalAdminUser(this.state.account)) {
res.push(Setting.getItem(<Link to="/models">{i18next.t("general:Models")}</Link>,
"/models"
));
res.push(Setting.getItem(<Link style={{color: "black"}} to="/applications">{i18next.t("general:Identity")}</Link>, "/identity", <LockTwoTone />, [
Setting.getItem(<Link to="/applications">{i18next.t("general:Applications")}</Link>, "/applications"),
Setting.getItem(<Link to="/providers">{i18next.t("general:Providers")}</Link>, "/providers"),
Setting.getItem(<Link to="/resources">{i18next.t("general:Resources")}</Link>, "/resources"),
Setting.getItem(<Link to="/certs">{i18next.t("general:Certs")}</Link>, "/certs"),
]));
res.push(Setting.getItem(<Link to="/adapters">{i18next.t("general:Adapters")}</Link>,
"/adapters"
));
res.push(Setting.getItem(<Link style={{color: "black"}} to="/records">{i18next.t("general:Logging & Auditing")}</Link>, "/logs", <WalletTwoTone />, [
Setting.getItem(<Link to="/records">{i18next.t("general:Records")}</Link>, "/records"),
Setting.getItem(<Link to="/tokens">{i18next.t("general:Tokens")}</Link>, "/tokens"),
Setting.getItem(<Link to="/sessions">{i18next.t("general:Sessions")}</Link>, "/sessions"),
]));
res.push(Setting.getItem(<Link to="/enforcers">{i18next.t("general:Enforcers")}</Link>,
"/enforcers"
));
}
res.push(Setting.getItem(<Link style={{color: "black"}} to="/products">{i18next.t("general:Business & Payments")}</Link>, "/business", <DollarTwoTone />, [
Setting.getItem(<Link to="/products">{i18next.t("general:Products")}</Link>, "/products"),
Setting.getItem(<Link to="/payments">{i18next.t("general:Payments")}</Link>, "/payments"),
Setting.getItem(<Link to="/plans">{i18next.t("general:Plans")}</Link>, "/plans"),
Setting.getItem(<Link to="/pricings">{i18next.t("general:Pricings")}</Link>, "/pricings"),
Setting.getItem(<Link to="/subscriptions">{i18next.t("general:Subscriptions")}</Link>, "/subscriptions"),
]));
if (Setting.isLocalAdminUser(this.state.account)) {
res.push(Setting.getItem(<Link to="/applications">{i18next.t("general:Applications")}</Link>,
"/applications"
));
res.push(Setting.getItem(<Link to="/providers">{i18next.t("general:Providers")}</Link>,
"/providers"
));
res.push(Setting.getItem(<Link to="/resources">{i18next.t("general:Resources")}</Link>,
"/resources"
));
res.push(Setting.getItem(<Link to="/records">{i18next.t("general:Records")}</Link>,
"/records"
));
res.push(Setting.getItem(<Link to="/plans">{i18next.t("general:Plans")}</Link>,
"/plans"
));
res.push(Setting.getItem(<Link to="/pricings">{i18next.t("general:Pricings")}</Link>,
"/pricings"
));
res.push(Setting.getItem(<Link to="/subscriptions">{i18next.t("general:Subscriptions")}</Link>,
"/subscriptions"
));
}
if (Setting.isLocalAdminUser(this.state.account)) {
res.push(Setting.getItem(<Link to="/tokens">{i18next.t("general:Tokens")}</Link>,
"/tokens"
));
res.push(Setting.getItem(<Link to="/sessions">{i18next.t("general:Sessions")}</Link>,
"/sessions"
));
res.push(Setting.getItem(<Link to="/webhooks">{i18next.t("general:Webhooks")}</Link>,
"/webhooks"
));
res.push(Setting.getItem(<Link to="/syncers">{i18next.t("general:Syncers")}</Link>,
"/syncers"
));
res.push(Setting.getItem(<Link to="/certs">{i18next.t("general:Certs")}</Link>,
"/certs"
));
if (Conf.EnableExtraPages) {
res.push(Setting.getItem(<Link to="/products">{i18next.t("general:Products")}</Link>,
"/products"
));
res.push(Setting.getItem(<Link to="/payments">{i18next.t("general:Payments")}</Link>,
"/payments"
));
}
}
if (Setting.isAdminUser(this.state.account)) {
res.push(Setting.getItem(<Link to="/sysinfo">{i18next.t("general:System Info")}</Link>,
"/sysinfo"
));
res.push(Setting.getItem(<a target="_blank" rel="noreferrer"
href={Setting.isLocalhost() ? `${Setting.ServerUrl}/swagger` : "/swagger"}>{i18next.t("general:Swagger")}</a>,
"/swagger"
));
res.push(Setting.getItem(<Link style={{color: "black"}} to="/sysinfo">{i18next.t("general:Admin")}</Link>, "/admin", <SettingTwoTone />, [
Setting.getItem(<Link to="/sysinfo">{i18next.t("general:System Info")}</Link>, "/sysinfo"),
Setting.getItem(<Link to="/syncers">{i18next.t("general:Syncers")}</Link>, "/syncers"),
Setting.getItem(<Link to="/webhooks">{i18next.t("general:Webhooks")}</Link>, "/webhooks"),
Setting.getItem(<a target="_blank" rel="noreferrer" href={Setting.isLocalhost() ? `${Setting.ServerUrl}/swagger` : "/swagger"}>{i18next.t("general:Swagger")}</a>, "/swagger")]));
}
return res;

View File

@ -20,8 +20,6 @@ export const IsDemoMode = false;
export const ForceLanguage = "";
export const DefaultLanguage = "en";
export const EnableExtraPages = true;
export const InitThemeAlgorithm = true;
export const ThemeDefault = {
themeType: "default",

View File

@ -221,6 +221,7 @@ class GroupTreePage extends React.Component {
onChange={(value) => {
this.setState({
organizationName: value,
groupName: "",
});
this.props.history.push(`/trees/${value}`);
}}

View File

@ -163,7 +163,7 @@ class PaymentEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col>
<Col span={22} >
<Input disabled={true} value={this.state.payment.organization} onChange={e => {
<Input disabled={true} value={this.state.payment.owner} onChange={e => {
// this.updatePaymentField('organization', e.target.value);
}} />
</Col>

View File

@ -115,6 +115,26 @@ class PaymentResultPage extends React.Component {
/>
</div>
);
} else if (payment.state === "Canceled") {
return (
<div>
{
Setting.renderHelmet(payment)
}
<Result
status="warning"
title={`${i18next.t("payment:The payment has been canceled")}: ${payment.productDisplayName}, ${i18next.t("payment:the current state is")}: ${payment.state}`}
subTitle={i18next.t("payment:Please click the below button to return to the original website")}
extra={[
<Button type="primary" key="returnUrl" onClick={() => {
this.goToPaymentUrl(payment);
}}>
{i18next.t("payment:Return to Website")}
</Button>,
]}
/>
</div>
);
} else {
return (
<div>
@ -124,7 +144,7 @@ class PaymentResultPage extends React.Component {
<Result
status="error"
title={`${i18next.t("payment:The payment has failed")}: ${payment.productDisplayName}, ${i18next.t("payment:the current state is")}: ${payment.state}`}
subTitle={i18next.t("payment:Please click the below button to return to the original website")}
subTitle={`${i18next.t("payment:Failed reason")}: ${payment.message}`}
extra={[
<Button type="primary" key="returnUrl" onClick={() => {
this.goToPaymentUrl(payment);

View File

@ -134,10 +134,14 @@ class ProviderEditPage extends React.Component {
case "Email":
return Setting.getLabel(i18next.t("signup:Username"), i18next.t("signup:Username - Tooltip"));
case "SMS":
if (provider.type === "Volc Engine SMS") {
if (provider.type === "Volc Engine SMS" || provider.type === "Amazon SNS" || provider.type === "Baidu Cloud SMS") {
return Setting.getLabel(i18next.t("provider:Access key"), i18next.t("provider:Access key - Tooltip"));
} else if (provider.type === "Huawei Cloud SMS") {
return Setting.getLabel(i18next.t("provider:App key"), i18next.t("provider:App key - Tooltip"));
} else if (provider.type === "UCloud SMS") {
return Setting.getLabel(i18next.t("provider:Public key"), i18next.t("provider:Public key - Tooltip"));
} else if (provider.type === "Msg91 SMS" || provider.type === "Infobip SMS") {
return Setting.getLabel(i18next.t("provider:Sender Id"), i18next.t("provider:Sender Id - Tooltip"));
} else {
return Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"));
}
@ -157,10 +161,16 @@ class ProviderEditPage extends React.Component {
case "Email":
return Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"));
case "SMS":
if (provider.type === "Volc Engine SMS") {
if (provider.type === "Volc Engine SMS" || provider.type === "Amazon SNS" || provider.type === "Baidu Cloud SMS") {
return Setting.getLabel(i18next.t("provider:Secret access key"), i18next.t("provider:Secret access key - Tooltip"));
} else if (provider.type === "Huawei Cloud SMS") {
return Setting.getLabel(i18next.t("provider:App secret"), i18next.t("provider:AppSecret - Tooltip"));
} else if (provider.type === "UCloud SMS") {
return Setting.getLabel(i18next.t("provider:Private Key"), i18next.t("provider:Private Key - Tooltip"));
} else if (provider.type === "Msg91 SMS") {
return Setting.getLabel(i18next.t("provider:Auth Key"), i18next.t("provider:Auth Key - Tooltip"));
} else if (provider.type === "Infobip SMS") {
return Setting.getLabel(i18next.t("provider:Api Key"), i18next.t("provider:Api Key - Tooltip"));
} else {
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
}
@ -234,7 +244,7 @@ class ProviderEditPage extends React.Component {
tooltip = i18next.t("provider:Agent ID - Tooltip");
}
} else if (provider.category === "SMS") {
if (provider.type === "Twilio SMS") {
if (provider.type === "Twilio SMS" || provider.type === "Azure ACS") {
text = i18next.t("provider:Sender number");
tooltip = i18next.t("provider:Sender number - Tooltip");
} else if (provider.type === "Tencent Cloud SMS") {
@ -246,6 +256,18 @@ class ProviderEditPage extends React.Component {
} else if (provider.type === "Huawei Cloud SMS") {
text = i18next.t("provider:Channel No.");
tooltip = i18next.t("provider:Channel No. - Tooltip");
} else if (provider.type === "Amazon SNS") {
text = i18next.t("provider:Region");
tooltip = i18next.t("provider:Region - Tooltip");
} else if (provider.type === "Baidu Cloud SMS") {
text = i18next.t("provider:Endpoint");
tooltip = i18next.t("provider:Endpoint - Tooltip");
} else if (provider.type === "Infobip SMS") {
text = i18next.t("provider:Base URL");
tooltip = i18next.t("provider:Base URL - Tooltip");
} else if (provider.type === "UCloud SMS") {
text = i18next.t("provider:Project Id");
tooltip = i18next.t("provider:Project Id - Tooltip");
}
} else if (provider.category === "Email") {
if (provider.type === "SUBMAIL") {
@ -622,7 +644,7 @@ class ProviderEditPage extends React.Component {
</Col>
</Row>
)}
{["Local File System", "MinIO", "Tencent Cloud COS"].includes(this.state.provider.type) ? null : (
{["Local File System", "MinIO", "Tencent Cloud COS", "Google Cloud Storage", "Qiniu Cloud Kodo"].includes(this.state.provider.type) ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Endpoint (Intranet)"), i18next.t("provider:Region endpoint for Intranet"))} :
@ -656,7 +678,7 @@ class ProviderEditPage extends React.Component {
}} />
</Col>
</Row>
{["MinIO"].includes(this.state.provider.type) ? null : (
{["MinIO", "Google Cloud Storage", "Qiniu Cloud Kodo"].includes(this.state.provider.type) ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
@ -668,7 +690,7 @@ class ProviderEditPage extends React.Component {
</Col>
</Row>
)}
{["AWS S3", "Tencent Cloud COS"].includes(this.state.provider.type) ? (
{["AWS S3", "Tencent Cloud COS", "Qiniu Cloud Kodo"].includes(this.state.provider.type) ? (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Region ID"), i18next.t("provider:Region ID - Tooltip"))} :
@ -757,7 +779,7 @@ class ProviderEditPage extends React.Component {
</React.Fragment>
) : this.state.provider.category === "SMS" ? (
<React.Fragment>
{this.state.provider.type === "Twilio SMS" ?
{["Twilio SMS", "Amazon SNS", "Azure ACS", "Msg91 SMS", "Infobip SMS"].includes(this.state.provider.type) ?
null :
(<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
@ -771,16 +793,20 @@ class ProviderEditPage extends React.Component {
</Row>
)
}
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Template code"), i18next.t("provider:Template code - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.templateCode} onChange={e => {
this.updateProviderField("templateCode", e.target.value);
}} />
</Col>
</Row>
{["Infobip SMS"].includes(this.state.provider.type) ?
null :
(<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Template code"), i18next.t("provider:Template code - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.templateCode} onChange={e => {
this.updateProviderField("templateCode", e.target.value);
}} />
</Col>
</Row>
)
}
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:SMS Test"), i18next.t("provider:SMS Test - Tooltip"))} :

View File

@ -37,7 +37,7 @@ class ProviderListPage extends BaseListPage {
newProvider() {
const randomName = Setting.getRandomName();
const owner = Setting.isDefaultOrganizationSelected(this.props.account) ? this.state.owner : Setting.getRequestOrganization();
const owner = Setting.isDefaultOrganizationSelected(this.props.account) ? this.state.owner : Setting.getRequestOrganization(this.props.account);
return {
owner: owner,
name: `provider_${randomName}`,

View File

@ -85,10 +85,26 @@ export const OtherProviderInfo = {
logo: `${StaticBaseUrl}/img/social_aliyun.png`,
url: "https://aliyun.com/product/sms",
},
"Amazon SNS": {
logo: `${StaticBaseUrl}/img/social_aws.png`,
url: "https://aws.amazon.com/cn/sns/",
},
"Azure ACS": {
logo: `${StaticBaseUrl}/img/social_azure.png`,
url: "https://azure.microsoft.com/en-us/products/communication-services",
},
"Infobip SMS": {
logo: `${StaticBaseUrl}/img/social_infobip.png`,
url: "https://portal.infobip.com/homepage/",
},
"Tencent Cloud SMS": {
logo: `${StaticBaseUrl}/img/social_tencent_cloud.jpg`,
url: "https://cloud.tencent.com/product/sms",
},
"Baidu Cloud SMS": {
logo: `${StaticBaseUrl}/img/social_baidu_cloud.png`,
url: "https://cloud.baidu.com/product/sms.html",
},
"Volc Engine SMS": {
logo: `${StaticBaseUrl}/img/social_volc_engine.jpg`,
url: "https://www.volcengine.com/products/cloud-sms",
@ -97,6 +113,10 @@ export const OtherProviderInfo = {
logo: `${StaticBaseUrl}/img/social_huawei.png`,
url: "https://www.huaweicloud.com/product/msgsms.html",
},
"UCloud SMS": {
logo: `${StaticBaseUrl}/img/social_ucloud.png`,
url: "https://www.ucloud.cn/site/product/usms.html",
},
"Twilio SMS": {
logo: `${StaticBaseUrl}/img/social_twilio.svg`,
url: "https://www.twilio.com/messaging",
@ -109,6 +129,10 @@ export const OtherProviderInfo = {
logo: `${StaticBaseUrl}/img/social_submail.svg`,
url: "https://www.mysubmail.com",
},
"Msg91 SMS": {
logo: `${StaticBaseUrl}/img/social_msg91.ico`,
url: "https://control.msg91.com/app/",
},
"Mock SMS": {
logo: `${StaticBaseUrl}/img/social_default.png`,
url: "",
@ -153,6 +177,14 @@ export const OtherProviderInfo = {
logo: `${StaticBaseUrl}/img/social_azure.png`,
url: "https://azure.microsoft.com/en-us/services/storage/blobs/",
},
"Qiniu Cloud Kodo": {
logo: `${StaticBaseUrl}/img/social_qiniu_cloud.png`,
url: "https://www.qiniu.com/solutions/storage",
},
"Google Cloud Storage": {
logo: `${StaticBaseUrl}/img/social_google_cloud.png`,
url: "https://cloud.google.com/storage",
},
},
SAML: {
"Aliyun IDaaS": {
@ -853,13 +885,19 @@ export function getProviderTypeOptions(category) {
} else if (category === "SMS") {
return (
[
{id: "Aliyun SMS", name: "Aliyun SMS"},
{id: "Aliyun SMS", name: "Alibaba Cloud SMS"},
{id: "Amazon SNS", name: "Amazon SNS"},
{id: "Azure ACS", name: "Azure ACS"},
{id: "Infobip SMS", name: "Infobip SMS"},
{id: "Tencent Cloud SMS", name: "Tencent Cloud SMS"},
{id: "Baidu Cloud SMS", name: "Baidu Cloud SMS"},
{id: "Volc Engine SMS", name: "Volc Engine SMS"},
{id: "Huawei Cloud SMS", name: "Huawei Cloud SMS"},
{id: "UCloud SMS", name: "UCloud SMS"},
{id: "Twilio SMS", name: "Twilio SMS"},
{id: "SmsBao SMS", name: "SmsBao SMS"},
{id: "SUBMAIL SMS", name: "SUBMAIL SMS"},
{id: "Msg91 SMS", name: "Msg91 SMS"},
]
);
} else if (category === "Storage") {
@ -871,6 +909,8 @@ export function getProviderTypeOptions(category) {
{id: "Aliyun OSS", name: "Aliyun OSS"},
{id: "Tencent Cloud COS", name: "Tencent Cloud COS"},
{id: "Azure Blob", name: "Azure Blob"},
{id: "Qiniu Cloud Kodo", name: "Qiniu Cloud Kodo"},
{id: "Google Cloud Storage", name: "Google Cloud Storage"},
]
);
} else if (category === "SAML") {
@ -1043,13 +1083,7 @@ export function getLabel(text, tooltip) {
}
export function getItem(label, key, icon, children, type) {
return {
key,
icon,
children,
label,
type,
};
return {label: label, key: key, icon: icon, children: children, type: type};
}
export function getOption(label, value) {
@ -1179,11 +1213,11 @@ export function isDefaultOrganizationSelected(account) {
const BuiltInObjects = [
"api-enforcer-built-in",
"permission-enforcer-built-in",
"user-enforcer-built-in",
"api-model-built-in",
"permission-model-built-in",
"user-model-built-in",
"api-adapter-built-in",
"permission-adapter-built-in",
"user-adapter-built-in",
];
export function builtInObject(obj) {

View File

@ -133,13 +133,25 @@ class UserEditPage extends React.Component {
this.setState({
application: res.data,
isGroupsVisible: res.data?.organizationObj.accountItems?.some((item) => item.name === "Groups" && item.visible),
});
});
}
getUserOrganization() {
return this.state.organizations.filter(organization => organization.name === this.state.user.owner)[0];
}
isGroupsVisible() {
const organization = this.getUserOrganization();
if (!organization) {
return false;
} else {
return organization.accountItems?.some((item) => item.name === "Groups" && item.visible);
}
}
getGroups(organizationName) {
if (this.state.isGroupsVisible) {
if (this.isGroupsVisible()) {
GroupBackend.getGroups(organizationName)
.then((res) => {
if (res.status === "ok") {
@ -319,7 +331,7 @@ class UserEditPage extends React.Component {
})}
>
{
this.state.groups?.map((group) => <Option key={group.name} value={group.name}>
this.state.groups?.map((group) => <Option key={group.name} value={`${group.owner}/${group.name}`}>
<Space>
{group.type === "Physical" ? <UsergroupAddOutlined /> : <HolderOutlined />}
{group.displayName}
@ -401,7 +413,7 @@ class UserEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"))} :
</Col>
<Col span={22} >
<PasswordModal user={this.state.user} organization={this.state.application?.organizationObj} account={this.props.account} disabled={disabled} />
<PasswordModal user={this.state.user} organization={this.getUserOrganization()} account={this.props.account} disabled={disabled} />
</Col>
</Row>
);
@ -442,7 +454,7 @@ class UserEditPage extends React.Component {
onChange={(value) => {
this.updateUserField("countryCode", value);
}}
countryCodes={this.state.application?.organizationObj.countryCodes}
countryCodes={this.getUserOrganization()?.countryCodes}
/>
<Input value={this.state.user.phone}
style={{width: "70%"}}
@ -599,10 +611,10 @@ class UserEditPage extends React.Component {
</Col>
<Col span={22} >
{
this.state.application?.organizationObj.tags?.length > 0 ? (
this.getUserOrganization()?.tags?.length > 0 ? (
<Select virtual={false} style={{width: "100%"}} value={this.state.user.tag}
onChange={(value => {this.updateUserField("tag", value);})}
options={this.state.application.organizationObj.tags?.map((tag) => {
options={this.getUserOrganization()?.tags?.map((tag) => {
const tokens = tag.split("|");
const value = tokens[0];
const displayValue = Setting.getLanguage() !== "zh" ? tokens[0] : tokens[1];
@ -888,7 +900,7 @@ class UserEditPage extends React.Component {
{Setting.getLabel(i18next.t("mfa:Multi-factor authentication"), i18next.t("mfa:Multi-factor authentication - Tooltip "))} :
</Col>
<Col span={22} >
<Card title={i18next.t("mfa:Multi-factor methods")}
<Card size="small" title={i18next.t("mfa:Multi-factor methods")}
extra={this.state.multiFactorAuths?.some(mfaProps => mfaProps.enabled) ?
<PopconfirmModal
text={i18next.t("general:Disable")}
@ -1008,7 +1020,7 @@ class UserEditPage extends React.Component {
</div>
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
{
this.state.application?.organizationObj.accountItems?.map(accountItem => {
this.getUserOrganization()?.accountItems?.map(accountItem => {
return (
<React.Fragment key={accountItem.name}>
{

View File

@ -76,7 +76,7 @@ class UserListPage extends BaseListPage {
phone: Setting.getRandomNumber(),
countryCode: this.state.organization.countryCodes?.length > 0 ? this.state.organization.countryCodes[0] : "",
address: [],
groups: this.props.groupName ? [this.props.groupName] : [],
groups: this.props.groupName ? [`${owner}/${this.props.groupName}`] : [],
affiliation: "Example Inc.",
tag: "staff",
region: "",

View File

@ -287,10 +287,10 @@ class PolicyTable extends React.Component {
) : (
<div>
<Tooltip placement="topLeft" title="Edit">
<Button disabled={this.state.editingIndex !== ""} style={{marginRight: "5px"}} icon={<EditOutlined />} size="small" onClick={() => this.edit(record, index)} />
<Button disabled={this.state.editingIndex !== "" || Setting.builtInObject({owner: this.props.owner, name: this.props.name})} style={{marginRight: "5px"}} icon={<EditOutlined />} size="small" onClick={() => this.edit(record, index)} />
</Tooltip>
<Tooltip placement="topLeft" title="Delete">
<Button disabled={this.state.editingIndex !== ""} style={{marginRight: "5px"}} icon={<DeleteOutlined />} size="small" onClick={() => this.deletePolicy(table, index)} />
<Button disabled={this.state.editingIndex !== "" || Setting.builtInObject({owner: this.props.owner, name: this.props.name})} style={{marginRight: "5px"}} icon={<DeleteOutlined />} size="small" onClick={() => this.deletePolicy(table, index)} />
</Tooltip>
</div>
);
@ -304,14 +304,14 @@ class PolicyTable extends React.Component {
onChange: (page) => this.setState({
page: page,
}),
disabled: this.state.editingIndex !== "",
disabled: this.state.editingIndex !== "" || Setting.builtInObject({owner: this.props.owner, name: this.props.name}),
current: this.state.page,
}}
columns={columns} dataSource={table} rowKey="key" size="middle" bordered
loading={this.state.loading}
title={() => (
<div>
<Button disabled={this.state.editingIndex !== ""} style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
<Button disabled={this.state.editingIndex !== "" || Setting.builtInObject({owner: this.props.owner, name: this.props.name})} style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
</div>
)}
/>

View File

@ -38,7 +38,7 @@ class SyncerTableColumnTable extends React.Component {
}
addRow(table) {
const row = {name: `column${table.length}`, type: "string", values: []};
const row = {name: `column${table.length}`, type: "string", values: [], isKey: table.filter(row => row.isKey).length === 0};
if (table === undefined) {
table = [];
}
@ -107,6 +107,26 @@ class SyncerTableColumnTable extends React.Component {
);
},
},
{
title: i18next.t("syncer:Is key"),
dataIndex: "isKey",
key: "isKey",
render: (text, record, index) => {
return (
<Switch checked={text} onChange={checked => {
if (!record.isKey && checked) {
table.forEach((row, i) => {
this.updateField(table, i, "isKey", false);
});
} else if (record.isKey && !checked) {
return;
}
this.updateField(table, index, "isKey", checked);
}} />
);
},
},
{
title: i18next.t("syncer:Is hashed"),
dataIndex: "isHashed",
@ -133,7 +153,7 @@ class SyncerTableColumnTable extends React.Component {
<Button style={{marginRight: "5px"}} disabled={index === table.length - 1} icon={<DownOutlined />} size="small" onClick={() => this.downRow(table, index)} />
</Tooltip>
<Tooltip placement="topLeft" title={i18next.t("general:Delete")}>
<Button icon={<DeleteOutlined />} size="small" onClick={() => this.deleteRow(table, index)} />
<Button icon={<DeleteOutlined />} disabled={record.isKey && table.length > 1} size="small" onClick={() => this.deleteRow(table, index)} />
</Tooltip>
</div>
);

View File

@ -29,7 +29,20 @@
dependencies:
"@ctrl/tinycolor" "^3.4.0"
"@ant-design/cssinjs@^1", "@ant-design/cssinjs@^1.10.1", "@ant-design/cssinjs@^1.5.6", "@ant-design/cssinjs@^1.8.1":
"@ant-design/cssinjs@1.16.1":
version "1.16.1"
resolved "https://registry.yarnpkg.com/@ant-design/cssinjs/-/cssinjs-1.16.1.tgz#0032044db5678dd25ac12def1abb1d52e6a4d583"
integrity sha512-KKVB5Or6BDC1Bo3Y4KMlOkyQU0P+6GTodubrQ9YfrtXG1TgO4wpaEfg9I4ZA49R7M+Ij2KKNwb+5abvmXy6K8w==
dependencies:
"@babel/runtime" "^7.11.1"
"@emotion/hash" "^0.8.0"
"@emotion/unitless" "^0.7.5"
classnames "^2.3.1"
csstype "^3.0.10"
rc-util "^5.35.0"
stylis "^4.0.13"
"@ant-design/cssinjs@^1", "@ant-design/cssinjs@^1.10.1", "@ant-design/cssinjs@^1.5.6":
version "1.10.1"
resolved "https://registry.yarnpkg.com/@ant-design/cssinjs/-/cssinjs-1.10.1.tgz#c9173f38e3d61f0883ca3c17d7cf1e30784e0dd7"
integrity sha512-PSoJS8RMzn95ZRg007dJGr6AU0Zim/O+tTN0xmXmh9CkIl4y3wuOr2Zhehaj7s130wPSYDVvahf3DKT50w/Zhw==
@ -10100,6 +10113,14 @@ rc-util@^5.0.1, rc-util@^5.0.6, rc-util@^5.15.0, rc-util@^5.16.0, rc-util@^5.16.
"@babel/runtime" "^7.18.3"
react-is "^16.12.0"
rc-util@^5.35.0:
version "5.35.0"
resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.35.0.tgz#bed1986248b7be525cc0894109e609ac60207f29"
integrity sha512-MTXlixb3EoSTEchsOc7XWsVyoUQqoCsh2Z1a2IptwNgqleMF6ZgQeY52UzUbNj5CcVBg9YljOWjuOV07jSSm4Q==
dependencies:
"@babel/runtime" "^7.18.3"
react-is "^16.12.0"
rc-virtual-list@^3.4.13, rc-virtual-list@^3.5.1, rc-virtual-list@^3.5.2:
version "3.5.2"
resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-3.5.2.tgz#5e1028869bae900eacbae6788d4eca7210736006"