Files
cloudpods/pkg/keystone/models/groups.go
2023-09-13 17:32:29 +08:00

492 lines
14 KiB
Go

// Copyright 2019 Yunion
//
// 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 models
import (
"context"
"database/sql"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/rbacscope"
"yunion.io/x/sqlchemy"
api "yunion.io/x/onecloud/pkg/apis/identity"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
"yunion.io/x/onecloud/pkg/cloudcommon/db/quotas"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/util/stringutils2"
)
type SGroupManager struct {
SIdentityBaseResourceManager
}
var GroupManager *SGroupManager
func init() {
GroupManager = &SGroupManager{
SIdentityBaseResourceManager: NewIdentityBaseResourceManager(
SGroup{},
"group",
"group",
"groups",
),
}
GroupManager.SetVirtualObject(GroupManager)
}
/*
+-------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+-------------+------+-----+---------+-------+
| id | varchar(64) | NO | PRI | NULL | |
| domain_id | varchar(64) | NO | MUL | NULL | |
| name | varchar(64) | NO | | NULL | |
| description | text | YES | | NULL | |
| extra | text | YES | | NULL | |
| created_at | datetime | YES | | NULL | |
+-------------+-------------+------+-----+---------+-------+
*/
type SGroup struct {
SIdentityBaseResource
// 用户组的显示名称
Displayname string `with:"128" charset:"utf8" nullable:"true" list:"domain" update:"domain" create:"domain_optional"`
}
func (manager *SGroupManager) GetContextManagers() [][]db.IModelManager {
return [][]db.IModelManager{
{UserManager},
{ProjectManager},
}
}
// 用户组列表
func (manager *SGroupManager) ListItemFilter(
ctx context.Context,
q *sqlchemy.SQuery,
userCred mcclient.TokenCredential,
query api.GroupListInput,
) (*sqlchemy.SQuery, error) {
q, err := manager.SIdentityBaseResourceManager.ListItemFilter(ctx, q, userCred, query.IdentityBaseResourceListInput)
if err != nil {
return nil, err
}
if len(query.Displayname) > 0 {
q = q.Equals("displayname", query.Displayname)
}
userIdStr := query.UserId
if len(userIdStr) > 0 {
user, err := UserManager.FetchById(userIdStr)
if err != nil {
if err == sql.ErrNoRows {
return nil, httperrors.NewResourceNotFoundError2(UserManager.Keyword(), userIdStr)
} else {
return nil, httperrors.NewGeneralError(err)
}
}
subq := UsergroupManager.Query("group_id").Equals("user_id", user.GetId())
q = q.In("id", subq.SubQuery())
}
projIdStr := query.ProjectId
if len(projIdStr) > 0 {
proj, err := ProjectManager.FetchProjectById(projIdStr)
if err != nil {
if err == sql.ErrNoRows {
return nil, httperrors.NewResourceNotFoundError2(ProjectManager.Keyword(), projIdStr)
} else {
return nil, httperrors.NewGeneralError(err)
}
}
subq := AssignmentManager.fetchProjectGroupIdsQuery(proj.Id)
q = q.In("id", subq.SubQuery())
}
if len(query.IdpId) > 0 {
idpObj, err := IdentityProviderManager.FetchByIdOrName(userCred, query.IdpId)
if err != nil {
if errors.Cause(err) == sql.ErrNoRows {
return nil, errors.Wrapf(httperrors.ErrResourceNotFound, "%s %s", IdentityProviderManager.Keyword(), query.IdpId)
} else {
return nil, errors.Wrap(err, "IdentityProviderManager.FetchByIdOrName")
}
}
subq := IdmappingManager.FetchPublicIdsExcludesQuery(idpObj.GetId(), api.IdMappingEntityGroup, nil)
q = q.In("id", subq.SubQuery())
}
return q, nil
}
func (manager *SGroupManager) OrderByExtraFields(
ctx context.Context,
q *sqlchemy.SQuery,
userCred mcclient.TokenCredential,
query api.GroupListInput,
) (*sqlchemy.SQuery, error) {
var err error
q, err = manager.SIdentityBaseResourceManager.OrderByExtraFields(ctx, q, userCred, query.IdentityBaseResourceListInput)
if err != nil {
return nil, errors.Wrap(err, "SIdentityBaseResourceManager.OrderByExtraFields")
}
return q, nil
}
func (manager *SGroupManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
var err error
q, err = manager.SIdentityBaseResourceManager.QueryDistinctExtraField(q, field)
if err == nil {
return q, nil
}
return q, httperrors.ErrNotFound
}
func (group *SGroup) GetUserCount() (int, error) {
q := UsergroupManager.Query().Equals("group_id", group.Id)
return q.CountWithError()
}
func (group *SGroup) GetProjectCount() (int, error) {
q := AssignmentManager.fetchGroupProjectIdsQuery(group.Id)
return q.CountWithError()
}
func (group *SGroup) ValidateDeleteCondition(ctx context.Context, info jsonutils.JSONObject) error {
if group.IsReadOnly() {
return httperrors.NewForbiddenError("readonly")
}
return group.SIdentityBaseResource.ValidateDeleteCondition(ctx, nil)
}
func (group *SGroup) Delete(ctx context.Context, userCred mcclient.TokenCredential) error {
err := AssignmentManager.projectRemoveAllGroup(ctx, userCred, group)
if err != nil {
return errors.Wrap(err, "AssignmentManager.projectRemoveAllGroup")
}
err = UsergroupManager.delete("", group.Id)
if err != nil {
return errors.Wrap(err, "UsergroupManager.delete")
}
return group.SIdentityBaseResource.Delete(ctx, userCred)
}
func (manager *SGroupManager) FetchCustomizeColumns(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
objs []interface{},
fields stringutils2.SSortedStrings,
isList bool,
) []api.GroupDetails {
rows := make([]api.GroupDetails, len(objs))
identRows := manager.SIdentityBaseResourceManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
idList := make([]string, len(rows))
for i := range rows {
rows[i] = api.GroupDetails{
IdentityBaseResourceDetails: identRows[i],
}
group := objs[i].(*SGroup)
idList[i] = group.Id
rows[i].UserCount, _ = group.GetUserCount()
rows[i].ProjectCount, _ = group.GetProjectCount()
}
idpRows := expandIdpAttributes(api.IdMappingEntityGroup, idList, fields)
for i := range rows {
rows[i].IdpResourceInfo = idpRows[i]
}
return rows
}
func (manager *SGroupManager) RegisterExternalGroup(ctx context.Context, idpId string, domainId string, groupId string, groupName string) (*SGroup, error) {
lockman.LockClass(ctx, manager, idpId)
defer lockman.ReleaseClass(ctx, manager, idpId)
pubId, err := IdmappingManager.RegisterIdMap(ctx, idpId, groupId, api.IdMappingEntityGroup)
if err != nil {
return nil, errors.Wrap(err, "IdmappingManager.registerIdMap")
}
group := SGroup{}
group.SetModelManager(manager, &group)
q := manager.RawQuery().Equals("id", pubId)
err = q.First(&group)
if err != nil && err != sql.ErrNoRows {
return nil, errors.Wrap(err, "Query")
}
if err == sql.ErrNoRows {
group.Id = pubId
group.DomainId = domainId
group.Name = groupName
group.Displayname = groupName
err = manager.TableSpec().Insert(ctx, &group)
if err != nil {
return nil, errors.Wrap(err, "Insert")
}
} else {
_, err = db.Update(&group, func() error {
group.DomainId = domainId
group.Name = groupName
group.Displayname = groupName
group.MarkUnDelete()
return nil
})
if err != nil {
return nil, errors.Wrap(err, "update")
}
}
return &group, nil
}
func (group *SGroup) ValidateUpdateCondition(ctx context.Context) error {
// if group.IsReadOnly() {
// return httperrors.NewForbiddenError("readonly")
// }
return group.SIdentityBaseResource.ValidateUpdateCondition(ctx)
}
func (group *SGroup) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.GroupUpdateInput) (api.GroupUpdateInput, error) {
data := jsonutils.Marshal(input)
if group.IsReadOnly() {
for _, k := range []string{
"name",
"displayname",
} {
if data.Contains(k) {
return input, httperrors.NewForbiddenError("field %s is readonly", k)
}
}
}
var err error
input.IdentityBaseUpdateInput, err = group.SIdentityBaseResource.ValidateUpdateData(ctx, userCred, query, input.IdentityBaseUpdateInput)
if err != nil {
return input, errors.Wrap(err, "SIdentityBaseResource.ValidateUpdateData")
}
return input, nil
}
func (manager *SGroupManager) fetchGroupById(gid string) *SGroup {
obj, _ := GroupManager.FetchById(gid)
if obj != nil {
return obj.(*SGroup)
}
return nil
}
func (manager *SGroupManager) NamespaceScope() rbacscope.TRbacScope {
return rbacscope.ScopeDomain
}
func (group *SGroup) getIdmapping() (*SIdmapping, error) {
return IdmappingManager.FetchFirstEntity(group.Id, api.IdMappingEntityGroup)
}
func (group *SGroup) IsReadOnly() bool {
idmap, _ := group.getIdmapping()
if idmap != nil {
return true
}
return false
}
func (group *SGroup) LinkedWithIdp(idpId string) bool {
idmap, _ := group.getIdmapping()
if idmap != nil && idmap.IdpId == idpId {
return true
}
return false
}
func (manager *SGroupManager) FetchGroupsInDomain(domainId string, excludes []string) ([]SGroup, error) {
q := manager.Query().Equals("domain_id", domainId).NotIn("id", excludes)
grps := make([]SGroup, 0)
err := db.FetchModelObjects(manager, q, &grps)
if err != nil && err != sql.ErrNoRows {
return nil, errors.Wrap(err, "db.FetchModelObjects")
}
return grps, nil
}
func (group *SGroup) UnlinkIdp(idpId string) error {
return IdmappingManager.deleteAny(idpId, api.IdMappingEntityGroup, group.Id)
}
// 组加入项目
func (group *SGroup) PerformJoin(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
input api.SJoinProjectsInput,
) (jsonutils.JSONObject, error) {
err := joinProjects(group, false, ctx, userCred, input)
if err != nil {
return nil, errors.Wrap(err, "joinProjects")
}
return nil, nil
}
// 组退出项目
func (group *SGroup) PerformLeave(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
input api.SLeaveProjectsInput,
) (jsonutils.JSONObject, error) {
err := leaveProjects(group, false, ctx, userCred, input)
if err != nil {
return nil, err
}
return nil, nil
}
func (manager *SGroupManager) FilterByOwner(q *sqlchemy.SQuery, man db.FilterByOwnerProvider, userCred mcclient.TokenCredential, owner mcclient.IIdentityProvider, scope rbacscope.TRbacScope) *sqlchemy.SQuery {
if owner != nil && scope == rbacscope.ScopeProject {
// if user has project level privilege, returns all groups in user's project
subq := AssignmentManager.fetchProjectGroupIdsQuery(owner.GetProjectId())
q = q.In("id", subq.SubQuery())
return q
}
return manager.SIdentityBaseResourceManager.FilterByOwner(q, man, userCred, owner, scope)
}
func (group *SGroup) GetUsages() []db.IUsage {
if group.Deleted {
return nil
}
usage := SIdentityQuota{Group: 1}
usage.SetKeys(quotas.SBaseDomainQuotaKeys{DomainId: group.DomainId})
return []db.IUsage{
&usage,
}
}
func (manager *SGroupManager) ValidateCreateData(
ctx context.Context,
userCred mcclient.TokenCredential,
ownerId mcclient.IIdentityProvider,
query jsonutils.JSONObject,
input api.GroupCreateInput,
) (api.GroupCreateInput, error) {
var err error
input.IdentityBaseResourceCreateInput, err = manager.SIdentityBaseResourceManager.ValidateCreateData(ctx, userCred, ownerId, query, input.IdentityBaseResourceCreateInput)
if err != nil {
return input, errors.Wrap(err, "SIdentityBaseResourceManager.ValidateCreateData")
}
quota := &SIdentityQuota{
SBaseDomainQuotaKeys: quotas.SBaseDomainQuotaKeys{DomainId: ownerId.GetProjectDomainId()},
Group: 1,
}
err = quotas.CheckSetPendingQuota(ctx, userCred, quota)
if err != nil {
return input, errors.Wrap(err, "CheckSetPendingQuota")
}
return input, nil
}
func (group *SGroup) PostCreate(
ctx context.Context,
userCred mcclient.TokenCredential,
ownerId mcclient.IIdentityProvider,
query jsonutils.JSONObject,
data jsonutils.JSONObject,
) {
group.SIdentityBaseResource.PostCreate(ctx, userCred, ownerId, query, data)
quota := &SIdentityQuota{
SBaseDomainQuotaKeys: quotas.SBaseDomainQuotaKeys{DomainId: ownerId.GetProjectDomainId()},
Group: 1,
}
err := quotas.CancelPendingUsage(ctx, userCred, quota, quota, true)
if err != nil {
log.Errorf("CancelPendingUsage fail %s", err)
}
}
// 组添加用户
func (group *SGroup) PerformAddUsers(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
input api.PerformGroupAddUsersInput,
) (jsonutils.JSONObject, error) {
users := make([]*SUser, 0)
for _, uid := range input.UserIds {
usr, err := UserManager.FetchByIdOrName(userCred, uid)
if err != nil {
if errors.Cause(err) == sql.ErrNoRows {
return nil, errors.Wrapf(httperrors.ErrResourceNotFound, "user %s", uid)
} else {
return nil, errors.Wrap(err, "UserManager.FetchByIdOrName")
}
}
users = append(users, usr.(*SUser))
}
for i := range users {
err := UsergroupManager.add(ctx, userCred, users[i], group)
if err != nil {
return nil, errors.Wrap(err, "UsergroupManager.add")
}
}
return nil, nil
}
// 组删除用户
func (group *SGroup) PerformRemoveUsers(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
input api.PerformGroupRemoveUsersInput,
) (jsonutils.JSONObject, error) {
users := make([]*SUser, 0)
for _, uid := range input.UserIds {
usr, err := UserManager.FetchByIdOrName(userCred, uid)
if err != nil {
if errors.Cause(err) == sql.ErrNoRows {
return nil, errors.Wrapf(httperrors.ErrResourceNotFound, "user %s", uid)
} else {
return nil, errors.Wrap(err, "UserManager.FetchByIdOrName")
}
}
users = append(users, usr.(*SUser))
}
for i := range users {
err := UsergroupManager.remove(ctx, userCred, users[i], group)
if err != nil {
return nil, errors.Wrap(err, "UsergroupManager.add")
}
}
return nil, nil
}