feat: use the casbin model to store relationships between users and groups (#2178)

* fix:reslove conflict

* fix: remove interface
This commit is contained in:
Yaodong Yu 2023-08-11 10:59:18 +08:00 committed by GitHub
parent eafaa135b4
commit 1a9d02be46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 173 additions and 33 deletions

View File

@ -90,7 +90,7 @@ func (c *ApiController) GetUsers() {
if limit == "" || page == "" { if limit == "" || page == "" {
if groupName != "" { if groupName != "" {
maskedUsers, err := object.GetMaskedUsers(object.GetGroupUsers(groupName)) maskedUsers, err := object.GetMaskedUsers(object.GetGroupUsers(util.GetId(owner, groupName)))
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@ -567,6 +567,22 @@ func (c *ApiController) RemoveUserFromGroup() {
name := c.Ctx.Request.Form.Get("name") name := c.Ctx.Request.Form.Get("name")
groupName := c.Ctx.Request.Form.Get("groupName") groupName := c.Ctx.Request.Form.Get("groupName")
c.Data["json"] = wrapActionResponse(object.RemoveUserFromGroup(owner, name, util.GetId(owner, groupName))) organization, err := object.GetOrganization(util.GetId("admin", owner))
c.ServeJSON() 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)
} }

View File

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

View File

@ -23,6 +23,7 @@ import (
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
xormadapter "github.com/casdoor/xorm-adapter/v3" xormadapter "github.com/casdoor/xorm-adapter/v3"
"github.com/xorm-io/core" "github.com/xorm-io/core"
"github.com/xorm-io/xorm"
) )
type Adapter struct { type Adapter struct {
@ -155,14 +156,17 @@ func (adapter *Adapter) initAdapter() error {
if adapter.builtInAdapter() { if adapter.builtInAdapter() {
dataSourceName = conf.GetConfigString("dataSourceName") dataSourceName = conf.GetConfigString("dataSourceName")
if adapter.DatabaseType == "mysql" {
dataSourceName = dataSourceName + adapter.Database
}
} else { } else {
switch adapter.DatabaseType { switch adapter.DatabaseType {
case "mssql": case "mssql":
dataSourceName = fmt.Sprintf("sqlserver://%s:%s@%s:%d?database=%s", adapter.User, dataSourceName = fmt.Sprintf("sqlserver://%s:%s@%s:%d?database=%s", adapter.User,
adapter.Password, adapter.Host, adapter.Port, adapter.Database) adapter.Password, adapter.Host, adapter.Port, adapter.Database)
case "mysql": case "mysql":
dataSourceName = fmt.Sprintf("%s:%s@tcp(%s:%d)/", adapter.User, dataSourceName = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", adapter.User,
adapter.Password, adapter.Host, adapter.Port) adapter.Password, adapter.Host, adapter.Port, adapter.Database)
case "postgres": case "postgres":
dataSourceName = fmt.Sprintf("user=%s password=%s host=%s port=%d 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) adapter.Password, adapter.Host, adapter.Port, adapter.Database)
@ -181,7 +185,8 @@ func (adapter *Adapter) initAdapter() error {
} }
var err 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 { if err != nil {
return err return err
} }
@ -327,7 +332,7 @@ func (adapter *Adapter) builtInAdapter() bool {
return false 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 { func getModelDef() model.Model {

View File

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

View File

@ -29,6 +29,23 @@ const (
UserPropertiesWechatOpenId = "wechatOpenId" 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 { type User struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"` Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"` 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") 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) affected, err := updateUser(id, user, columns)
if err != nil { if err != nil {
return false, err return false, err
@ -778,6 +802,10 @@ func ExtendUserWithRolesAndPermissions(user *User) (err error) {
return return
} }
func DeleteGroupForUser(user string, group string) (bool, error) {
return userEnforcer.DeleteGroupForUser(user, group)
}
func userChangeTrigger(oldName string, newName string) error { func userChangeTrigger(oldName string, newName string) error {
session := ormer.Engine.NewSession() session := ormer.Engine.NewSession()
defer session.Close() 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

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