diff --git a/controllers/group.go b/controllers/group.go index b1f1136f..d89d6360 100644 --- a/controllers/group.go +++ b/controllers/group.go @@ -143,5 +143,6 @@ func (c *ApiController) DeleteGroup() { return } - c.ResponseOk(wrapActionResponse(object.DeleteGroup(&group))) + c.Data["json"] = wrapActionResponse(object.DeleteGroup(&group)) + c.ServeJSON() } diff --git a/controllers/organization.go b/controllers/organization.go index e2b13a1d..669308b0 100644 --- a/controllers/organization.go +++ b/controllers/organization.go @@ -47,21 +47,31 @@ func (c *ApiController) GetOrganizations() { c.Data["json"] = maskedOrganizations c.ServeJSON() } else { - limit := util.ParseInt(limit) - count, err := object.GetOrganizationCount(owner, field, value) - if err != nil { - c.ResponseError(err.Error()) - return - } + isGlobalAdmin := c.IsGlobalAdmin() + if !isGlobalAdmin { + maskedOrganizations, err := object.GetMaskedOrganizations(object.GetOrganizations(owner, c.getCurrentUser().Owner)) + if err != nil { + c.ResponseError(err.Error()) + return + } + c.ResponseOk(maskedOrganizations) + } else { + limit := util.ParseInt(limit) + count, err := object.GetOrganizationCount(owner, field, value) + if err != nil { + c.ResponseError(err.Error()) + return + } - paginator := pagination.SetPaginator(c.Ctx, limit, count) - organizations, err := object.GetMaskedOrganizations(object.GetPaginationOrganizations(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)) - if err != nil { - c.ResponseError(err.Error()) - return - } + paginator := pagination.SetPaginator(c.Ctx, limit, count) + organizations, err := object.GetMaskedOrganizations(object.GetPaginationOrganizations(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)) + if err != nil { + c.ResponseError(err.Error()) + return + } - c.ResponseOk(organizations, paginator.Nums()) + c.ResponseOk(organizations, paginator.Nums()) + } } } @@ -74,14 +84,13 @@ func (c *ApiController) GetOrganizations() { // @router /get-organization [get] func (c *ApiController) GetOrganization() { id := c.Input().Get("id") - maskedOrganization, err := object.GetMaskedOrganization(object.GetOrganization(id)) if err != nil { - panic(err) + c.ResponseError(err.Error()) + return } - c.Data["json"] = maskedOrganization - c.ServeJSON() + c.ResponseOk(maskedOrganization) } // UpdateOrganization ... diff --git a/controllers/user.go b/controllers/user.go index 77cbeaa2..cce3024a 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -90,7 +90,7 @@ func (c *ApiController) GetUsers() { if limit == "" || page == "" { if groupId != "" { - maskedUsers, err := object.GetMaskedUsers(object.GetUsersByGroup(groupId)) + maskedUsers, err := object.GetMaskedUsers(object.GetGroupUsers(groupId)) if err != nil { c.ResponseError(err.Error()) return @@ -550,3 +550,12 @@ func (c *ApiController) AddUserkeys() { c.ResponseOk(affected) } + +func (c *ApiController) RemoveUserFromGroup() { + owner := c.Ctx.Request.Form.Get("owner") + name := c.Ctx.Request.Form.Get("name") + groupId := c.Ctx.Request.Form.Get("groupId") + + c.Data["json"] = wrapActionResponse(object.RemoveUserFromGroup(owner, name, groupId)) + c.ServeJSON() +} diff --git a/go.mod b/go.mod index 83344e75..58811cf0 100644 --- a/go.mod +++ b/go.mod @@ -59,6 +59,7 @@ require ( github.com/tealeg/xlsx v1.0.5 github.com/thanhpk/randstr v1.0.4 github.com/tklauser/go-sysconf v0.3.10 // indirect + github.com/xorm-io/builder v0.3.13 // indirect github.com/xorm-io/core v0.7.4 github.com/xorm-io/xorm v1.1.6 github.com/yusufpapurcu/wmi v1.2.2 // indirect diff --git a/object/group.go b/object/group.go index f1589e7f..2ea08a53 100644 --- a/object/group.go +++ b/object/group.go @@ -15,6 +15,7 @@ package object import ( + "errors" "fmt" "github.com/casdoor/casdoor/util" @@ -27,14 +28,14 @@ type Group struct { CreatedTime string `xorm:"varchar(100)" json:"createdTime"` UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"` - Id string `xorm:"varchar(100) not null index" json:"id"` - DisplayName string `xorm:"varchar(100)" json:"displayName"` - Manager string `xorm:"varchar(100)" json:"manager"` - ContactEmail string `xorm:"varchar(100)" json:"contactEmail"` - Type string `xorm:"varchar(100)" json:"type"` - ParentGroupId string `xorm:"varchar(100)" json:"parentGroupId"` - IsTopGroup bool `xorm:"bool" json:"isTopGroup"` - Users *[]string `xorm:"-" json:"users"` + Id string `xorm:"varchar(100) not null index" json:"id"` + DisplayName string `xorm:"varchar(100)" json:"displayName"` + Manager string `xorm:"varchar(100)" json:"manager"` + ContactEmail string `xorm:"varchar(100)" json:"contactEmail"` + Type string `xorm:"varchar(100)" json:"type"` + ParentId string `xorm:"varchar(100)" json:"parentId"` + IsTopGroup bool `xorm:"bool" json:"isTopGroup"` + Users *[]string `xorm:"-" json:"users"` Title string `json:"title,omitempty"` Key string `json:"key,omitempty"` @@ -158,11 +159,45 @@ func AddGroups(groups []*Group) (bool, error) { } func DeleteGroup(group *Group) (bool, error) { - affected, err := adapter.Engine.ID(core.PK{group.Owner, group.Name}).Delete(&Group{}) + _, err := adapter.Engine.Get(group) if err != nil { return false, err } + if count, err := adapter.Engine.Where("parent_id = ?", group.Id).Count(&Group{}); err != nil { + return false, err + } else if count > 0 { + return false, errors.New("group has children group") + } + + if count, err := GetGroupUserCount(group.GetId(), "", ""); err != nil { + return false, err + } else if count > 0 { + return false, errors.New("group has users") + } + + session := adapter.Engine.NewSession() + defer session.Close() + + if err := session.Begin(); err != nil { + return false, err + } + + if _, err := session.Delete(&UserGroupRelation{GroupId: group.Id}); err != nil { + session.Rollback() + return false, err + } + + affected, err := session.ID(core.PK{group.Owner, group.Name}).Delete(&Group{}) + if err != nil { + session.Rollback() + return false, err + } + + if err := session.Commit(); err != nil { + return false, err + } + return affected != 0, nil } @@ -170,11 +205,11 @@ func (group *Group) GetId() string { return fmt.Sprintf("%s/%s", group.Owner, group.Name) } -func ConvertToTreeData(groups []*Group, parentGroupId string) []*Group { +func ConvertToTreeData(groups []*Group, parentId string) []*Group { treeData := []*Group{} for _, group := range groups { - if group.ParentGroupId == parentGroupId { + if group.ParentId == parentId { node := &Group{ Title: group.DisplayName, Key: group.Name, diff --git a/object/organization.go b/object/organization.go index 78b2a662..ec7f791a 100644 --- a/object/organization.go +++ b/object/organization.go @@ -22,6 +22,7 @@ import ( "github.com/casdoor/casdoor/cred" "github.com/casdoor/casdoor/i18n" "github.com/casdoor/casdoor/util" + "github.com/xorm-io/builder" "github.com/xorm-io/core" ) @@ -75,11 +76,18 @@ func GetOrganizationCount(owner, field, value string) (int64, error) { return session.Count(&Organization{}) } -func GetOrganizations(owner string) ([]*Organization, error) { +func GetOrganizations(owner string, name ...string) ([]*Organization, error) { organizations := []*Organization{} - err := adapter.Engine.Desc("created_time").Find(&organizations, &Organization{Owner: owner}) - if err != nil { - return nil, err + if name != nil && len(name) > 0 { + err := adapter.Engine.Desc("created_time").Where(builder.In("name", name)).Find(&organizations) + if err != nil { + return nil, err + } + } else { + err := adapter.Engine.Desc("created_time").Find(&organizations, &Organization{Owner: owner}) + if err != nil { + return nil, err + } } return organizations, nil diff --git a/object/user.go b/object/user.go index 07357972..ed7a8c7b 100644 --- a/object/user.go +++ b/object/user.go @@ -225,14 +225,7 @@ func GetUserCount(owner, field, value string, groupId string) (int64, error) { session := GetSession(owner, -1, -1, field, value, "", "") if groupId != "" { - group, err := GetGroup(groupId) - if group == nil || err != nil { - return 0, err - } - // users count in group - return adapter.Engine.Table("user_group_relation").Join("INNER", "user AS u", "user_group_relation.user_id = u.id"). - Where("user_group_relation.group_id = ?", group.Id). - Count(&UserGroupRelation{}) + return GetGroupUserCount(groupId, field, value) } return session.Count(&User{}) @@ -276,20 +269,7 @@ func GetPaginationUsers(owner string, offset, limit int, field, value, sortField users := []*User{} if groupId != "" { - group, err := GetGroup(groupId) - if group == nil || err != nil { - return []*User{}, err - } - - session := adapter.Engine.Prepare() - if offset != -1 && limit != -1 { - session.Limit(limit, offset) - } - - err = session.Table("user_group_relation").Join("INNER", "user AS u", "user_group_relation.user_id = u.id"). - Where("user_group_relation.group_id = ?", group.Id). - Find(&users) - return users, err + return GetPaginationGroupUsers(groupId, offset, limit, field, value, sortField, sortOrder) } session := GetSessionForUser(owner, offset, limit, field, value, sortField, sortOrder) @@ -300,23 +280,6 @@ func GetPaginationUsers(owner string, offset, limit int, field, value, sortField return users, nil } -func GetUsersByGroup(groupId string) ([]*User, error) { - group, err := GetGroup(groupId) - if group == nil || err != nil { - return []*User{}, err - } - - users := []*User{} - err = adapter.Engine.Table("user_group_relation").Join("INNER", "user AS u", "user_group_relation.user_id = u.id"). - Where("user_group_relation.group_id = ?", group.Id). - Find(&users) - if err != nil { - return nil, err - } - - return users, nil -} - func getUser(owner string, name string) (*User, error) { if owner == "" || name == "" { return nil, nil @@ -574,7 +537,7 @@ func updateUser(oldUser, user *User, columns []string) (int64, error) { session.Begin() if util.ContainsString(columns, "groups") { - affected, err := updateGroupRelation(session, user) + affected, err := updateUserGroupRelation(session, user) if err != nil { session.Rollback() return affected, err @@ -763,6 +726,11 @@ func DeleteUser(user *User) (bool, error) { return false, err } + affected, err = deleteRelationByUser(user.Id) + if err != nil { + return false, err + } + return affected != 0, nil } diff --git a/object/user_group.go b/object/user_group.go index 570bab43..8a61c994 100644 --- a/object/user_group.go +++ b/object/user_group.go @@ -2,7 +2,10 @@ package object import ( "errors" + "fmt" + "github.com/casdoor/casdoor/util" + "github.com/xorm-io/core" "github.com/xorm-io/xorm" ) @@ -14,9 +17,7 @@ type UserGroupRelation struct { UpdatedTime string `xorm:"updated" json:"updatedTime"` } -func updateGroupRelation(session *xorm.Session, user *User) (int64, error) { - groupIds := user.Groups - +func updateUserGroupRelation(session *xorm.Session, user *User) (int64, error) { physicalGroupCount, err := session.Where("type = ?", "Physical").In("id", user.Groups).Count(Group{}) if err != nil { return 0, err @@ -26,12 +27,12 @@ func updateGroupRelation(session *xorm.Session, user *User) (int64, error) { } groups := []*Group{} - err = session.In("id", groupIds).Find(&groups) + err = session.In("id", user.Groups).Find(&groups) if err != nil { return 0, err } - if len(groups) == 0 || len(groups) != len(groupIds) { - return 0, nil + if len(groups) != len(user.Groups) { + return 0, errors.New("group not found") } _, err = session.Delete(&UserGroupRelation{UserId: user.Id}) @@ -43,6 +44,9 @@ func updateGroupRelation(session *xorm.Session, user *User) (int64, error) { for _, group := range groups { relations = append(relations, &UserGroupRelation{UserId: user.Id, GroupId: group.Id}) } + if len(relations) == 0 { + return 1, nil + } _, err = session.Insert(relations) if err != nil { return 0, err @@ -50,3 +54,104 @@ func updateGroupRelation(session *xorm.Session, user *User) (int64, error) { return 1, nil } + +func RemoveUserFromGroup(owner, name, groupId string) (bool, error) { + user, err := getUser(owner, name) + if err != nil { + return false, err + } + + groups := []string{} + for _, group := range user.Groups { + if group != groupId { + groups = append(groups, group) + } + } + user.Groups = groups + + _, err = UpdateUser(util.GetId(owner, name), user, []string{"groups"}, false) + if err != nil { + return false, err + } + return true, nil +} + +func deleteUserGroupRelation(session *xorm.Session, userId, groupId string) (int64, error) { + affected, err := session.ID(core.PK{userId, groupId}).Delete(&UserGroupRelation{}) + return affected, err +} + +func deleteRelationByUser(id string) (int64, error) { + affected, err := adapter.Engine.Delete(&UserGroupRelation{UserId: id}) + return affected, err +} + +func GetGroupUserCount(id string, field, value string) (int64, error) { + group, err := GetGroup(id) + if group == nil || err != nil { + return 0, err + } + + if field == "" && value == "" { + return adapter.Engine.Count(UserGroupRelation{GroupId: group.Id}) + } else { + return adapter.Engine.Table("user"). + Join("INNER", []string{"user_group_relation", "r"}, "user.id = r.user_id"). + Where("r.group_id = ?", group.Id). + And(fmt.Sprintf("user.%s LIKE ?", util.CamelToSnakeCase(field)), "%"+value+"%"). + Count() + } +} + +func GetPaginationGroupUsers(id string, offset, limit int, field, value, sortField, sortOrder string) ([]*User, error) { + group, err := GetGroup(id) + if group == nil || err != nil { + return nil, err + } + + users := []*User{} + session := adapter.Engine.Table("user"). + Join("INNER", []string{"user_group_relation", "r"}, "user.id = r.user_id"). + Where("r.group_id = ?", group.Id) + + if offset != -1 && limit != -1 { + session.Limit(limit, offset) + } + + if field != "" && value != "" { + session = session.And(fmt.Sprintf("user.%s LIKE ?", util.CamelToSnakeCase(field)), "%"+value+"%") + } + + if sortField == "" || sortOrder == "" { + sortField = "created_time" + } + if sortOrder == "ascend" { + session = session.Asc(fmt.Sprintf("user.%s", util.SnakeString(sortField))) + } else { + session = session.Desc(fmt.Sprintf("user.%s", util.SnakeString(sortField))) + } + + err = session.Find(&users) + if err != nil { + return nil, err + } + + return users, nil +} + +func GetGroupUsers(id string) ([]*User, error) { + group, err := GetGroup(id) + if group == nil || err != nil { + return []*User{}, err + } + + users := []*User{} + err = adapter.Engine.Table("user_group_relation").Join("INNER", []string{"user", "u"}, "user_group_relation.user_id = u.id"). + Where("user_group_relation.group_id = ?", group.Id). + Find(&users) + if err != nil { + return nil, err + } + + return users, nil +} diff --git a/routers/router.go b/routers/router.go index ca661b0c..5b24cc34 100644 --- a/routers/router.go +++ b/routers/router.go @@ -77,6 +77,7 @@ func initAPI() { beego.Router("/api/add-user", &controllers.ApiController{}, "POST:AddUser") beego.Router("/api/delete-user", &controllers.ApiController{}, "POST:DeleteUser") beego.Router("/api/upload-users", &controllers.ApiController{}, "POST:UploadUsers") + beego.Router("/api/remove-user-from-group", &controllers.ApiController{}, "POST:RemoveUserFromGroup") beego.Router("/api/get-groups", &controllers.ApiController{}, "GET:GetGroups") beego.Router("/api/get-group", &controllers.ApiController{}, "GET:GetGroup") diff --git a/web/src/App.js b/web/src/App.js index 982036ff..55ed9850 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -131,7 +131,7 @@ class App extends Component { }); if (uri === "/") { this.setState({selectedMenuKey: "/"}); - } else if (uri.includes("/organizations")) { + } else if (uri.includes("/organizations") || uri.includes("/trees")) { this.setState({selectedMenuKey: "/organizations"}); } else if (uri.includes("/users")) { this.setState({selectedMenuKey: "/users"}); @@ -410,15 +410,13 @@ class App extends Component { res.push(Setting.getItem({i18next.t("general:Home")}, "/")); - if (Setting.isAdminUser(this.state.account)) { + if (Setting.isLocalAdminUser(this.state.account)) { res.push(Setting.getItem({i18next.t("general:Organizations")}, "/organizations")); res.push(Setting.getItem({i18next.t("general:Groups")}, "/groups")); - } - if (Setting.isLocalAdminUser(this.state.account)) { res.push(Setting.getItem({i18next.t("general:Users")}, "/users" )); @@ -560,8 +558,8 @@ class App extends Component { this.renderLoginIfNotLoggedIn()} /> this.renderLoginIfNotLoggedIn()} /> this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> this.renderLoginIfNotLoggedIn()} /> this.renderLoginIfNotLoggedIn()} /> this.renderLoginIfNotLoggedIn()} /> @@ -632,7 +630,7 @@ class App extends Component { isWithoutCard() { return Setting.isMobile() || window.location.pathname === "/chat" || - window.location.pathname.startsWith("/group-tree"); + window.location.pathname.startsWith("/trees"); } renderContent() { diff --git a/web/src/GroupEdit.js b/web/src/GroupEdit.js index fe910e9f..36566121 100644 --- a/web/src/GroupEdit.js +++ b/web/src/GroupEdit.js @@ -44,6 +44,11 @@ class GroupEditPage extends React.Component { GroupBackend.getGroup(this.state.organizationName, this.state.groupName) .then((res) => { if (res.status === "ok") { + if (res.data === null) { + this.props.history.push("/404"); + return; + } + this.setState({ group: res.data, }); @@ -171,8 +176,8 @@ class GroupEditPage extends React.Component {