From cca92874ab7b221218151c5bf789ca4c3500c721 Mon Sep 17 00:00:00 2001 From: Livio Amstutz Date: Fri, 4 Sep 2020 15:20:39 +0200 Subject: [PATCH] fix: permissions (#698) * get my zitadel permissions from memberships (not grants) * change SearchMyProjectOrgs to user user_membership --- .../eventsourcing/eventstore/user_grant.go | 112 +++++++-- .../eventsourcing/handler/handler.go | 1 + .../eventsourcing/handler/user_grant.go | 14 +- .../eventsourcing/handler/user_membership.go | 216 ++++++++++++++++++ .../eventsourcing/view/user_membership.go | 68 ++++++ internal/user/model/user_membership_view.go | 13 +- .../repository/view/model/user_membership.go | 64 +++--- .../repository/view/usermembership_view.go | 10 + .../cockroach/V1.11__usermembership.sql | 18 ++ 9 files changed, 454 insertions(+), 62 deletions(-) create mode 100644 internal/auth/repository/eventsourcing/handler/user_membership.go create mode 100644 internal/auth/repository/eventsourcing/view/user_membership.go create mode 100644 migrations/cockroach/V1.11__usermembership.sql diff --git a/internal/auth/repository/eventsourcing/eventstore/user_grant.go b/internal/auth/repository/eventsourcing/eventstore/user_grant.go index f28845db0d..d0b5eb66b1 100644 --- a/internal/auth/repository/eventsourcing/eventstore/user_grant.go +++ b/internal/auth/repository/eventsourcing/eventstore/user_grant.go @@ -2,6 +2,7 @@ package eventstore import ( "context" + "github.com/caos/logging" "github.com/caos/zitadel/internal/api/authz" @@ -11,6 +12,8 @@ import ( global_model "github.com/caos/zitadel/internal/model" org_model "github.com/caos/zitadel/internal/org/model" org_view_model "github.com/caos/zitadel/internal/org/repository/view/model" + user_model "github.com/caos/zitadel/internal/user/model" + user_view_model "github.com/caos/zitadel/internal/user/repository/view/model" grant_model "github.com/caos/zitadel/internal/usergrant/model" "github.com/caos/zitadel/internal/usergrant/repository/view/model" ) @@ -63,6 +66,7 @@ func (repo *UserGrantRepo) SearchMyProjectOrgs(ctx context.Context, request *gra if isAdmin { return repo.SearchAdminOrgs(request) } + return repo.searchZitadelOrgs(ctxData, request) } request.Queries = append(request.Queries, &grant_model.UserGrantSearchQuery{Key: grant_model.UserGrantSearchKeyProjectID, Method: global_model.SearchMethodEquals, Value: ctxData.ProjectID}) @@ -73,35 +77,48 @@ func (repo *UserGrantRepo) SearchMyProjectOrgs(ctx context.Context, request *gra if len(grants.Result) > 0 { return grantRespToOrgResp(grants), nil } - user, err := repo.View.UserByID(ctxData.UserID) - if err != nil { - return nil, err + return repo.userOrg(ctxData) +} + +func membershipsToOrgResp(memberships []*user_view_model.UserMembershipView, count uint64) *grant_model.ProjectOrgSearchResponse { + orgs := make([]*grant_model.Org, 0, len(memberships)) + for _, m := range memberships { + if !containsOrg(orgs, m.ResourceOwner) { + orgs = append(orgs, &grant_model.Org{OrgID: m.ResourceOwner, OrgName: m.ResourceOwnerName}) + } } - org, err := repo.View.OrgByID(user.ResourceOwner) - if err != nil { - return nil, err + return &grant_model.ProjectOrgSearchResponse{ + TotalResult: count, + Result: orgs, } - return &grant_model.ProjectOrgSearchResponse{Result: []*grant_model.Org{&grant_model.Org{ - OrgID: org.ID, - OrgName: org.Name, - }}}, nil } func (repo *UserGrantRepo) SearchMyZitadelPermissions(ctx context.Context) ([]string, error) { - grant, err := repo.AuthZRepo.ResolveGrants(ctx) + ctxData := authz.GetCtxData(ctx) + memberships, count, err := repo.View.SearchUserMemberships(&user_model.UserMembershipSearchRequest{ + Queries: []*user_model.UserMembershipSearchQuery{ + { + Key: user_model.UserMembershipSearchKeyUserID, + Method: global_model.SearchMethodEquals, + Value: ctxData.UserID, + }, + { + Key: user_model.UserMembershipSearchKeyResourceOwner, + Method: global_model.SearchMethodEquals, + Value: ctxData.OrgID, + }, + }, + }) if err != nil { return nil, err } - if grant == nil { + if count == 0 { return []string{}, nil } permissions := &grant_model.Permissions{Permissions: []string{}} - for _, role := range grant.Roles { - roleName, ctxID := authz.SplitPermission(role) - for _, mapping := range repo.Auth.RolePermissionMappings { - if mapping.Role == roleName { - permissions.AppendPermissions(ctxID, mapping.Permissions...) - } + for _, membership := range memberships { + for _, role := range membership.Roles { + permissions = repo.mapRoleToPermission(permissions, membership, role) } } return permissions.Permissions, nil @@ -159,6 +176,56 @@ func (repo *UserGrantRepo) UserGrantsByProjectAndUserID(projectID, userID string return model.UserGrantsToModel(grants), nil } +func (repo *UserGrantRepo) userOrg(ctxData authz.CtxData) (*grant_model.ProjectOrgSearchResponse, error) { + user, err := repo.View.UserByID(ctxData.UserID) + if err != nil { + return nil, err + } + org, err := repo.View.OrgByID(user.ResourceOwner) + if err != nil { + return nil, err + } + return &grant_model.ProjectOrgSearchResponse{Result: []*grant_model.Org{&grant_model.Org{ + OrgID: org.ID, + OrgName: org.Name, + }}}, nil +} + +func (repo *UserGrantRepo) searchZitadelOrgs(ctxData authz.CtxData, request *grant_model.UserGrantSearchRequest) (*grant_model.ProjectOrgSearchResponse, error) { + memberships, count, err := repo.View.SearchUserMemberships(&user_model.UserMembershipSearchRequest{ + Offset: request.Offset, + Limit: request.Limit, + Asc: request.Asc, + Queries: []*user_model.UserMembershipSearchQuery{ + { + Key: user_model.UserMembershipSearchKeyUserID, + Method: global_model.SearchMethodEquals, + Value: ctxData.UserID, + }, + }, + }) + if err != nil { + return nil, err + } + if len(memberships) > 0 { + return membershipsToOrgResp(memberships, count), nil + } + return repo.userOrg(ctxData) +} + +func (repo *UserGrantRepo) mapRoleToPermission(permissions *grant_model.Permissions, membership *user_view_model.UserMembershipView, role string) *grant_model.Permissions { + for _, mapping := range repo.Auth.RolePermissionMappings { + if mapping.Role == role { + ctxID := "" + if membership.MemberType == int32(user_model.MemberTypeProject) || membership.MemberType == int32(user_model.MemberTypeProjectGrant) { + ctxID = membership.ObjectID + } + permissions.AppendPermissions(ctxID, mapping.Permissions...) + } + } + return permissions +} + func grantRespToOrgResp(grants *grant_model.UserGrantSearchResponse) *grant_model.ProjectOrgSearchResponse { resp := &grant_model.ProjectOrgSearchResponse{ TotalResult: grants.TotalResult, @@ -207,3 +274,12 @@ func addIamAdminRoles(orgRoles, iamAdminRoles []string) []string { } return result } + +func containsOrg(orgs []*grant_model.Org, resourceOwner string) bool { + for _, org := range orgs { + if org.OrgID == resourceOwner { + return true + } + } + return false +} diff --git a/internal/auth/repository/eventsourcing/handler/handler.go b/internal/auth/repository/eventsourcing/handler/handler.go index 2fd4a45d58..dc0ba02aa7 100644 --- a/internal/auth/repository/eventsourcing/handler/handler.go +++ b/internal/auth/repository/eventsourcing/handler/handler.go @@ -39,6 +39,7 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, ev return []query.Handler{ &User{handler: handler{view, bulkLimit, configs.cycleDuration("User"), errorCount}, orgEvents: repos.OrgEvents}, &UserSession{handler: handler{view, bulkLimit, configs.cycleDuration("UserSession"), errorCount}, userEvents: repos.UserEvents}, + &UserMembership{handler: handler{view, bulkLimit, configs.cycleDuration("UserMembership"), errorCount}, orgEvents: repos.OrgEvents, projectEvents: repos.ProjectEvents}, &Token{handler: handler{view, bulkLimit, configs.cycleDuration("Token"), errorCount}, ProjectEvents: repos.ProjectEvents}, &Key{handler: handler{view, bulkLimit, configs.cycleDuration("Key"), errorCount}}, &Application{handler: handler{view, bulkLimit, configs.cycleDuration("Application"), errorCount}}, diff --git a/internal/auth/repository/eventsourcing/handler/user_grant.go b/internal/auth/repository/eventsourcing/handler/user_grant.go index 96b2dba5d0..fa7cb6719c 100644 --- a/internal/auth/repository/eventsourcing/handler/user_grant.go +++ b/internal/auth/repository/eventsourcing/handler/user_grant.go @@ -260,10 +260,20 @@ func (u *UserGrant) processMember(event *models.Event, rolePrefix, roleSuffix st proj_es_model.ProjectGrantMemberRemoved: grant, err := u.view.UserGrantByIDs(event.ResourceOwner, u.iamProjectID, userID) - if err != nil { + if err != nil && !errors.IsNotFound(err) { return err } - return u.view.DeleteUserGrant(grant.ID, event.Sequence) + if errors.IsNotFound(err) { + return u.view.ProcessedUserGrantSequence(event.Sequence) + } + if roleSuffix != "" { + roleKeys = suffixRoles(roleSuffix, roleKeys) + } + if grant.RoleKeys == nil { + return u.view.ProcessedUserGrantSequence(event.Sequence) + } + grant.RoleKeys = mergeExistingRoles(rolePrefix, roleSuffix, grant.RoleKeys, nil) + return u.view.PutUserGrant(grant, event.Sequence) default: return u.view.ProcessedUserGrantSequence(event.Sequence) } diff --git a/internal/auth/repository/eventsourcing/handler/user_membership.go b/internal/auth/repository/eventsourcing/handler/user_membership.go new file mode 100644 index 0000000000..3796778845 --- /dev/null +++ b/internal/auth/repository/eventsourcing/handler/user_membership.go @@ -0,0 +1,216 @@ +package handler + +import ( + "context" + + iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" + org_model "github.com/caos/zitadel/internal/org/model" + org_event "github.com/caos/zitadel/internal/org/repository/eventsourcing" + proj_event "github.com/caos/zitadel/internal/project/repository/eventsourcing" + proj_es_model "github.com/caos/zitadel/internal/project/repository/eventsourcing/model" + + "github.com/caos/logging" + + "github.com/caos/zitadel/internal/eventstore/models" + es_models "github.com/caos/zitadel/internal/eventstore/models" + "github.com/caos/zitadel/internal/eventstore/spooler" + org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model" + usr_model "github.com/caos/zitadel/internal/user/model" + usr_es_model "github.com/caos/zitadel/internal/user/repository/view/model" +) + +type UserMembership struct { + handler + orgEvents *org_event.OrgEventstore + projectEvents *proj_event.ProjectEventstore +} + +const ( + userMembershipTable = "auth.user_memberships" +) + +func (m *UserMembership) ViewModel() string { + return userMembershipTable +} + +func (m *UserMembership) EventQuery() (*models.SearchQuery, error) { + sequence, err := m.view.GetLatestUserMembershipSequence() + if err != nil { + return nil, err + } + return es_models.NewSearchQuery(). + AggregateTypeFilter(iam_es_model.IAMAggregate, org_es_model.OrgAggregate, proj_es_model.ProjectAggregate). + LatestSequenceFilter(sequence.CurrentSequence), nil +} + +func (m *UserMembership) Reduce(event *models.Event) (err error) { + switch event.AggregateType { + case iam_es_model.IAMAggregate: + err = m.processIam(event) + case org_es_model.OrgAggregate: + err = m.processOrg(event) + case proj_es_model.ProjectAggregate: + err = m.processProject(event) + } + return err +} + +func (m *UserMembership) processIam(event *models.Event) (err error) { + member := new(usr_es_model.UserMembershipView) + err = member.AppendEvent(event) + if err != nil { + return err + } + switch event.Type { + case iam_es_model.IAMMemberAdded: + m.fillIamDisplayName(member) + case iam_es_model.IAMMemberChanged: + member, err = m.view.UserMembershipByIDs(member.UserID, event.AggregateID, event.AggregateID, usr_model.MemberTypeIam) + if err != nil { + return err + } + err = member.AppendEvent(event) + case iam_es_model.IAMMemberRemoved: + return m.view.DeleteUserMembership(member.UserID, event.AggregateID, event.AggregateID, usr_model.MemberTypeIam, event.Sequence) + default: + return m.view.ProcessedUserMembershipSequence(event.Sequence) + } + if err != nil { + return err + } + return m.view.PutUserMembership(member, event.Sequence) +} + +func (m *UserMembership) fillIamDisplayName(member *usr_es_model.UserMembershipView) { + member.DisplayName = member.AggregateID + member.ResourceOwnerName = member.ResourceOwner +} + +func (m *UserMembership) processOrg(event *models.Event) (err error) { + member := new(usr_es_model.UserMembershipView) + err = member.AppendEvent(event) + if err != nil { + return err + } + switch event.Type { + case org_es_model.OrgMemberAdded: + err = m.fillOrgName(member) + case org_es_model.OrgMemberChanged: + member, err = m.view.UserMembershipByIDs(member.UserID, event.AggregateID, event.AggregateID, usr_model.MemberTypeOrganisation) + if err != nil { + return err + } + err = member.AppendEvent(event) + case org_es_model.OrgMemberRemoved: + return m.view.DeleteUserMembership(member.UserID, event.AggregateID, event.AggregateID, usr_model.MemberTypeOrganisation, event.Sequence) + case org_es_model.OrgChanged: + err = m.updateOrgName(event) + default: + return m.view.ProcessedUserMembershipSequence(event.Sequence) + } + if err != nil { + return err + } + return m.view.PutUserMembership(member, event.Sequence) +} + +func (m *UserMembership) fillOrgName(member *usr_es_model.UserMembershipView) (err error) { + org, err := m.orgEvents.OrgByID(context.Background(), org_model.NewOrg(member.ResourceOwner)) + if err != nil { + return err + } + member.ResourceOwnerName = org.Name + if member.AggregateID == org.AggregateID { + member.DisplayName = org.Name + } + return nil +} + +func (m *UserMembership) updateOrgName(event *models.Event) error { + org, err := m.orgEvents.OrgByID(context.Background(), org_model.NewOrg(event.AggregateID)) + if err != nil { + return err + } + + memberships, err := m.view.UserMembershipsByResourceOwner(event.ResourceOwner) + if err != nil { + return err + } + for _, membership := range memberships { + membership.ResourceOwnerName = org.Name + if membership.AggregateID == event.AggregateID { + membership.DisplayName = org.Name + } + } + return m.view.BulkPutUserMemberships(memberships, event.Sequence) +} + +func (m *UserMembership) processProject(event *models.Event) (err error) { + member := new(usr_es_model.UserMembershipView) + err = member.AppendEvent(event) + if err != nil { + return err + } + switch event.Type { + case proj_es_model.ProjectMemberAdded, proj_es_model.ProjectGrantMemberAdded: + err = m.fillProjectDisplayName(member) + if err != nil { + return err + } + err = m.fillOrgName(member) + case proj_es_model.ProjectMemberChanged: + member, err = m.view.UserMembershipByIDs(member.UserID, event.AggregateID, event.AggregateID, usr_model.MemberTypeProject) + if err != nil { + return err + } + err = member.AppendEvent(event) + case proj_es_model.ProjectMemberRemoved: + return m.view.DeleteUserMembership(member.UserID, event.AggregateID, event.AggregateID, usr_model.MemberTypeProject, event.Sequence) + case proj_es_model.ProjectGrantMemberChanged: + member, err = m.view.UserMembershipByIDs(member.UserID, event.AggregateID, member.ObjectID, usr_model.MemberTypeProjectGrant) + if err != nil { + return err + } + err = member.AppendEvent(event) + case proj_es_model.ProjectGrantMemberRemoved: + return m.view.DeleteUserMembership(member.UserID, event.AggregateID, member.ObjectID, usr_model.MemberTypeProjectGrant, event.Sequence) + case proj_es_model.ProjectChanged: + err = m.updateProjectDisplayName(event) + default: + return m.view.ProcessedUserMembershipSequence(event.Sequence) + } + if err != nil { + return err + } + return m.view.PutUserMembership(member, event.Sequence) +} + +func (m *UserMembership) fillProjectDisplayName(member *usr_es_model.UserMembershipView) (err error) { + project, err := m.projectEvents.ProjectByID(context.Background(), member.AggregateID) + if err != nil { + return err + } + member.DisplayName = project.Name + return nil +} + +func (m *UserMembership) updateProjectDisplayName(event *models.Event) error { + project, err := m.projectEvents.ProjectByID(context.Background(), event.AggregateID) + if err != nil { + return err + } + + memberships, err := m.view.UserMembershipsByAggregateID(event.AggregateID) + if err != nil { + return err + } + for _, membership := range memberships { + membership.DisplayName = project.Name + } + return m.view.BulkPutUserMemberships(memberships, event.Sequence) +} + +func (m *UserMembership) OnError(event *models.Event, err error) error { + logging.LogWithFields("SPOOL-Ms3fj", "id", event.AggregateID).WithError(err).Warn("something went wrong in orgmember handler") + return spooler.HandleError(event, err, m.view.GetLatestUserMembershipFailedEvent, m.view.ProcessedUserMembershipFailedEvent, m.view.ProcessedUserMembershipSequence, m.errorCountUntilSkip) +} diff --git a/internal/auth/repository/eventsourcing/view/user_membership.go b/internal/auth/repository/eventsourcing/view/user_membership.go new file mode 100644 index 0000000000..30ce719b06 --- /dev/null +++ b/internal/auth/repository/eventsourcing/view/user_membership.go @@ -0,0 +1,68 @@ +package view + +import ( + usr_model "github.com/caos/zitadel/internal/user/model" + "github.com/caos/zitadel/internal/user/repository/view" + "github.com/caos/zitadel/internal/user/repository/view/model" + "github.com/caos/zitadel/internal/view/repository" +) + +const ( + userMembershipTable = "auth.user_memberships" +) + +func (v *View) UserMembershipByIDs(userID, aggregateID, objectID string, memberType usr_model.MemberType) (*model.UserMembershipView, error) { + return view.UserMembershipByIDs(v.Db, userMembershipTable, userID, aggregateID, objectID, memberType) +} + +func (v *View) UserMembershipsByAggregateID(aggregateID string) ([]*model.UserMembershipView, error) { + return view.UserMembershipsByAggregateID(v.Db, userMembershipTable, aggregateID) +} + +func (v *View) UserMembershipsByResourceOwner(resourceOwner string) ([]*model.UserMembershipView, error) { + return view.UserMembershipsByResourceOwner(v.Db, userMembershipTable, resourceOwner) +} + +func (v *View) SearchUserMemberships(request *usr_model.UserMembershipSearchRequest) ([]*model.UserMembershipView, uint64, error) { + return view.SearchUserMemberships(v.Db, userMembershipTable, request) +} + +func (v *View) PutUserMembership(membership *model.UserMembershipView, sequence uint64) error { + err := view.PutUserMembership(v.Db, userMembershipTable, membership) + if err != nil { + return err + } + return v.ProcessedUserMembershipSequence(sequence) +} + +func (v *View) BulkPutUserMemberships(memberships []*model.UserMembershipView, sequence uint64) error { + err := view.PutUserMemberships(v.Db, userTable, memberships...) + if err != nil { + return err + } + return v.ProcessedUserMembershipSequence(sequence) +} + +func (v *View) DeleteUserMembership(userID, aggregateID, objectID string, memberType usr_model.MemberType, eventSequence uint64) error { + err := view.DeleteUserMembership(v.Db, userMembershipTable, userID, aggregateID, objectID, memberType) + if err != nil { + return nil + } + return v.ProcessedUserMembershipSequence(eventSequence) +} + +func (v *View) GetLatestUserMembershipSequence() (*repository.CurrentSequence, error) { + return v.latestSequence(userMembershipTable) +} + +func (v *View) ProcessedUserMembershipSequence(eventSequence uint64) error { + return v.saveCurrentSequence(userMembershipTable, eventSequence) +} + +func (v *View) GetLatestUserMembershipFailedEvent(sequence uint64) (*repository.FailedEvent, error) { + return v.latestFailedEvent(userMembershipTable, sequence) +} + +func (v *View) ProcessedUserMembershipFailedEvent(failedEvent *repository.FailedEvent) error { + return v.saveFailedEvent(failedEvent) +} diff --git a/internal/user/model/user_membership_view.go b/internal/user/model/user_membership_view.go index 1c86ed8928..bad5901ae8 100644 --- a/internal/user/model/user_membership_view.go +++ b/internal/user/model/user_membership_view.go @@ -13,12 +13,13 @@ type UserMembershipView struct { //ObjectID differs from aggregate id if obejct is sub of an aggregate ObjectID string - Roles []string - DisplayName string - CreationDate time.Time - ChangeDate time.Time - ResourceOwner string - Sequence uint64 + Roles []string + DisplayName string + CreationDate time.Time + ChangeDate time.Time + ResourceOwner string + ResourceOwnerName string + Sequence uint64 } type MemberType int32 diff --git a/internal/user/repository/view/model/user_membership.go b/internal/user/repository/view/model/user_membership.go index 66ca18ec90..bc2d2a6472 100644 --- a/internal/user/repository/view/model/user_membership.go +++ b/internal/user/repository/view/model/user_membership.go @@ -5,13 +5,14 @@ import ( "time" "github.com/caos/logging" + "github.com/lib/pq" + caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore/models" iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model" proj_es_model "github.com/caos/zitadel/internal/project/repository/eventsourcing/model" "github.com/caos/zitadel/internal/user/model" - "github.com/lib/pq" ) const ( @@ -28,41 +29,28 @@ type UserMembershipView struct { AggregateID string `json:"-" gorm:"column:aggregate_id;primary_key"` ObjectID string `json:"-" gorm:"column:object_id;primary_key"` - Roles pq.StringArray `json:"-" gorm:"column:roles"` - DisplayName string `json:"-" gorm:"column:display_name"` - CreationDate time.Time `json:"-" gorm:"column:creation_date"` - ChangeDate time.Time `json:"-" gorm:"column:change_date"` - ResourceOwner string `json:"-" gorm:"column:resource_owner"` - Sequence uint64 `json:"-" gorm:"column:sequence"` -} - -func UserMembershipFromModel(membership *model.UserMembershipView) *UserMembershipView { - return &UserMembershipView{ - UserID: membership.UserID, - MemberType: int32(membership.MemberType), - AggregateID: membership.AggregateID, - ObjectID: membership.ObjectID, - Roles: membership.Roles, - DisplayName: membership.DisplayName, - ChangeDate: membership.ChangeDate, - CreationDate: membership.CreationDate, - ResourceOwner: membership.ResourceOwner, - Sequence: membership.Sequence, - } + Roles pq.StringArray `json:"-" gorm:"column:roles"` + DisplayName string `json:"-" gorm:"column:display_name"` + CreationDate time.Time `json:"-" gorm:"column:creation_date"` + ChangeDate time.Time `json:"-" gorm:"column:change_date"` + ResourceOwner string `json:"-" gorm:"column:resource_owner"` + ResourceOwnerName string `json:"-" gorm:"column:resource_owner_name"` + Sequence uint64 `json:"-" gorm:"column:sequence"` } func UserMembershipToModel(membership *UserMembershipView) *model.UserMembershipView { return &model.UserMembershipView{ - UserID: membership.UserID, - MemberType: model.MemberType(membership.MemberType), - AggregateID: membership.AggregateID, - ObjectID: membership.ObjectID, - Roles: membership.Roles, - DisplayName: membership.DisplayName, - ChangeDate: membership.ChangeDate, - CreationDate: membership.CreationDate, - ResourceOwner: membership.ResourceOwner, - Sequence: membership.Sequence, + UserID: membership.UserID, + MemberType: model.MemberType(membership.MemberType), + AggregateID: membership.AggregateID, + ObjectID: membership.ObjectID, + Roles: membership.Roles, + DisplayName: membership.DisplayName, + ChangeDate: membership.ChangeDate, + CreationDate: membership.CreationDate, + ResourceOwner: membership.ResourceOwner, + ResourceOwnerName: membership.ResourceOwnerName, + Sequence: membership.Sequence, } } @@ -82,22 +70,26 @@ func (u *UserMembershipView) AppendEvent(event *models.Event) (err error) { case iam_es_model.IAMMemberAdded: u.setRootData(event, model.MemberTypeIam) err = u.setIamMemberData(event) - case iam_es_model.IAMMemberChanged: + case iam_es_model.IAMMemberChanged, + iam_es_model.IAMMemberRemoved: err = u.setIamMemberData(event) case org_es_model.OrgMemberAdded: u.setRootData(event, model.MemberTypeOrganisation) err = u.setOrgMemberData(event) - case org_es_model.OrgMemberChanged: + case org_es_model.OrgMemberChanged, + org_es_model.OrgMemberRemoved: err = u.setOrgMemberData(event) case proj_es_model.ProjectMemberAdded: u.setRootData(event, model.MemberTypeProject) err = u.setProjectMemberData(event) - case proj_es_model.ProjectMemberChanged: + case proj_es_model.ProjectMemberChanged, + proj_es_model.ProjectMemberRemoved: err = u.setProjectMemberData(event) case proj_es_model.ProjectGrantMemberAdded: u.setRootData(event, model.MemberTypeProjectGrant) err = u.setProjectMemberData(event) - case proj_es_model.ProjectGrantMemberChanged: + case proj_es_model.ProjectGrantMemberChanged, + proj_es_model.ProjectGrantMemberRemoved: err = u.setProjectMemberData(event) } return err diff --git a/internal/user/repository/view/usermembership_view.go b/internal/user/repository/view/usermembership_view.go index e92956b6f4..9006b35f2b 100644 --- a/internal/user/repository/view/usermembership_view.go +++ b/internal/user/repository/view/usermembership_view.go @@ -35,6 +35,16 @@ func UserMembershipsByAggregateID(db *gorm.DB, table, aggregateID string) ([]*mo return memberships, err } +func UserMembershipsByResourceOwner(db *gorm.DB, table, resourceOwner string) ([]*model.UserMembershipView, error) { + memberships := make([]*model.UserMembershipView, 0) + aggregateIDQuery := &usr_model.UserMembershipSearchQuery{Key: usr_model.UserMembershipSearchKeyResourceOwner, Value: resourceOwner, Method: global_model.SearchMethodEquals} + query := repository.PrepareSearchQuery(table, model.UserMembershipSearchRequest{ + Queries: []*usr_model.UserMembershipSearchQuery{aggregateIDQuery}, + }) + _, err := query(db, &memberships) + return memberships, err +} + func SearchUserMemberships(db *gorm.DB, table string, req *usr_model.UserMembershipSearchRequest) ([]*model.UserMembershipView, uint64, error) { users := make([]*model.UserMembershipView, 0) query := repository.PrepareSearchQuery(table, model.UserMembershipSearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries}) diff --git a/migrations/cockroach/V1.11__usermembership.sql b/migrations/cockroach/V1.11__usermembership.sql new file mode 100644 index 0000000000..20bba86942 --- /dev/null +++ b/migrations/cockroach/V1.11__usermembership.sql @@ -0,0 +1,18 @@ +CREATE TABLE auth.user_memberships ( + user_id TEXT, + member_type SMALLINT, + aggregate_id TEXT, + object_id TEXT, + + roles TEXT ARRAY, + display_name TEXT, + resource_owner TEXT, + resource_owner_name TEXT, + creation_date TIMESTAMPTZ, + change_date TIMESTAMPTZ, + sequence BIGINT, + + PRIMARY KEY (user_id, member_type, aggregate_id, object_id) +); + +ALTER TABLE management.user_memberships ADD COLUMN resource_owner_name TEXT; \ No newline at end of file