Compare commits

...

11 Commits

Author SHA1 Message Date
Yaodong Yu
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
Yang Luo
eafaa135b4 Change builtInAvailableField back to 5 2023-08-11 02:45:11 +08:00
Yang Luo
6746551447 Improve error message in InitEnforcer() 2023-08-11 02:36:29 +08:00
Yang Luo
3cb46c3628 Add isKey to syncer's table 2023-08-09 00:33:04 +08:00
Yaodong Yu
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
Yang Luo
bb937c30c1 Fix empty cert in getPaymentProvider() 2023-08-08 22:37:48 +08:00
Baihhh
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
Yang Luo
62b2082e82 Add getUserOrganization() to user edit page 2023-08-08 21:58:27 +08:00
Yang Luo
a1806439f8 Add UserPrincipalName and MemberOf to get-ldap-users API 2023-08-08 20:18:47 +08:00
Yang Luo
01e58158b7 feat: Remove useless code 2023-08-08 19:16:55 +08:00
Yaodong Yu
15427ad9d6 fix: fix add provider error (#2184) 2023-08-07 17:22:32 +08:00
25 changed files with 1749 additions and 148 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, util.GetId(owner, 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)
}

18
go.mod
View File

@@ -14,7 +14,7 @@ require (
github.com/casbin/casbin/v2 v2.30.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
)

1402
go.sum

File diff suppressed because it is too large Load Diff

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 {
@@ -155,14 +156,17 @@ func (adapter *Adapter) initAdapter() error {
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:%d?database=%s", adapter.User,
adapter.Password, adapter.Host, adapter.Port, adapter.Database)
case "mysql":
dataSourceName = fmt.Sprintf("%s:%s@tcp(%s:%d)/", 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=%d sslmode=disable dbname=%s", adapter.User,
adapter.Password, adapter.Host, adapter.Port, adapter.Database)
@@ -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,6 +319,11 @@ 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
}
@@ -313,7 +332,7 @@ func (adapter *Adapter) builtInAdapter() bool {
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

@@ -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

@@ -214,30 +214,18 @@ func ConvertToTreeData(groups []*Group, parentId string) []*Group {
return treeData
}
func RemoveUserFromGroup(owner, name, groupId string) (bool, error) {
user, err := getUser(owner, name)
if err != nil {
return false, err
}
if user == nil {
return false, errors.New("user not exist")
}
user.Groups = util.DeleteVal(user.Groups, groupId)
affected, err := updateUser(user.GetId(), user, []string{"groups"})
if err != nil {
return false, err
}
return affected != 0, err
}
func GetGroupUserCount(groupId string, field, value string) (int64, error) {
owner, _ := util.GetOwnerAndNameFromId(groupId)
names, err := userEnforcer.GetUserNamesByGroupName(groupId)
if err != nil {
return 0, err
}
if field == "" && value == "" {
return ormer.Engine.Where(builder.Like{"`groups`", groupId}).
Count(&User{})
return int64(len(names)), nil
} else {
return ormer.Engine.Table("user").
Where(builder.Like{"`groups`", groupId}).
Where("owner = ?", owner).In("name", names).
And(fmt.Sprintf("user.%s LIKE ?", util.CamelToSnakeCase(field)), "%"+value+"%").
Count()
}
@@ -245,8 +233,14 @@ func GetGroupUserCount(groupId string, field, value string) (int64, 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`", groupId + "\""})
Where("owner = ?", owner).In("name", names)
if offset != -1 && limit != -1 {
session.Limit(limit, offset)
@@ -265,7 +259,7 @@ func GetPaginationGroupUsers(groupId string, offset, limit int, field, value, so
session = session.Desc(fmt.Sprintf("user.%s", util.SnakeString(sortField)))
}
err := session.Find(&users)
err = session.Find(&users)
if err != nil {
return nil, err
}
@@ -275,13 +269,13 @@ func GetPaginationGroupUsers(groupId string, offset, limit int, field, value, so
func GetGroupUsers(groupId string) ([]*User, error) {
users := []*User{}
err := ormer.Engine.Table("user").
Where(builder.Like{"`groups`", groupId + "\""}).
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

@@ -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

@@ -58,7 +58,7 @@ type PermissionRule struct {
Id string `xorm:"varchar(100) index not null default ''" json:"id"`
}
const builtInAvailableField = 10
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)

View File

@@ -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

@@ -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"`
@@ -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
@@ -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()

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

@@ -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

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

View File

@@ -644,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"))} :
@@ -678,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"))} :
@@ -690,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"))} :

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

@@ -177,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": {
@@ -901,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") {

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") {
@@ -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

@@ -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>
);