From d2ea9a1b8c06fb9dc1e91e736abd94227ef921a4 Mon Sep 17 00:00:00 2001 From: Silvan Date: Thu, 16 Dec 2021 14:25:38 +0100 Subject: [PATCH] feat: member queries (#2796) * refactor(domain): add user type * fix(projections): start with login names * fix(login_policy): correct handling of user domain claimed event * fix(projections): add members * refactor: simplify member projections * add migration for members * add metadata to member projections * refactor: login name projection * fix: set correct suffixes on login name projections * test(projections): login name reduces * fix: correct cols in reduce member * test(projections): org, iam, project members * member additional cols and conds as opt, add project grant members * fix(migration): members * fix(migration): correct database name * migration version * migs * better naming for member cond and col * split project and project grant members * prepare member columns * feat(queries): membership query * test(queries): membership prepare * fix(queries): multiple projections for latest sequence * fix(api): use query for membership queries in auth and management * feat: org member queries * fix(api): use query for iam member calls * fix(queries): org members * fix(queries): project members * fix(queries): project grant members * fix(query): member queries and user avatar column * member cols * fix(queries): membership stmt * fix user test * fix user test * fix(membership): correct display name * fix(projection): additional member manipulation events * additional member tests * fix(projections): additional events of idp links * fix: use query for memberships (#2797) * fix(api): use query for memberships * remove comment * handle err * refactor(projections): idp user link user aggregate type * fix(projections): handle old user events * fix(api): add asset prefix * no image for iam members --- cmd/zitadel/main.go | 4 +- internal/api/grpc/admin/iam_member.go | 11 +- .../api/grpc/admin/iam_member_converter.go | 24 +- internal/api/grpc/admin/server.go | 32 +- internal/api/grpc/management/org.go | 21 +- internal/api/grpc/management/org_converter.go | 27 ++ internal/api/grpc/management/project.go | 9 +- .../api/grpc/management/project_converter.go | 33 ++- internal/api/grpc/management/project_grant.go | 10 +- .../management/project_grant_converter.go | 45 +-- internal/api/grpc/management/server.go | 4 +- internal/api/grpc/member/converter.go | 56 ++++ internal/api/grpc/member/iam_member.go | 90 ------ internal/api/grpc/member/org_member.go | 90 ------ .../api/grpc/member/project_grant_member.go | 90 ------ internal/api/grpc/member/project_member.go | 90 ------ internal/api/grpc/user/membership.go | 17 +- .../eventsourcing/eventstore/user_grant.go | 105 +++---- internal/query/iam_member.go | 134 ++++++++- internal/query/iam_member_test.go | 277 ++++++++++++++++++ internal/query/login_name.go | 21 ++ internal/query/member.go | 4 +- internal/query/org_member.go | 137 ++++++++- internal/query/org_member_test.go | 277 ++++++++++++++++++ internal/query/project_grant_member.go | 139 ++++++++- internal/query/project_grant_member_test.go | 277 ++++++++++++++++++ internal/query/project_member.go | 136 ++++++++- internal/query/project_member_test.go | 277 ++++++++++++++++++ internal/query/projection/iam_member.go | 25 +- internal/query/projection/iam_member_test.go | 35 ++- .../query/projection/idp_login_policy_link.go | 51 ++++ .../projection/idp_login_policy_link_test.go | 87 ++++++ internal/query/projection/idp_user_link.go | 80 +++++ .../query/projection/idp_user_link_test.go | 116 ++++++++ internal/query/projection/login_name.go | 12 +- internal/query/projection/member.go | 10 +- internal/query/projection/org_member.go | 46 ++- internal/query/projection/org_member_test.go | 55 ++++ .../query/projection/project_grant_member.go | 85 +++++- .../projection/project_grant_member_test.go | 111 +++++++ internal/query/projection/project_member.go | 64 +++- .../query/projection/project_member_test.go | 83 ++++++ internal/query/user_membership.go | 39 +-- internal/query/user_membership_test.go | 241 ++------------- 44 files changed, 2820 insertions(+), 757 deletions(-) delete mode 100644 internal/api/grpc/member/iam_member.go delete mode 100644 internal/api/grpc/member/org_member.go delete mode 100644 internal/api/grpc/member/project_grant_member.go delete mode 100644 internal/api/grpc/member/project_member.go create mode 100644 internal/query/iam_member_test.go create mode 100644 internal/query/login_name.go create mode 100644 internal/query/org_member_test.go create mode 100644 internal/query/project_grant_member_test.go create mode 100644 internal/query/project_member_test.go diff --git a/cmd/zitadel/main.go b/cmd/zitadel/main.go index f10b85622c..6944f2d367 100644 --- a/cmd/zitadel/main.go +++ b/cmd/zitadel/main.go @@ -225,12 +225,12 @@ func startAPI(ctx context.Context, conf *Config, verifier *internal_authz.TokenV apis := api.Create(conf.API, conf.InternalAuthZ, query, authZRepo, authRepo, repo, conf.SystemDefaults) if *adminEnabled { - apis.RegisterServer(ctx, admin.CreateServer(command, query, repo, conf.SystemDefaults.Domain)) + apis.RegisterServer(ctx, admin.CreateServer(command, query, repo, conf.SystemDefaults.Domain, conf.Admin.APIDomain+"/assets/v1/")) } managementRepo, err := mgmt_es.Start(conf.Mgmt, conf.SystemDefaults, roles, query, static) logging.Log("API-Gd2qq").OnError(err).Fatal("error starting management repo") if *managementEnabled { - apis.RegisterServer(ctx, management.CreateServer(command, query, managementRepo, conf.SystemDefaults)) + apis.RegisterServer(ctx, management.CreateServer(command, query, managementRepo, conf.SystemDefaults, conf.Mgmt.APIDomain+"/assets/v1/")) } if *authEnabled { apis.RegisterServer(ctx, auth.CreateServer(command, query, authRepo, conf.SystemDefaults)) diff --git a/internal/api/grpc/admin/iam_member.go b/internal/api/grpc/admin/iam_member.go index 4cd5ce69f3..2275da13b0 100644 --- a/internal/api/grpc/admin/iam_member.go +++ b/internal/api/grpc/admin/iam_member.go @@ -18,13 +18,18 @@ func (s *Server) ListIAMMemberRoles(ctx context.Context, req *admin_pb.ListIAMMe } func (s *Server) ListIAMMembers(ctx context.Context, req *admin_pb.ListIAMMembersRequest) (*admin_pb.ListIAMMembersResponse, error) { - res, err := s.iam.SearchIAMMembers(ctx, ListIAMMemberRequestToModel(req)) + queries, err := ListIAMMembersRequestToQuery(req) + if err != nil { + return nil, err + } + res, err := s.query.IAMMembers(ctx, queries) if err != nil { return nil, err } return &admin_pb.ListIAMMembersResponse{ - Details: object.ToListDetails(res.TotalResult, res.Sequence, res.Timestamp), - Result: member.IAMMembersToPb(res.Result), + Details: object.ToListDetails(res.Count, res.Sequence, res.Timestamp), + //TODO: resource owner of user of the member instead of the membership resource owner + Result: member.MembersToPb("", res.Members), }, nil } diff --git a/internal/api/grpc/admin/iam_member_converter.go b/internal/api/grpc/admin/iam_member_converter.go index 89f935738c..f2cee0ddfe 100644 --- a/internal/api/grpc/admin/iam_member_converter.go +++ b/internal/api/grpc/admin/iam_member_converter.go @@ -4,7 +4,7 @@ import ( member_grpc "github.com/caos/zitadel/internal/api/grpc/member" "github.com/caos/zitadel/internal/api/grpc/object" "github.com/caos/zitadel/internal/domain" - "github.com/caos/zitadel/internal/iam/model" + "github.com/caos/zitadel/internal/query" admin_pb "github.com/caos/zitadel/pkg/grpc/admin" ) @@ -22,13 +22,21 @@ func UpdateIAMMemberToDomain(req *admin_pb.UpdateIAMMemberRequest) *domain.Membe } } -func ListIAMMemberRequestToModel(req *admin_pb.ListIAMMembersRequest) *model.IAMMemberSearchRequest { +func ListIAMMembersRequestToQuery(req *admin_pb.ListIAMMembersRequest) (*query.IAMMembersQuery, error) { offset, limit, asc := object.ListQueryToModel(req.Query) - return &model.IAMMemberSearchRequest{ - Offset: offset, - Limit: limit, - Asc: asc, - // SortingColumn: model.IAMMemberSearchKey, //TOOD: not implemented in proto - Queries: member_grpc.MemberQueriesToIAMMember(req.Queries), + queries, err := member_grpc.MemberQueriesToQuery(req.Queries) + if err != nil { + return nil, err } + return &query.IAMMembersQuery{ + MembersQuery: query.MembersQuery{ + SearchRequest: query.SearchRequest{ + Offset: offset, + Limit: limit, + Asc: asc, + // SortingColumn: model.IAMMemberSearchKey, //TOOD: not implemented in proto + }, + Queries: queries, + }, + }, nil } diff --git a/internal/api/grpc/admin/server.go b/internal/api/grpc/admin/server.go index e5ff49a9da..fcc0f8df2a 100644 --- a/internal/api/grpc/admin/server.go +++ b/internal/api/grpc/admin/server.go @@ -20,28 +20,30 @@ var _ admin.AdminServiceServer = (*Server)(nil) type Server struct { admin.UnimplementedAdminServiceServer - command *command.Commands - query *query.Queries - iam repository.IAMRepository - administrator repository.AdministratorRepository - repo repository.Repository - users repository.UserRepository - iamDomain string + command *command.Commands + query *query.Queries + iam repository.IAMRepository + administrator repository.AdministratorRepository + repo repository.Repository + users repository.UserRepository + iamDomain string + assetsAPIDomain string } type Config struct { Repository eventsourcing.Config } -func CreateServer(command *command.Commands, query *query.Queries, repo repository.Repository, iamDomain string) *Server { +func CreateServer(command *command.Commands, query *query.Queries, repo repository.Repository, iamDomain, assetsAPIDomain string) *Server { return &Server{ - command: command, - query: query, - iam: repo, - administrator: repo, - repo: repo, - users: repo, - iamDomain: iamDomain, + command: command, + query: query, + iam: repo, + administrator: repo, + repo: repo, + users: repo, + iamDomain: iamDomain, + assetsAPIDomain: assetsAPIDomain, } } diff --git a/internal/api/grpc/management/org.go b/internal/api/grpc/management/org.go index a800cefb7f..ce2cc985af 100644 --- a/internal/api/grpc/management/org.go +++ b/internal/api/grpc/management/org.go @@ -11,7 +11,6 @@ import ( policy_grpc "github.com/caos/zitadel/internal/api/grpc/policy" "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/eventstore/v1/models" - org_model "github.com/caos/zitadel/internal/org/model" "github.com/caos/zitadel/internal/query" usr_model "github.com/caos/zitadel/internal/user/model" mgmt_pb "github.com/caos/zitadel/pkg/grpc/management" @@ -216,36 +215,24 @@ func (s *Server) ListOrgMemberRoles(ctx context.Context, req *mgmt_pb.ListOrgMem } func (s *Server) ListOrgMembers(ctx context.Context, req *mgmt_pb.ListOrgMembersRequest) (*mgmt_pb.ListOrgMembersResponse, error) { - queries, err := ListOrgMembersRequestToModel(req) + queries, err := ListOrgMembersRequestToModel(ctx, req) if err != nil { return nil, err } - members, err := s.org.SearchMyOrgMembers(ctx, queries) + members, err := s.query.OrgMembers(ctx, queries) if err != nil { return nil, err } return &mgmt_pb.ListOrgMembersResponse{ - Result: member_grpc.OrgMembersToPb(members.Result), + Result: member_grpc.MembersToPb(s.assetAPIPrefix, members.Members), Details: object.ToListDetails( - members.TotalResult, + members.Count, members.Sequence, members.Timestamp, ), }, nil } -func ListOrgMembersRequestToModel(req *mgmt_pb.ListOrgMembersRequest) (*org_model.OrgMemberSearchRequest, error) { - offset, limit, asc := object.ListQueryToModel(req.Query) - queries := member_grpc.MemberQueriesToOrgMember(req.Queries) - return &org_model.OrgMemberSearchRequest{ - Offset: offset, - Limit: limit, - Asc: asc, - //SortingColumn: //TODO: sorting - Queries: queries, - }, nil -} - func (s *Server) AddOrgMember(ctx context.Context, req *mgmt_pb.AddOrgMemberRequest) (*mgmt_pb.AddOrgMemberResponse, error) { addedMember, err := s.command.AddOrgMember(ctx, AddOrgMemberRequestToDomain(ctx, req)) if err != nil { diff --git a/internal/api/grpc/management/org_converter.go b/internal/api/grpc/management/org_converter.go index a12ff4ccbf..bd0f1b9c74 100644 --- a/internal/api/grpc/management/org_converter.go +++ b/internal/api/grpc/management/org_converter.go @@ -4,6 +4,7 @@ import ( "context" "github.com/caos/zitadel/internal/api/authz" + member_grpc "github.com/caos/zitadel/internal/api/grpc/member" "github.com/caos/zitadel/internal/api/grpc/object" org_grpc "github.com/caos/zitadel/internal/api/grpc/org" "github.com/caos/zitadel/internal/domain" @@ -72,3 +73,29 @@ func AddOrgMemberRequestToDomain(ctx context.Context, req *mgmt_pb.AddOrgMemberR func UpdateOrgMemberRequestToDomain(ctx context.Context, req *mgmt_pb.UpdateOrgMemberRequest) *domain.Member { return domain.NewMember(authz.GetCtxData(ctx).OrgID, req.UserId, req.Roles...) } + +func ListOrgMembersRequestToModel(ctx context.Context, req *mgmt_pb.ListOrgMembersRequest) (*query.OrgMembersQuery, error) { + ctxData := authz.GetCtxData(ctx) + offset, limit, asc := object.ListQueryToModel(req.Query) + queries, err := member_grpc.MemberQueriesToQuery(req.Queries) + if err != nil { + return nil, err + } + ownerQuery, err := query.NewMemberResourceOwnerSearchQuery(ctxData.OrgID) + if err != nil { + return nil, err + } + queries = append(queries, ownerQuery) + return &query.OrgMembersQuery{ + MembersQuery: query.MembersQuery{ + SearchRequest: query.SearchRequest{ + Offset: offset, + Limit: limit, + Asc: asc, + //SortingColumn: //TODO: sorting + }, + Queries: queries, + }, + OrgID: ctxData.OrgID, + }, nil +} diff --git a/internal/api/grpc/management/project.go b/internal/api/grpc/management/project.go index c04d975c64..7346f859fb 100644 --- a/internal/api/grpc/management/project.go +++ b/internal/api/grpc/management/project.go @@ -279,19 +279,18 @@ func (s *Server) ListProjectMemberRoles(ctx context.Context, _ *mgmt_pb.ListProj } func (s *Server) ListProjectMembers(ctx context.Context, req *mgmt_pb.ListProjectMembersRequest) (*mgmt_pb.ListProjectMembersResponse, error) { - queries, err := ListProjectMembersRequestToModel(req) + queries, err := ListProjectMembersRequestToModel(ctx, req) if err != nil { return nil, err } - queries.AppendProjectQuery(req.ProjectId) - members, err := s.project.SearchProjectMembers(ctx, queries) + members, err := s.query.ProjectMembers(ctx, queries) if err != nil { return nil, err } return &mgmt_pb.ListProjectMembersResponse{ - Result: member_grpc.ProjectMembersToPb(members.Result), + Result: member_grpc.MembersToPb(s.assetAPIPrefix, members.Members), Details: object_grpc.ToListDetails( - members.TotalResult, + members.Count, members.Sequence, members.Timestamp, ), diff --git a/internal/api/grpc/management/project_converter.go b/internal/api/grpc/management/project_converter.go index 2cb2876dc4..10c2199194 100644 --- a/internal/api/grpc/management/project_converter.go +++ b/internal/api/grpc/management/project_converter.go @@ -1,12 +1,14 @@ package management import ( + "context" + + "github.com/caos/zitadel/internal/api/authz" member_grpc "github.com/caos/zitadel/internal/api/grpc/member" "github.com/caos/zitadel/internal/api/grpc/object" proj_grpc "github.com/caos/zitadel/internal/api/grpc/project" "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/eventstore/v1/models" - proj_model "github.com/caos/zitadel/internal/project/model" "github.com/caos/zitadel/internal/query" mgmt_pb "github.com/caos/zitadel/pkg/grpc/management" proj_pb "github.com/caos/zitadel/pkg/grpc/project" @@ -163,14 +165,27 @@ func listGrantedProjectRolesRequestToModel(req *mgmt_pb.ListGrantedProjectRolesR }, nil } -func ListProjectMembersRequestToModel(req *mgmt_pb.ListProjectMembersRequest) (*proj_model.ProjectMemberSearchRequest, error) { +func ListProjectMembersRequestToModel(ctx context.Context, req *mgmt_pb.ListProjectMembersRequest) (*query.ProjectMembersQuery, error) { offset, limit, asc := object.ListQueryToModel(req.Query) - queries := member_grpc.MemberQueriesToProjectMember(req.Queries) - return &proj_model.ProjectMemberSearchRequest{ - Offset: offset, - Limit: limit, - Asc: asc, - //SortingColumn: //TODO: sorting - Queries: queries, + queries, err := member_grpc.MemberQueriesToQuery(req.Queries) + if err != nil { + return nil, err + } + ownerQuery, err := query.NewMemberResourceOwnerSearchQuery(authz.GetCtxData(ctx).OrgID) + if err != nil { + return nil, err + } + queries = append(queries, ownerQuery) + return &query.ProjectMembersQuery{ + MembersQuery: query.MembersQuery{ + SearchRequest: query.SearchRequest{ + Offset: offset, + Limit: limit, + Asc: asc, + //SortingColumn: //TODO: sorting + }, + Queries: queries, + }, + ProjectID: req.ProjectId, }, nil } diff --git a/internal/api/grpc/management/project_grant.go b/internal/api/grpc/management/project_grant.go index 07c28feb31..6b9542541d 100644 --- a/internal/api/grpc/management/project_grant.go +++ b/internal/api/grpc/management/project_grant.go @@ -134,14 +134,18 @@ func (s *Server) ListProjectGrantMemberRoles(ctx context.Context, req *mgmt_pb.L } func (s *Server) ListProjectGrantMembers(ctx context.Context, req *mgmt_pb.ListProjectGrantMembersRequest) (*mgmt_pb.ListProjectGrantMembersResponse, error) { - response, err := s.project.SearchProjectGrantMembers(ctx, ListProjectGrantMembersRequestToModel(req)) + queries, err := ListProjectGrantMembersRequestToModel(ctx, req) + if err != nil { + return nil, err + } + response, err := s.query.ProjectGrantMembers(ctx, queries) if err != nil { return nil, err } return &mgmt_pb.ListProjectGrantMembersResponse{ - Result: member_grpc.ProjectGrantMembersToPb(response.Result), + Result: member_grpc.MembersToPb(s.assetAPIPrefix, response.Members), Details: object_grpc.ToListDetails( - response.TotalResult, + response.Count, response.Sequence, response.Timestamp, ), diff --git a/internal/api/grpc/management/project_grant_converter.go b/internal/api/grpc/management/project_grant_converter.go index 6dfae0ec11..6c98c09dd4 100644 --- a/internal/api/grpc/management/project_grant_converter.go +++ b/internal/api/grpc/management/project_grant_converter.go @@ -1,12 +1,14 @@ package management import ( + "context" + + "github.com/caos/zitadel/internal/api/authz" member_grpc "github.com/caos/zitadel/internal/api/grpc/member" "github.com/caos/zitadel/internal/api/grpc/object" "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore/v1/models" - proj_model "github.com/caos/zitadel/internal/project/model" "github.com/caos/zitadel/internal/query" mgmt_pb "github.com/caos/zitadel/pkg/grpc/management" proj_pb "github.com/caos/zitadel/pkg/grpc/project" @@ -118,27 +120,30 @@ func UpdateProjectGrantRequestToDomain(req *mgmt_pb.UpdateProjectGrantRequest) * } } -func ListProjectGrantMembersRequestToModel(req *mgmt_pb.ListProjectGrantMembersRequest) *proj_model.ProjectGrantMemberSearchRequest { +func ListProjectGrantMembersRequestToModel(ctx context.Context, req *mgmt_pb.ListProjectGrantMembersRequest) (*query.ProjectGrantMembersQuery, error) { offset, limit, asc := object.ListQueryToModel(req.Query) - queries := member_grpc.MemberQueriesToProjectGrantMember(req.Queries) - queries = append(queries, - &proj_model.ProjectGrantMemberSearchQuery{ - Key: proj_model.ProjectGrantMemberSearchKeyProjectID, - Method: domain.SearchMethodEquals, - Value: req.ProjectId, - }, - &proj_model.ProjectGrantMemberSearchQuery{ - Key: proj_model.ProjectGrantMemberSearchKeyGrantID, - Method: domain.SearchMethodEquals, - Value: req.GrantId, - }) - return &proj_model.ProjectGrantMemberSearchRequest{ - Offset: offset, - Limit: limit, - Asc: asc, - //SortingColumn: //TODO: sorting - Queries: queries, + queries, err := member_grpc.MemberQueriesToQuery(req.Queries) + if err != nil { + return nil, err } + ownerQuery, err := query.NewMemberResourceOwnerSearchQuery(authz.GetCtxData(ctx).OrgID) + if err != nil { + return nil, err + } + queries = append(queries, ownerQuery) + return &query.ProjectGrantMembersQuery{ + MembersQuery: query.MembersQuery{ + SearchRequest: query.SearchRequest{ + Offset: offset, + Limit: limit, + Asc: asc, + //SortingColumn: //TODO: sorting + }, + Queries: queries, + }, + ProjectID: req.ProjectId, + GrantID: req.GrantId, + }, nil } func AddProjectGrantMemberRequestToDomain(req *mgmt_pb.AddProjectGrantMemberRequest) *domain.ProjectGrantMember { diff --git a/internal/api/grpc/management/server.go b/internal/api/grpc/management/server.go index 53d3cc6d45..a4db772d18 100644 --- a/internal/api/grpc/management/server.go +++ b/internal/api/grpc/management/server.go @@ -30,13 +30,14 @@ type Server struct { iam repository.IamRepository authZ authz.Config systemDefaults systemdefaults.SystemDefaults + assetAPIPrefix string } type Config struct { Repository eventsourcing.Config } -func CreateServer(command *command.Commands, query *query.Queries, repo repository.Repository, sd systemdefaults.SystemDefaults) *Server { +func CreateServer(command *command.Commands, query *query.Queries, repo repository.Repository, sd systemdefaults.SystemDefaults, assetAPIPrefix string) *Server { return &Server{ command: command, query: query, @@ -46,6 +47,7 @@ func CreateServer(command *command.Commands, query *query.Queries, repo reposito usergrant: repo, iam: repo, systemDefaults: sd, + assetAPIPrefix: assetAPIPrefix, } } diff --git a/internal/api/grpc/member/converter.go b/internal/api/grpc/member/converter.go index 970aa4ed0a..472860b844 100644 --- a/internal/api/grpc/member/converter.go +++ b/internal/api/grpc/member/converter.go @@ -1,7 +1,10 @@ package member import ( + "github.com/caos/zitadel/internal/api/grpc/object" "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/query" member_pb "github.com/caos/zitadel/pkg/grpc/member" ) @@ -11,3 +14,56 @@ func MemberToDomain(member *member_pb.Member) *domain.Member { Roles: member.Roles, } } + +func MembersToPb(assetAPIPrefix string, members []*query.Member) []*member_pb.Member { + m := make([]*member_pb.Member, len(members)) + for i, member := range members { + m[i] = MemberToPb(assetAPIPrefix, member) + } + return m +} + +func MemberToPb(assetAPIPrefix string, m *query.Member) *member_pb.Member { + return &member_pb.Member{ + UserId: m.UserID, + Roles: m.Roles, + PreferredLoginName: m.PreferredLoginName, + Email: m.Email, + FirstName: m.FirstName, + LastName: m.LastName, + DisplayName: m.DisplayName, + AvatarUrl: domain.AvatarURL(assetAPIPrefix, m.ResourceOwner, m.AvatarURL), + Details: object.ToViewDetailsPb( + m.Sequence, + m.CreationDate, + m.ChangeDate, + m.ResourceOwner, + ), + } +} + +func MemberQueriesToQuery(queries []*member_pb.SearchQuery) (q []query.SearchQuery, err error) { + q = make([]query.SearchQuery, len(queries)) + for i, query := range queries { + q[i], err = MemberQueryToMember(query) + if err != nil { + return nil, err + } + } + return q, nil +} + +func MemberQueryToMember(search *member_pb.SearchQuery) (query.SearchQuery, error) { + switch q := search.Query.(type) { + case *member_pb.SearchQuery_EmailQuery: + return query.NewMemberEmailSearchQuery(object.TextMethodToQuery(q.EmailQuery.Method), q.EmailQuery.Email) + case *member_pb.SearchQuery_FirstNameQuery: + return query.NewMemberFirstNameSearchQuery(object.TextMethodToQuery(q.FirstNameQuery.Method), q.FirstNameQuery.FirstName) + case *member_pb.SearchQuery_LastNameQuery: + return query.NewMemberLastNameSearchQuery(object.TextMethodToQuery(q.LastNameQuery.Method), q.LastNameQuery.LastName) + case *member_pb.SearchQuery_UserIdQuery: + return query.NewMemberUserIDSearchQuery(q.UserIdQuery.UserId) + default: + return nil, errors.ThrowInvalidArgument(nil, "MEMBE-7Bb92", "Errors.Query.InvalidRequest") + } +} diff --git a/internal/api/grpc/member/iam_member.go b/internal/api/grpc/member/iam_member.go deleted file mode 100644 index a1c716d105..0000000000 --- a/internal/api/grpc/member/iam_member.go +++ /dev/null @@ -1,90 +0,0 @@ -package member - -import ( - "github.com/caos/zitadel/internal/api/grpc/object" - "github.com/caos/zitadel/internal/domain" - iam_model "github.com/caos/zitadel/internal/iam/model" - member_pb "github.com/caos/zitadel/pkg/grpc/member" -) - -func IAMMembersToPb(members []*iam_model.IAMMemberView) []*member_pb.Member { - m := make([]*member_pb.Member, len(members)) - for i, member := range members { - m[i] = IAMMemberToPb(member) - } - return m -} - -func IAMMemberToPb(m *iam_model.IAMMemberView) *member_pb.Member { - return &member_pb.Member{ - UserId: m.UserID, - Roles: m.Roles, - PreferredLoginName: m.PreferredLoginName, - Email: m.Email, - FirstName: m.FirstName, - LastName: m.LastName, - DisplayName: m.DisplayName, - AvatarUrl: m.AvatarURL, - Details: object.ToViewDetailsPb( - m.Sequence, - m.CreationDate, - m.ChangeDate, - "", //TODO: not returnd - ), - } -} - -func MemberQueriesToIAMMember(queries []*member_pb.SearchQuery) []*iam_model.IAMMemberSearchQuery { - q := make([]*iam_model.IAMMemberSearchQuery, len(queries)) - for i, query := range queries { - q[i] = MemberQueryToIAMMember(query) - } - return q -} - -func MemberQueryToIAMMember(query *member_pb.SearchQuery) *iam_model.IAMMemberSearchQuery { - switch q := query.Query.(type) { - case *member_pb.SearchQuery_EmailQuery: - return EmailQueryToIAMMemberQuery(q.EmailQuery) - case *member_pb.SearchQuery_FirstNameQuery: - return FirstNameQueryToIAMMemberQuery(q.FirstNameQuery) - case *member_pb.SearchQuery_LastNameQuery: - return LastNameQueryToIAMMemberQuery(q.LastNameQuery) - case *member_pb.SearchQuery_UserIdQuery: - return UserIDQueryToIAMMemberQuery(q.UserIdQuery) - default: - return nil - } -} - -func FirstNameQueryToIAMMemberQuery(query *member_pb.FirstNameQuery) *iam_model.IAMMemberSearchQuery { - return &iam_model.IAMMemberSearchQuery{ - Key: iam_model.IAMMemberSearchKeyFirstName, - Method: object.TextMethodToModel(query.Method), - Value: query.FirstName, - } -} - -func LastNameQueryToIAMMemberQuery(query *member_pb.LastNameQuery) *iam_model.IAMMemberSearchQuery { - return &iam_model.IAMMemberSearchQuery{ - Key: iam_model.IAMMemberSearchKeyLastName, - Method: object.TextMethodToModel(query.Method), - Value: query.LastName, - } -} - -func EmailQueryToIAMMemberQuery(query *member_pb.EmailQuery) *iam_model.IAMMemberSearchQuery { - return &iam_model.IAMMemberSearchQuery{ - Key: iam_model.IAMMemberSearchKeyEmail, - Method: object.TextMethodToModel(query.Method), - Value: query.Email, - } -} - -func UserIDQueryToIAMMemberQuery(query *member_pb.UserIDQuery) *iam_model.IAMMemberSearchQuery { - return &iam_model.IAMMemberSearchQuery{ - Key: iam_model.IAMMemberSearchKeyUserID, - Method: domain.SearchMethodEquals, - Value: query.UserId, - } -} diff --git a/internal/api/grpc/member/org_member.go b/internal/api/grpc/member/org_member.go deleted file mode 100644 index a5f2b434c6..0000000000 --- a/internal/api/grpc/member/org_member.go +++ /dev/null @@ -1,90 +0,0 @@ -package member - -import ( - "github.com/caos/zitadel/internal/api/grpc/object" - "github.com/caos/zitadel/internal/domain" - org_model "github.com/caos/zitadel/internal/org/model" - member_pb "github.com/caos/zitadel/pkg/grpc/member" -) - -func OrgMembersToPb(members []*org_model.OrgMemberView) []*member_pb.Member { - m := make([]*member_pb.Member, len(members)) - for i, member := range members { - m[i] = OrgMemberToPb(member) - } - return m -} - -func OrgMemberToPb(m *org_model.OrgMemberView) *member_pb.Member { - return &member_pb.Member{ - UserId: m.UserID, - Roles: m.Roles, - PreferredLoginName: m.PreferredLoginName, - Email: m.Email, - FirstName: m.FirstName, - LastName: m.LastName, - DisplayName: m.DisplayName, - AvatarUrl: m.AvatarURL, - Details: object.ToViewDetailsPb( - m.Sequence, - m.CreationDate, - m.ChangeDate, - "", //TODO: not returnd - ), - } -} - -func MemberQueriesToOrgMember(queries []*member_pb.SearchQuery) []*org_model.OrgMemberSearchQuery { - q := make([]*org_model.OrgMemberSearchQuery, len(queries)) - for i, query := range queries { - q[i] = MemberQueryToOrgMember(query) - } - return q -} - -func MemberQueryToOrgMember(query *member_pb.SearchQuery) *org_model.OrgMemberSearchQuery { - switch q := query.Query.(type) { - case *member_pb.SearchQuery_EmailQuery: - return EmailQueryToOrgMemberQuery(q.EmailQuery) - case *member_pb.SearchQuery_FirstNameQuery: - return FirstNameQueryToOrgMemberQuery(q.FirstNameQuery) - case *member_pb.SearchQuery_LastNameQuery: - return LastNameQueryToOrgMemberQuery(q.LastNameQuery) - case *member_pb.SearchQuery_UserIdQuery: - return UserIDQueryToOrgMemberQuery(q.UserIdQuery) - default: - return nil - } -} - -func FirstNameQueryToOrgMemberQuery(query *member_pb.FirstNameQuery) *org_model.OrgMemberSearchQuery { - return &org_model.OrgMemberSearchQuery{ - Key: org_model.OrgMemberSearchKeyFirstName, - Method: object.TextMethodToModel(query.Method), - Value: query.FirstName, - } -} - -func LastNameQueryToOrgMemberQuery(query *member_pb.LastNameQuery) *org_model.OrgMemberSearchQuery { - return &org_model.OrgMemberSearchQuery{ - Key: org_model.OrgMemberSearchKeyLastName, - Method: object.TextMethodToModel(query.Method), - Value: query.LastName, - } -} - -func EmailQueryToOrgMemberQuery(query *member_pb.EmailQuery) *org_model.OrgMemberSearchQuery { - return &org_model.OrgMemberSearchQuery{ - Key: org_model.OrgMemberSearchKeyEmail, - Method: object.TextMethodToModel(query.Method), - Value: query.Email, - } -} - -func UserIDQueryToOrgMemberQuery(query *member_pb.UserIDQuery) *org_model.OrgMemberSearchQuery { - return &org_model.OrgMemberSearchQuery{ - Key: org_model.OrgMemberSearchKeyUserID, - Method: domain.SearchMethodEquals, - Value: query.UserId, - } -} diff --git a/internal/api/grpc/member/project_grant_member.go b/internal/api/grpc/member/project_grant_member.go deleted file mode 100644 index d807fd4b44..0000000000 --- a/internal/api/grpc/member/project_grant_member.go +++ /dev/null @@ -1,90 +0,0 @@ -package member - -import ( - "github.com/caos/zitadel/internal/api/grpc/object" - "github.com/caos/zitadel/internal/domain" - proj_model "github.com/caos/zitadel/internal/project/model" - member_pb "github.com/caos/zitadel/pkg/grpc/member" -) - -func ProjectGrantMembersToPb(members []*proj_model.ProjectGrantMemberView) []*member_pb.Member { - m := make([]*member_pb.Member, len(members)) - for i, member := range members { - m[i] = ProjectGrantMemberToPb(member) - } - return m -} - -func ProjectGrantMemberToPb(m *proj_model.ProjectGrantMemberView) *member_pb.Member { - return &member_pb.Member{ - UserId: m.UserID, - Roles: m.Roles, - PreferredLoginName: m.PreferredLoginName, - Email: m.Email, - FirstName: m.FirstName, - LastName: m.LastName, - DisplayName: m.DisplayName, - AvatarUrl: m.AvatarURL, - Details: object.ToViewDetailsPb( - m.Sequence, - m.CreationDate, - m.ChangeDate, - "", //TODO: not returnd - ), - } -} - -func MemberQueriesToProjectGrantMember(queries []*member_pb.SearchQuery) []*proj_model.ProjectGrantMemberSearchQuery { - q := make([]*proj_model.ProjectGrantMemberSearchQuery, len(queries)) - for i, query := range queries { - q[i] = MemberQueryToProjectGrantMember(query) - } - return q -} - -func MemberQueryToProjectGrantMember(query *member_pb.SearchQuery) *proj_model.ProjectGrantMemberSearchQuery { - switch q := query.Query.(type) { - case *member_pb.SearchQuery_EmailQuery: - return EmailQueryToProjectGrantMemberQuery(q.EmailQuery) - case *member_pb.SearchQuery_FirstNameQuery: - return FirstNameQueryToProjectGrantMemberQuery(q.FirstNameQuery) - case *member_pb.SearchQuery_LastNameQuery: - return LastNameQueryToProjectGrantMemberQuery(q.LastNameQuery) - case *member_pb.SearchQuery_UserIdQuery: - return UserIDQueryToProjectGrantMemberQuery(q.UserIdQuery) - default: - return nil - } -} - -func FirstNameQueryToProjectGrantMemberQuery(query *member_pb.FirstNameQuery) *proj_model.ProjectGrantMemberSearchQuery { - return &proj_model.ProjectGrantMemberSearchQuery{ - Key: proj_model.ProjectGrantMemberSearchKeyFirstName, - Method: object.TextMethodToModel(query.Method), - Value: query.FirstName, - } -} - -func LastNameQueryToProjectGrantMemberQuery(query *member_pb.LastNameQuery) *proj_model.ProjectGrantMemberSearchQuery { - return &proj_model.ProjectGrantMemberSearchQuery{ - Key: proj_model.ProjectGrantMemberSearchKeyLastName, - Method: object.TextMethodToModel(query.Method), - Value: query.LastName, - } -} - -func EmailQueryToProjectGrantMemberQuery(query *member_pb.EmailQuery) *proj_model.ProjectGrantMemberSearchQuery { - return &proj_model.ProjectGrantMemberSearchQuery{ - Key: proj_model.ProjectGrantMemberSearchKeyEmail, - Method: object.TextMethodToModel(query.Method), - Value: query.Email, - } -} - -func UserIDQueryToProjectGrantMemberQuery(query *member_pb.UserIDQuery) *proj_model.ProjectGrantMemberSearchQuery { - return &proj_model.ProjectGrantMemberSearchQuery{ - Key: proj_model.ProjectGrantMemberSearchKeyUserID, - Method: domain.SearchMethodEquals, - Value: query.UserId, - } -} diff --git a/internal/api/grpc/member/project_member.go b/internal/api/grpc/member/project_member.go deleted file mode 100644 index afd63bea08..0000000000 --- a/internal/api/grpc/member/project_member.go +++ /dev/null @@ -1,90 +0,0 @@ -package member - -import ( - "github.com/caos/zitadel/internal/api/grpc/object" - "github.com/caos/zitadel/internal/domain" - proj_model "github.com/caos/zitadel/internal/project/model" - member_pb "github.com/caos/zitadel/pkg/grpc/member" -) - -func ProjectMembersToPb(members []*proj_model.ProjectMemberView) []*member_pb.Member { - m := make([]*member_pb.Member, len(members)) - for i, member := range members { - m[i] = ProjectMemberToPb(member) - } - return m -} - -func ProjectMemberToPb(m *proj_model.ProjectMemberView) *member_pb.Member { - return &member_pb.Member{ - UserId: m.UserID, - Roles: m.Roles, - PreferredLoginName: m.PreferredLoginName, - Email: m.Email, - FirstName: m.FirstName, - LastName: m.LastName, - DisplayName: m.DisplayName, - AvatarUrl: m.AvatarURL, - Details: object.ToViewDetailsPb( - m.Sequence, - m.CreationDate, - m.ChangeDate, - "", //TODO: not returnd - ), - } -} - -func MemberQueriesToProjectMember(queries []*member_pb.SearchQuery) []*proj_model.ProjectMemberSearchQuery { - q := make([]*proj_model.ProjectMemberSearchQuery, len(queries)) - for i, query := range queries { - q[i] = MemberQueryToProjectMember(query) - } - return q -} - -func MemberQueryToProjectMember(query *member_pb.SearchQuery) *proj_model.ProjectMemberSearchQuery { - switch q := query.Query.(type) { - case *member_pb.SearchQuery_EmailQuery: - return EmailQueryToProjectMemberQuery(q.EmailQuery) - case *member_pb.SearchQuery_FirstNameQuery: - return FirstNameQueryToProjectMemberQuery(q.FirstNameQuery) - case *member_pb.SearchQuery_LastNameQuery: - return LastNameQueryToProjectMemberQuery(q.LastNameQuery) - case *member_pb.SearchQuery_UserIdQuery: - return UserIDQueryToProjectMemberQuery(q.UserIdQuery) - default: - return nil - } -} - -func FirstNameQueryToProjectMemberQuery(query *member_pb.FirstNameQuery) *proj_model.ProjectMemberSearchQuery { - return &proj_model.ProjectMemberSearchQuery{ - Key: proj_model.ProjectMemberSearchKeyFirstName, - Method: object.TextMethodToModel(query.Method), - Value: query.FirstName, - } -} - -func LastNameQueryToProjectMemberQuery(query *member_pb.LastNameQuery) *proj_model.ProjectMemberSearchQuery { - return &proj_model.ProjectMemberSearchQuery{ - Key: proj_model.ProjectMemberSearchKeyLastName, - Method: object.TextMethodToModel(query.Method), - Value: query.LastName, - } -} - -func EmailQueryToProjectMemberQuery(query *member_pb.EmailQuery) *proj_model.ProjectMemberSearchQuery { - return &proj_model.ProjectMemberSearchQuery{ - Key: proj_model.ProjectMemberSearchKeyEmail, - Method: object.TextMethodToModel(query.Method), - Value: query.Email, - } -} - -func UserIDQueryToProjectMemberQuery(query *member_pb.UserIDQuery) *proj_model.ProjectMemberSearchQuery { - return &proj_model.ProjectMemberSearchQuery{ - Key: proj_model.ProjectMemberSearchKeyUserID, - Method: domain.SearchMethodEquals, - Value: query.UserId, - } -} diff --git a/internal/api/grpc/user/membership.go b/internal/api/grpc/user/membership.go index ccc66c4ed3..6a61b1b369 100644 --- a/internal/api/grpc/user/membership.go +++ b/internal/api/grpc/user/membership.go @@ -101,10 +101,11 @@ func MembershipsToMembershipsPb(memberships []*query.Membership) []*user_pb.Memb } func MembershipToMembershipPb(membership *query.Membership) *user_pb.Membership { + typ, name := memberTypeToPb(membership) return &user_pb.Membership{ UserId: membership.UserID, - Type: memberTypeToPb(membership), - DisplayName: membership.DisplayName, + Type: typ, + DisplayName: name, Roles: membership.Roles, Details: object.ToViewDetailsPb( membership.Sequence, @@ -115,23 +116,23 @@ func MembershipToMembershipPb(membership *query.Membership) *user_pb.Membership } } -func memberTypeToPb(membership *query.Membership) user_pb.MembershipType { +func memberTypeToPb(membership *query.Membership) (user_pb.MembershipType, string) { if membership.Org != nil { return &user_pb.Membership_OrgId{ OrgId: membership.Org.OrgID, - } + }, membership.Org.Name } else if membership.Project != nil { return &user_pb.Membership_ProjectId{ ProjectId: membership.Project.ProjectID, - } + }, membership.Project.Name } else if membership.ProjectGrant != nil { return &user_pb.Membership_ProjectGrantId{ ProjectGrantId: membership.ProjectGrant.GrantID, - } + }, membership.ProjectGrant.ProjectName } else if membership.IAM != nil { return &user_pb.Membership_Iam{ Iam: true, - } + }, membership.IAM.Name } - return nil + return nil, "" } diff --git a/internal/auth/repository/eventsourcing/eventstore/user_grant.go b/internal/auth/repository/eventsourcing/eventstore/user_grant.go index 1132c0bec4..9c5063704c 100644 --- a/internal/auth/repository/eventsourcing/eventstore/user_grant.go +++ b/internal/auth/repository/eventsourcing/eventstore/user_grant.go @@ -90,15 +90,21 @@ func (repo *UserGrantRepo) SearchMyProjectOrgs(ctx context.Context, request *gra 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 { +func (repo *UserGrantRepo) membershipsToOrgResp(memberships *query.Memberships) *grant_model.ProjectOrgSearchResponse { + orgs := make([]*grant_model.Org, 0, len(memberships.Memberships)) + for _, m := range memberships.Memberships { if !containsOrg(orgs, m.ResourceOwner) { - orgs = append(orgs, &grant_model.Org{OrgID: m.ResourceOwner, OrgName: m.ResourceOwnerName}) + org, err := repo.Query.OrgByID(context.TODO(), m.ResourceOwner) + if err != nil { + logging.LogWithFields("EVENT-k8Ikl", "owner", m.ResourceOwner).WithError(err).Warn("org not found") + orgs = append(orgs, &grant_model.Org{OrgID: m.ResourceOwner}) + continue + } + orgs = append(orgs, &grant_model.Org{OrgID: m.ResourceOwner, OrgName: org.Name}) } } return &grant_model.ProjectOrgSearchResponse{ - TotalResult: count, + TotalResult: memberships.Count, Result: orgs, } } @@ -143,50 +149,42 @@ func (repo *UserGrantRepo) SearchMyZitadelPermissions(ctx context.Context) ([]st return permissions.Permissions, nil } -func (repo *UserGrantRepo) searchUserMemberships(ctx context.Context) ([]*user_view_model.UserMembershipView, error) { +func (repo *UserGrantRepo) searchUserMemberships(ctx context.Context) ([]*query.Membership, error) { ctxData := authz.GetCtxData(ctx) - orgMemberships, orgCount, err := repo.View.SearchUserMemberships(&user_model.UserMembershipSearchRequest{ - Queries: []*user_model.UserMembershipSearchQuery{ - { - Key: user_model.UserMembershipSearchKeyUserID, - Method: domain.SearchMethodEquals, - Value: ctxData.UserID, - }, - { - Key: user_model.UserMembershipSearchKeyResourceOwner, - Method: domain.SearchMethodEquals, - Value: ctxData.OrgID, - }, - }, + userQuery, err := query.NewMembershipUserIDQuery(ctxData.UserID) + if err != nil { + return nil, err + } + ownerQuery, err := query.NewMembershipResourceOwnerQuery(ctxData.OrgID) + if err != nil { + return nil, err + } + + orgMemberships, err := repo.Query.Memberships(ctx, &query.MembershipSearchQuery{ + Queries: []query.SearchQuery{userQuery, ownerQuery}, }) if err != nil { return nil, err } - iamMemberships, iamCount, err := repo.View.SearchUserMemberships(&user_model.UserMembershipSearchRequest{ - Queries: []*user_model.UserMembershipSearchQuery{ - { - Key: user_model.UserMembershipSearchKeyUserID, - Method: domain.SearchMethodEquals, - Value: ctxData.UserID, - }, - { - Key: user_model.UserMembershipSearchKeyAggregateID, - Method: domain.SearchMethodEquals, - Value: repo.IamID, - }, - }, + iamQuery, err := query.NewMembershipIsIAMQuery() + if err != nil { + return nil, err + } + iamMemberships, err := repo.Query.Memberships(ctx, &query.MembershipSearchQuery{ + Queries: []query.SearchQuery{userQuery, iamQuery}, }) if err != nil { return nil, err } - if orgCount == 0 && iamCount == 0 { - return []*user_view_model.UserMembershipView{}, nil + if orgMemberships.Count == 0 && iamMemberships.Count == 0 { + return []*query.Membership{}, nil } - return append(orgMemberships, iamMemberships...), nil + return append(orgMemberships.Memberships, iamMemberships.Memberships...), nil } func (repo *UserGrantRepo) SearchMyProjectPermissions(ctx context.Context) ([]string, error) { ctxData := authz.GetCtxData(ctx) + //TODO: blocked until user grants moved to query-pkg usergrant, err := repo.View.UserGrantByIDs(ctxData.OrgID, ctxData.ProjectID, ctxData.UserID) if err != nil { return nil, err @@ -224,6 +222,7 @@ func (repo *UserGrantRepo) SearchAdminOrgs(request *grant_model.UserGrantSearchR } func (repo *UserGrantRepo) IsIamAdmin(ctx context.Context) (bool, error) { + //TODO: blocked until user grants moved to query grantSearch := &grant_model.UserGrantSearchRequest{ Queries: []*grant_model.UserGrantSearchQuery{ {Key: grant_model.UserGrantSearchKeyResourceOwner, Method: domain.SearchMethodEquals, Value: repo.IamID}, @@ -247,6 +246,7 @@ func (repo *UserGrantRepo) UserGrantsByProjectAndUserID(projectID, userID string } func (repo *UserGrantRepo) userOrg(ctxData authz.CtxData) (*grant_model.ProjectOrgSearchResponse, error) { + //TODO: blocked until user moved to query pkg user, err := repo.View.UserByID(ctxData.UserID) if err != nil { return nil, err @@ -255,40 +255,43 @@ func (repo *UserGrantRepo) userOrg(ctxData authz.CtxData) (*grant_model.ProjectO if err != nil { return nil, err } - return &grant_model.ProjectOrgSearchResponse{Result: []*grant_model.Org{&grant_model.Org{ + return &grant_model.ProjectOrgSearchResponse{Result: []*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: domain.SearchMethodEquals, - Value: ctxData.UserID, - }, + userQuery, err := query.NewMembershipUserIDQuery(ctxData.UserID) + if err != nil { + return nil, err + } + memberships, err := repo.Query.Memberships(context.TODO(), &query.MembershipSearchQuery{ + SearchRequest: query.SearchRequest{ + Offset: request.Offset, + Limit: request.Limit, + Asc: request.Asc, + //TODO: sorting column }, + Queries: []query.SearchQuery{userQuery}, }) if err != nil { return nil, err } - if len(memberships) > 0 { - return membershipsToOrgResp(memberships, count), nil + if len(memberships.Memberships) > 0 { + return repo.membershipsToOrgResp(memberships), nil } return repo.userOrg(ctxData) } -func (repo *UserGrantRepo) mapRoleToPermission(permissions *grant_model.Permissions, membership *user_view_model.UserMembershipView, role string) *grant_model.Permissions { +func (repo *UserGrantRepo) mapRoleToPermission(permissions *grant_model.Permissions, membership *query.Membership, 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 + if membership.Project != nil { + ctxID = membership.Project.ProjectID + } else if membership.ProjectGrant != nil { + ctxID = membership.ProjectGrant.GrantID } permissions.AppendPermissions(ctxID, mapping.Permissions...) } diff --git a/internal/query/iam_member.go b/internal/query/iam_member.go index e5332c102e..e0237ea617 100644 --- a/internal/query/iam_member.go +++ b/internal/query/iam_member.go @@ -1,6 +1,14 @@ package query -import "github.com/caos/zitadel/internal/query/projection" +import ( + "context" + "database/sql" + + sq "github.com/Masterminds/squirrel" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/query/projection" + "github.com/lib/pq" +) var ( iamMemberTable = table{ @@ -36,3 +44,127 @@ var ( table: iamMemberTable, } ) + +type IAMMembersQuery struct { + MembersQuery +} + +func (q *IAMMembersQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder { + return q.MembersQuery. + toQuery(query) +} + +func (q *Queries) IAMMembers(ctx context.Context, queries *IAMMembersQuery) (*Members, error) { + query, scan := prepareIAMMembersQuery() + stmt, args, err := queries.toQuery(query).ToSql() + if err != nil { + return nil, errors.ThrowInvalidArgument(err, "QUERY-USNwM", "Errors.Query.InvalidRequest") + } + + currentSequence, err := q.latestSequence(ctx, iamMemberTable) + if err != nil { + return nil, err + } + + rows, err := q.client.QueryContext(ctx, stmt, args...) + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-Pdg1I", "Errors.Internal") + } + members, err := scan(rows) + if err != nil { + return nil, err + } + members.LatestSequence = currentSequence + return members, err +} + +func prepareIAMMembersQuery() (sq.SelectBuilder, func(*sql.Rows) (*Members, error)) { + return sq.Select( + IAMMemberCreationDate.identifier(), + IAMMemberChangeDate.identifier(), + IAMMemberSequence.identifier(), + IAMMemberResourceOwner.identifier(), + IAMMemberUserID.identifier(), + IAMMemberRoles.identifier(), + LoginNameNameCol.identifier(), + HumanEmailCol.identifier(), + HumanFirstNameCol.identifier(), + HumanLastNameCol.identifier(), + HumanDisplayNameCol.identifier(), + MachineNameCol.identifier(), + HumanAvaterURLCol.identifier(), + countColumn.identifier(), + ).From(iamMemberTable.identifier()). + LeftJoin(join(HumanUserIDCol, IAMMemberUserID)). + LeftJoin(join(MachineUserIDCol, IAMMemberUserID)). + LeftJoin(join(LoginNameUserIDCol, IAMMemberUserID)). + Where( + sq.Eq{LoginNameIsPrimaryCol.identifier(): true}, + ).PlaceholderFormat(sq.Dollar), + func(rows *sql.Rows) (*Members, error) { + members := make([]*Member, 0) + var count uint64 + + for rows.Next() { + member := new(Member) + roles := pq.StringArray{} + + var ( + preferredLoginName = sql.NullString{} + email = sql.NullString{} + firstName = sql.NullString{} + lastName = sql.NullString{} + displayName = sql.NullString{} + machineName = sql.NullString{} + avatarURL = sql.NullString{} + ) + + err := rows.Scan( + &member.CreationDate, + &member.ChangeDate, + &member.Sequence, + &member.ResourceOwner, + &member.UserID, + &roles, + &preferredLoginName, + &email, + &firstName, + &lastName, + &displayName, + &machineName, + &avatarURL, + + &count, + ) + + if err != nil { + return nil, err + } + + member.Roles = roles + member.PreferredLoginName = preferredLoginName.String + member.Email = email.String + member.FirstName = firstName.String + member.LastName = lastName.String + member.AvatarURL = avatarURL.String + if displayName.Valid { + member.DisplayName = displayName.String + } else { + member.DisplayName = machineName.String + } + + members = append(members, member) + } + + if err := rows.Close(); err != nil { + return nil, errors.ThrowInternal(err, "QUERY-EqJFc", "Errors.Query.CloseRows") + } + + return &Members{ + Members: members, + SearchResponse: SearchResponse{ + Count: count, + }, + }, nil + } +} diff --git a/internal/query/iam_member_test.go b/internal/query/iam_member_test.go new file mode 100644 index 0000000000..c9d4860ac4 --- /dev/null +++ b/internal/query/iam_member_test.go @@ -0,0 +1,277 @@ +package query + +import ( + "database/sql" + "database/sql/driver" + "errors" + "fmt" + "regexp" + "testing" + + "github.com/lib/pq" +) + +var ( + iamMembersQuery = regexp.QuoteMeta("SELECT" + + " members.creation_date" + + ", members.change_date" + + ", members.sequence" + + ", members.resource_owner" + + ", members.user_id" + + ", members.roles" + + ", zitadel.projections.login_names.login_name" + + ", zitadel.projections.users_humans.email" + + ", zitadel.projections.users_humans.first_name" + + ", zitadel.projections.users_humans.last_name" + + ", zitadel.projections.users_humans.display_name" + + ", zitadel.projections.users_machines.name" + + ", zitadel.projections.users_humans.avater_key" + + ", COUNT(*) OVER () " + + "FROM zitadel.projections.iam_members as members " + + "LEFT JOIN zitadel.projections.users_humans " + + "ON members.user_id = zitadel.projections.users_humans.user_id " + + "LEFT JOIN zitadel.projections.users_machines " + + "ON members.user_id = zitadel.projections.users_machines.user_id " + + "LEFT JOIN zitadel.projections.login_names " + + "ON members.user_id = zitadel.projections.login_names.user_id " + + "WHERE zitadel.projections.login_names.is_primary = $1") + iamMembersColumns = []string{ + "creation_date", + "change_date", + "sequence", + "resource_owner", + "user_id", + "roles", + "login_name", + "email", + "first_name", + "last_name", + "display_name", + "name", + "avater_key", + "count", + } +) + +func Test_IAMMemberPrepares(t *testing.T) { + type want struct { + sqlExpectations sqlExpectation + err checkErr + } + tests := []struct { + name string + prepare interface{} + want want + object interface{} + }{ + { + name: "prepareIAMMembersQuery no result", + prepare: prepareIAMMembersQuery, + want: want{ + sqlExpectations: mockQueries( + iamMembersQuery, + nil, + nil, + ), + }, + object: &Members{ + Members: []*Member{}, + }, + }, + { + name: "prepareIAMMembersQuery human found", + prepare: prepareIAMMembersQuery, + want: want{ + sqlExpectations: mockQueries( + iamMembersQuery, + iamMembersColumns, + [][]driver.Value{ + { + testNow, + testNow, + uint64(20211206), + "ro", + "user-id", + pq.StringArray{"role-1", "role-2"}, + "gigi@caos-ag.zitadel.ch", + "gigi@caos.ch", + "first-name", + "last-name", + "display name", + nil, + nil, + }, + }, + ), + }, + object: &Members{ + SearchResponse: SearchResponse{ + Count: 1, + }, + Members: []*Member{ + { + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211206, + ResourceOwner: "ro", + UserID: "user-id", + Roles: []string{"role-1", "role-2"}, + PreferredLoginName: "gigi@caos-ag.zitadel.ch", + Email: "gigi@caos.ch", + FirstName: "first-name", + LastName: "last-name", + DisplayName: "display name", + AvatarURL: "", + }, + }, + }, + }, + { + name: "prepareIAMMembersQuery machine found", + prepare: prepareIAMMembersQuery, + want: want{ + sqlExpectations: mockQueries( + iamMembersQuery, + iamMembersColumns, + [][]driver.Value{ + { + testNow, + testNow, + uint64(20211206), + "ro", + "user-id", + pq.StringArray{"role-1", "role-2"}, + "machine@caos-ag.zitadel.ch", + nil, + nil, + nil, + nil, + "machine-name", + nil, + }, + }, + ), + }, + object: &Members{ + SearchResponse: SearchResponse{ + Count: 1, + }, + Members: []*Member{ + { + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211206, + ResourceOwner: "ro", + UserID: "user-id", + Roles: []string{"role-1", "role-2"}, + PreferredLoginName: "machine@caos-ag.zitadel.ch", + Email: "", + FirstName: "", + LastName: "", + DisplayName: "machine-name", + AvatarURL: "", + }, + }, + }, + }, + { + name: "prepareIAMMembersQuery multiple users", + prepare: prepareIAMMembersQuery, + want: want{ + sqlExpectations: mockQueries( + iamMembersQuery, + iamMembersColumns, + [][]driver.Value{ + { + testNow, + testNow, + uint64(20211206), + "ro", + "user-id-1", + pq.StringArray{"role-1", "role-2"}, + "gigi@caos-ag.zitadel.ch", + "gigi@caos.ch", + "first-name", + "last-name", + "display name", + nil, + nil, + }, + { + testNow, + testNow, + uint64(20211206), + "ro", + "user-id-2", + pq.StringArray{"role-1", "role-2"}, + "machine@caos-ag.zitadel.ch", + nil, + nil, + nil, + nil, + "machine-name", + nil, + }, + }, + ), + }, + object: &Members{ + SearchResponse: SearchResponse{ + Count: 2, + }, + Members: []*Member{ + { + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211206, + ResourceOwner: "ro", + UserID: "user-id-1", + Roles: []string{"role-1", "role-2"}, + PreferredLoginName: "gigi@caos-ag.zitadel.ch", + Email: "gigi@caos.ch", + FirstName: "first-name", + LastName: "last-name", + DisplayName: "display name", + AvatarURL: "", + }, + { + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211206, + ResourceOwner: "ro", + UserID: "user-id-2", + Roles: []string{"role-1", "role-2"}, + PreferredLoginName: "machine@caos-ag.zitadel.ch", + Email: "", + FirstName: "", + LastName: "", + DisplayName: "machine-name", + AvatarURL: "", + }, + }, + }, + }, + { + name: "prepareIAMMembersQuery sql err", + prepare: prepareIAMMembersQuery, + want: want{ + sqlExpectations: mockQueryErr( + iamMembersQuery, + sql.ErrConnDone, + ), + err: func(err error) (error, bool) { + if !errors.Is(err, sql.ErrConnDone) { + return fmt.Errorf("err should be sql.ErrConnDone got: %w", err), false + } + return nil, true + }, + }, + object: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assertPrepare(t, tt.prepare, tt.object, tt.want.sqlExpectations, tt.want.err) + }) + } +} diff --git a/internal/query/login_name.go b/internal/query/login_name.go new file mode 100644 index 0000000000..8eb7d0c62a --- /dev/null +++ b/internal/query/login_name.go @@ -0,0 +1,21 @@ +package query + +import "github.com/caos/zitadel/internal/query/projection" + +var ( + loginNameTable = table{ + name: projection.LoginNameProjectionTable, + } + LoginNameUserIDCol = Column{ + name: "user_id", + table: loginNameTable, + } + LoginNameNameCol = Column{ + name: projection.LoginNameCol, + table: loginNameTable, + } + LoginNameIsPrimaryCol = Column{ + name: projection.LoginNameDomainIsPrimaryCol, + table: loginNameTable, + } +) diff --git a/internal/query/member.go b/internal/query/member.go index 8d67cef860..5de18e57b0 100644 --- a/internal/query/member.go +++ b/internal/query/member.go @@ -4,10 +4,10 @@ import ( "context" "time" - sq "github.com/Masterminds/squirrel" - "github.com/caos/zitadel/internal/query/projection" "github.com/caos/zitadel/internal/telemetry/tracing" + + sq "github.com/Masterminds/squirrel" ) type MembersQuery struct { diff --git a/internal/query/org_member.go b/internal/query/org_member.go index 326ccf05ac..136262ca59 100644 --- a/internal/query/org_member.go +++ b/internal/query/org_member.go @@ -1,6 +1,15 @@ package query -import "github.com/caos/zitadel/internal/query/projection" +import ( + "context" + "database/sql" + + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/query/projection" + + sq "github.com/Masterminds/squirrel" + "github.com/lib/pq" +) var ( orgMemberTable = table{ @@ -36,3 +45,129 @@ var ( table: orgMemberTable, } ) + +type OrgMembersQuery struct { + MembersQuery + OrgID string +} + +func (q *OrgMembersQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder { + return q.MembersQuery. + toQuery(query). + Where(sq.Eq{OrgMemberOrgID.identifier(): q.OrgID}) +} + +func (q *Queries) OrgMembers(ctx context.Context, queries *OrgMembersQuery) (*Members, error) { + query, scan := prepareOrgMembersQuery() + stmt, args, err := queries.toQuery(query).ToSql() + if err != nil { + return nil, errors.ThrowInvalidArgument(err, "QUERY-PDAVB", "Errors.Query.InvalidRequest") + } + + currentSequence, err := q.latestSequence(ctx, orgsTable) + if err != nil { + return nil, err + } + + rows, err := q.client.QueryContext(ctx, stmt, args...) + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-5g4yV", "Errors.Internal") + } + members, err := scan(rows) + if err != nil { + return nil, err + } + members.LatestSequence = currentSequence + return members, err +} + +func prepareOrgMembersQuery() (sq.SelectBuilder, func(*sql.Rows) (*Members, error)) { + return sq.Select( + OrgMemberCreationDate.identifier(), + OrgMemberChangeDate.identifier(), + OrgMemberSequence.identifier(), + OrgMemberResourceOwner.identifier(), + OrgMemberUserID.identifier(), + OrgMemberRoles.identifier(), + LoginNameNameCol.identifier(), + HumanEmailCol.identifier(), + HumanFirstNameCol.identifier(), + HumanLastNameCol.identifier(), + HumanDisplayNameCol.identifier(), + MachineNameCol.identifier(), + HumanAvaterURLCol.identifier(), + countColumn.identifier(), + ).From(orgMemberTable.identifier()). + LeftJoin(join(HumanUserIDCol, OrgMemberUserID)). + LeftJoin(join(MachineUserIDCol, OrgMemberUserID)). + LeftJoin(join(LoginNameUserIDCol, OrgMemberUserID)). + Where( + sq.Eq{LoginNameIsPrimaryCol.identifier(): true}, + ).PlaceholderFormat(sq.Dollar), + func(rows *sql.Rows) (*Members, error) { + members := make([]*Member, 0) + var count uint64 + + for rows.Next() { + member := new(Member) + roles := pq.StringArray{} + + var ( + preferredLoginName = sql.NullString{} + email = sql.NullString{} + firstName = sql.NullString{} + lastName = sql.NullString{} + displayName = sql.NullString{} + machineName = sql.NullString{} + avatarURL = sql.NullString{} + ) + + err := rows.Scan( + &member.CreationDate, + &member.ChangeDate, + &member.Sequence, + &member.ResourceOwner, + &member.UserID, + &roles, + &preferredLoginName, + &email, + &firstName, + &lastName, + &displayName, + &machineName, + &avatarURL, + + &count, + ) + + if err != nil { + return nil, err + } + + member.Roles = roles + member.PreferredLoginName = preferredLoginName.String + member.Email = email.String + member.FirstName = firstName.String + member.LastName = lastName.String + member.AvatarURL = avatarURL.String + if displayName.Valid { + member.DisplayName = displayName.String + } else { + member.DisplayName = machineName.String + } + + members = append(members, member) + } + + if err := rows.Close(); err != nil { + return nil, errors.ThrowInternal(err, "QUERY-N34NV", "Errors.Query.CloseRows") + } + + return &Members{ + Members: members, + SearchResponse: SearchResponse{ + Count: count, + }, + }, nil + } +} diff --git a/internal/query/org_member_test.go b/internal/query/org_member_test.go new file mode 100644 index 0000000000..a57cedcbfb --- /dev/null +++ b/internal/query/org_member_test.go @@ -0,0 +1,277 @@ +package query + +import ( + "database/sql" + "database/sql/driver" + "errors" + "fmt" + "regexp" + "testing" + + "github.com/lib/pq" +) + +var ( + orgMembersQuery = regexp.QuoteMeta("SELECT" + + " members.creation_date" + + ", members.change_date" + + ", members.sequence" + + ", members.resource_owner" + + ", members.user_id" + + ", members.roles" + + ", zitadel.projections.login_names.login_name" + + ", zitadel.projections.users_humans.email" + + ", zitadel.projections.users_humans.first_name" + + ", zitadel.projections.users_humans.last_name" + + ", zitadel.projections.users_humans.display_name" + + ", zitadel.projections.users_machines.name" + + ", zitadel.projections.users_humans.avater_key" + + ", COUNT(*) OVER () " + + "FROM zitadel.projections.org_members as members " + + "LEFT JOIN zitadel.projections.users_humans " + + "ON members.user_id = zitadel.projections.users_humans.user_id " + + "LEFT JOIN zitadel.projections.users_machines " + + "ON members.user_id = zitadel.projections.users_machines.user_id " + + "LEFT JOIN zitadel.projections.login_names " + + "ON members.user_id = zitadel.projections.login_names.user_id " + + "WHERE zitadel.projections.login_names.is_primary = $1") + orgMembersColumns = []string{ + "creation_date", + "change_date", + "sequence", + "resource_owner", + "user_id", + "roles", + "login_name", + "email", + "first_name", + "last_name", + "display_name", + "name", + "avater_key", + "count", + } +) + +func Test_OrgMemberPrepares(t *testing.T) { + type want struct { + sqlExpectations sqlExpectation + err checkErr + } + tests := []struct { + name string + prepare interface{} + want want + object interface{} + }{ + { + name: "prepareOrgMembersQuery no result", + prepare: prepareOrgMembersQuery, + want: want{ + sqlExpectations: mockQueries( + orgMembersQuery, + nil, + nil, + ), + }, + object: &Members{ + Members: []*Member{}, + }, + }, + { + name: "prepareOrgMembersQuery human found", + prepare: prepareOrgMembersQuery, + want: want{ + sqlExpectations: mockQueries( + orgMembersQuery, + orgMembersColumns, + [][]driver.Value{ + { + testNow, + testNow, + uint64(20211206), + "ro", + "user-id", + pq.StringArray{"role-1", "role-2"}, + "gigi@caos-ag.zitadel.ch", + "gigi@caos.ch", + "first-name", + "last-name", + "display name", + nil, + nil, + }, + }, + ), + }, + object: &Members{ + SearchResponse: SearchResponse{ + Count: 1, + }, + Members: []*Member{ + { + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211206, + ResourceOwner: "ro", + UserID: "user-id", + Roles: []string{"role-1", "role-2"}, + PreferredLoginName: "gigi@caos-ag.zitadel.ch", + Email: "gigi@caos.ch", + FirstName: "first-name", + LastName: "last-name", + DisplayName: "display name", + AvatarURL: "", + }, + }, + }, + }, + { + name: "prepareOrgMembersQuery machine found", + prepare: prepareOrgMembersQuery, + want: want{ + sqlExpectations: mockQueries( + orgMembersQuery, + orgMembersColumns, + [][]driver.Value{ + { + testNow, + testNow, + uint64(20211206), + "ro", + "user-id", + pq.StringArray{"role-1", "role-2"}, + "machine@caos-ag.zitadel.ch", + nil, + nil, + nil, + nil, + "machine-name", + nil, + }, + }, + ), + }, + object: &Members{ + SearchResponse: SearchResponse{ + Count: 1, + }, + Members: []*Member{ + { + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211206, + ResourceOwner: "ro", + UserID: "user-id", + Roles: []string{"role-1", "role-2"}, + PreferredLoginName: "machine@caos-ag.zitadel.ch", + Email: "", + FirstName: "", + LastName: "", + DisplayName: "machine-name", + AvatarURL: "", + }, + }, + }, + }, + { + name: "prepareOrgMembersQuery multiple users", + prepare: prepareOrgMembersQuery, + want: want{ + sqlExpectations: mockQueries( + orgMembersQuery, + orgMembersColumns, + [][]driver.Value{ + { + testNow, + testNow, + uint64(20211206), + "ro", + "user-id-1", + pq.StringArray{"role-1", "role-2"}, + "gigi@caos-ag.zitadel.ch", + "gigi@caos.ch", + "first-name", + "last-name", + "display name", + nil, + nil, + }, + { + testNow, + testNow, + uint64(20211206), + "ro", + "user-id-2", + pq.StringArray{"role-1", "role-2"}, + "machine@caos-ag.zitadel.ch", + nil, + nil, + nil, + nil, + "machine-name", + nil, + }, + }, + ), + }, + object: &Members{ + SearchResponse: SearchResponse{ + Count: 2, + }, + Members: []*Member{ + { + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211206, + ResourceOwner: "ro", + UserID: "user-id-1", + Roles: []string{"role-1", "role-2"}, + PreferredLoginName: "gigi@caos-ag.zitadel.ch", + Email: "gigi@caos.ch", + FirstName: "first-name", + LastName: "last-name", + DisplayName: "display name", + AvatarURL: "", + }, + { + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211206, + ResourceOwner: "ro", + UserID: "user-id-2", + Roles: []string{"role-1", "role-2"}, + PreferredLoginName: "machine@caos-ag.zitadel.ch", + Email: "", + FirstName: "", + LastName: "", + DisplayName: "machine-name", + AvatarURL: "", + }, + }, + }, + }, + { + name: "prepareOrgMembersQuery sql err", + prepare: prepareOrgMembersQuery, + want: want{ + sqlExpectations: mockQueryErr( + orgMembersQuery, + sql.ErrConnDone, + ), + err: func(err error) (error, bool) { + if !errors.Is(err, sql.ErrConnDone) { + return fmt.Errorf("err should be sql.ErrConnDone got: %w", err), false + } + return nil, true + }, + }, + object: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assertPrepare(t, tt.prepare, tt.object, tt.want.sqlExpectations, tt.want.err) + }) + } +} diff --git a/internal/query/project_grant_member.go b/internal/query/project_grant_member.go index 4e213264f4..b3c03fdaf3 100644 --- a/internal/query/project_grant_member.go +++ b/internal/query/project_grant_member.go @@ -1,6 +1,14 @@ package query -import "github.com/caos/zitadel/internal/query/projection" +import ( + "context" + "database/sql" + + sq "github.com/Masterminds/squirrel" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/query/projection" + "github.com/lib/pq" +) var ( projectGrantMemberTable = table{ @@ -40,3 +48,132 @@ var ( table: projectGrantMemberTable, } ) + +type ProjectGrantMembersQuery struct { + MembersQuery + ProjectID, GrantID string +} + +func (q *ProjectGrantMembersQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder { + return q.MembersQuery. + toQuery(query). + Where(sq.Eq{ + ProjectGrantMemberProjectID.identifier(): q.ProjectID, + ProjectGrantMemberGrantID.identifier(): q.GrantID, + }) +} + +func (q *Queries) ProjectGrantMembers(ctx context.Context, queries *ProjectGrantMembersQuery) (*Members, error) { + query, scan := prepareProjectGrantMembersQuery() + stmt, args, err := queries.toQuery(query).ToSql() + if err != nil { + return nil, errors.ThrowInvalidArgument(err, "QUERY-USNwM", "Errors.Query.InvalidRequest") + } + + currentSequence, err := q.latestSequence(ctx, projectGrantMemberTable) + if err != nil { + return nil, err + } + + rows, err := q.client.QueryContext(ctx, stmt, args...) + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-Pdg1I", "Errors.Internal") + } + members, err := scan(rows) + if err != nil { + return nil, err + } + members.LatestSequence = currentSequence + return members, err +} + +func prepareProjectGrantMembersQuery() (sq.SelectBuilder, func(*sql.Rows) (*Members, error)) { + return sq.Select( + ProjectGrantMemberCreationDate.identifier(), + ProjectGrantMemberChangeDate.identifier(), + ProjectGrantMemberSequence.identifier(), + ProjectGrantMemberResourceOwner.identifier(), + ProjectGrantMemberUserID.identifier(), + ProjectGrantMemberRoles.identifier(), + LoginNameNameCol.identifier(), + HumanEmailCol.identifier(), + HumanFirstNameCol.identifier(), + HumanLastNameCol.identifier(), + HumanDisplayNameCol.identifier(), + MachineNameCol.identifier(), + HumanAvaterURLCol.identifier(), + countColumn.identifier(), + ).From(projectGrantMemberTable.identifier()). + LeftJoin(join(HumanUserIDCol, ProjectGrantMemberUserID)). + LeftJoin(join(MachineUserIDCol, ProjectGrantMemberUserID)). + LeftJoin(join(LoginNameUserIDCol, ProjectGrantMemberUserID)). + Where( + sq.Eq{LoginNameIsPrimaryCol.identifier(): true}, + ).PlaceholderFormat(sq.Dollar), + func(rows *sql.Rows) (*Members, error) { + members := make([]*Member, 0) + var count uint64 + + for rows.Next() { + member := new(Member) + roles := pq.StringArray{} + + var ( + preferredLoginName = sql.NullString{} + email = sql.NullString{} + firstName = sql.NullString{} + lastName = sql.NullString{} + displayName = sql.NullString{} + machineName = sql.NullString{} + avatarURL = sql.NullString{} + ) + + err := rows.Scan( + &member.CreationDate, + &member.ChangeDate, + &member.Sequence, + &member.ResourceOwner, + &member.UserID, + &roles, + &preferredLoginName, + &email, + &firstName, + &lastName, + &displayName, + &machineName, + &avatarURL, + + &count, + ) + + if err != nil { + return nil, err + } + + member.Roles = roles + member.PreferredLoginName = preferredLoginName.String + member.Email = email.String + member.FirstName = firstName.String + member.LastName = lastName.String + member.AvatarURL = avatarURL.String + if displayName.Valid { + member.DisplayName = displayName.String + } else { + member.DisplayName = machineName.String + } + + members = append(members, member) + } + + if err := rows.Close(); err != nil { + return nil, errors.ThrowInternal(err, "QUERY-EqJFc", "Errors.Query.CloseRows") + } + + return &Members{ + Members: members, + SearchResponse: SearchResponse{ + Count: count, + }, + }, nil + } +} diff --git a/internal/query/project_grant_member_test.go b/internal/query/project_grant_member_test.go new file mode 100644 index 0000000000..50b29b44a0 --- /dev/null +++ b/internal/query/project_grant_member_test.go @@ -0,0 +1,277 @@ +package query + +import ( + "database/sql" + "database/sql/driver" + "errors" + "fmt" + "regexp" + "testing" + + "github.com/lib/pq" +) + +var ( + projectGrantMembersQuery = regexp.QuoteMeta("SELECT" + + " members.creation_date" + + ", members.change_date" + + ", members.sequence" + + ", members.resource_owner" + + ", members.user_id" + + ", members.roles" + + ", zitadel.projections.login_names.login_name" + + ", zitadel.projections.users_humans.email" + + ", zitadel.projections.users_humans.first_name" + + ", zitadel.projections.users_humans.last_name" + + ", zitadel.projections.users_humans.display_name" + + ", zitadel.projections.users_machines.name" + + ", zitadel.projections.users_humans.avater_key" + + ", COUNT(*) OVER () " + + "FROM zitadel.projections.project_grant_members as members " + + "LEFT JOIN zitadel.projections.users_humans " + + "ON members.user_id = zitadel.projections.users_humans.user_id " + + "LEFT JOIN zitadel.projections.users_machines " + + "ON members.user_id = zitadel.projections.users_machines.user_id " + + "LEFT JOIN zitadel.projections.login_names " + + "ON members.user_id = zitadel.projections.login_names.user_id " + + "WHERE zitadel.projections.login_names.is_primary = $1") + projectGrantMembersColumns = []string{ + "creation_date", + "change_date", + "sequence", + "resource_owner", + "user_id", + "roles", + "login_name", + "email", + "first_name", + "last_name", + "display_name", + "name", + "avater_key", + "count", + } +) + +func Test_ProjectGrantMemberPrepares(t *testing.T) { + type want struct { + sqlExpectations sqlExpectation + err checkErr + } + tests := []struct { + name string + prepare interface{} + want want + object interface{} + }{ + { + name: "prepareProjectGrantMembersQuery no result", + prepare: prepareProjectGrantMembersQuery, + want: want{ + sqlExpectations: mockQueries( + projectGrantMembersQuery, + nil, + nil, + ), + }, + object: &Members{ + Members: []*Member{}, + }, + }, + { + name: "prepareProjectGrantMembersQuery human found", + prepare: prepareProjectGrantMembersQuery, + want: want{ + sqlExpectations: mockQueries( + projectGrantMembersQuery, + projectGrantMembersColumns, + [][]driver.Value{ + { + testNow, + testNow, + uint64(20211206), + "ro", + "user-id", + pq.StringArray{"role-1", "role-2"}, + "gigi@caos-ag.zitadel.ch", + "gigi@caos.ch", + "first-name", + "last-name", + "display name", + nil, + nil, + }, + }, + ), + }, + object: &Members{ + SearchResponse: SearchResponse{ + Count: 1, + }, + Members: []*Member{ + { + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211206, + ResourceOwner: "ro", + UserID: "user-id", + Roles: []string{"role-1", "role-2"}, + PreferredLoginName: "gigi@caos-ag.zitadel.ch", + Email: "gigi@caos.ch", + FirstName: "first-name", + LastName: "last-name", + DisplayName: "display name", + AvatarURL: "", + }, + }, + }, + }, + { + name: "prepareProjectGrantMembersQuery machine found", + prepare: prepareProjectGrantMembersQuery, + want: want{ + sqlExpectations: mockQueries( + projectGrantMembersQuery, + projectGrantMembersColumns, + [][]driver.Value{ + { + testNow, + testNow, + uint64(20211206), + "ro", + "user-id", + pq.StringArray{"role-1", "role-2"}, + "machine@caos-ag.zitadel.ch", + nil, + nil, + nil, + nil, + "machine-name", + nil, + }, + }, + ), + }, + object: &Members{ + SearchResponse: SearchResponse{ + Count: 1, + }, + Members: []*Member{ + { + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211206, + ResourceOwner: "ro", + UserID: "user-id", + Roles: []string{"role-1", "role-2"}, + PreferredLoginName: "machine@caos-ag.zitadel.ch", + Email: "", + FirstName: "", + LastName: "", + DisplayName: "machine-name", + AvatarURL: "", + }, + }, + }, + }, + { + name: "prepareProjectGrantMembersQuery multiple users", + prepare: prepareProjectGrantMembersQuery, + want: want{ + sqlExpectations: mockQueries( + projectGrantMembersQuery, + projectGrantMembersColumns, + [][]driver.Value{ + { + testNow, + testNow, + uint64(20211206), + "ro", + "user-id-1", + pq.StringArray{"role-1", "role-2"}, + "gigi@caos-ag.zitadel.ch", + "gigi@caos.ch", + "first-name", + "last-name", + "display name", + nil, + nil, + }, + { + testNow, + testNow, + uint64(20211206), + "ro", + "user-id-2", + pq.StringArray{"role-1", "role-2"}, + "machine@caos-ag.zitadel.ch", + nil, + nil, + nil, + nil, + "machine-name", + nil, + }, + }, + ), + }, + object: &Members{ + SearchResponse: SearchResponse{ + Count: 2, + }, + Members: []*Member{ + { + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211206, + ResourceOwner: "ro", + UserID: "user-id-1", + Roles: []string{"role-1", "role-2"}, + PreferredLoginName: "gigi@caos-ag.zitadel.ch", + Email: "gigi@caos.ch", + FirstName: "first-name", + LastName: "last-name", + DisplayName: "display name", + AvatarURL: "", + }, + { + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211206, + ResourceOwner: "ro", + UserID: "user-id-2", + Roles: []string{"role-1", "role-2"}, + PreferredLoginName: "machine@caos-ag.zitadel.ch", + Email: "", + FirstName: "", + LastName: "", + DisplayName: "machine-name", + AvatarURL: "", + }, + }, + }, + }, + { + name: "prepareProjectGrantMembersQuery sql err", + prepare: prepareProjectGrantMembersQuery, + want: want{ + sqlExpectations: mockQueryErr( + projectGrantMembersQuery, + sql.ErrConnDone, + ), + err: func(err error) (error, bool) { + if !errors.Is(err, sql.ErrConnDone) { + return fmt.Errorf("err should be sql.ErrConnDone got: %w", err), false + } + return nil, true + }, + }, + object: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assertPrepare(t, tt.prepare, tt.object, tt.want.sqlExpectations, tt.want.err) + }) + } +} diff --git a/internal/query/project_member.go b/internal/query/project_member.go index 97a1ead978..16ee79ab53 100644 --- a/internal/query/project_member.go +++ b/internal/query/project_member.go @@ -1,6 +1,14 @@ package query -import "github.com/caos/zitadel/internal/query/projection" +import ( + "context" + "database/sql" + + sq "github.com/Masterminds/squirrel" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/query/projection" + "github.com/lib/pq" +) var ( projectMemberTable = table{ @@ -36,3 +44,129 @@ var ( table: projectMemberTable, } ) + +type ProjectMembersQuery struct { + MembersQuery + ProjectID string +} + +func (q *ProjectMembersQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder { + return q.MembersQuery. + toQuery(query). + Where(sq.Eq{ProjectMemberProjectID.identifier(): q.ProjectID}) +} + +func (q *Queries) ProjectMembers(ctx context.Context, queries *ProjectMembersQuery) (*Members, error) { + query, scan := prepareProjectMembersQuery() + stmt, args, err := queries.toQuery(query).ToSql() + if err != nil { + return nil, errors.ThrowInvalidArgument(err, "QUERY-T8CuT", "Errors.Query.InvalidRequest") + } + + currentSequence, err := q.latestSequence(ctx, projectMemberTable) + if err != nil { + return nil, err + } + + rows, err := q.client.QueryContext(ctx, stmt, args...) + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-uh6pj", "Errors.Internal") + } + members, err := scan(rows) + if err != nil { + return nil, err + } + members.LatestSequence = currentSequence + return members, err +} + +func prepareProjectMembersQuery() (sq.SelectBuilder, func(*sql.Rows) (*Members, error)) { + return sq.Select( + ProjectMemberCreationDate.identifier(), + ProjectMemberChangeDate.identifier(), + ProjectMemberSequence.identifier(), + ProjectMemberResourceOwner.identifier(), + ProjectMemberUserID.identifier(), + ProjectMemberRoles.identifier(), + LoginNameNameCol.identifier(), + HumanEmailCol.identifier(), + HumanFirstNameCol.identifier(), + HumanLastNameCol.identifier(), + HumanDisplayNameCol.identifier(), + MachineNameCol.identifier(), + HumanAvaterURLCol.identifier(), + countColumn.identifier(), + ).From(projectMemberTable.identifier()). + LeftJoin(join(HumanUserIDCol, ProjectMemberUserID)). + LeftJoin(join(MachineUserIDCol, ProjectMemberUserID)). + LeftJoin(join(LoginNameUserIDCol, ProjectMemberUserID)). + Where( + sq.Eq{LoginNameIsPrimaryCol.identifier(): true}, + ).PlaceholderFormat(sq.Dollar), + func(rows *sql.Rows) (*Members, error) { + members := make([]*Member, 0) + var count uint64 + + for rows.Next() { + member := new(Member) + roles := pq.StringArray{} + + var ( + preferredLoginName = sql.NullString{} + email = sql.NullString{} + firstName = sql.NullString{} + lastName = sql.NullString{} + displayName = sql.NullString{} + machineName = sql.NullString{} + avatarURL = sql.NullString{} + ) + + err := rows.Scan( + &member.CreationDate, + &member.ChangeDate, + &member.Sequence, + &member.ResourceOwner, + &member.UserID, + &roles, + &preferredLoginName, + &email, + &firstName, + &lastName, + &displayName, + &machineName, + &avatarURL, + + &count, + ) + + if err != nil { + return nil, err + } + + member.Roles = roles + member.PreferredLoginName = preferredLoginName.String + member.Email = email.String + member.FirstName = firstName.String + member.LastName = lastName.String + member.AvatarURL = avatarURL.String + if displayName.Valid { + member.DisplayName = displayName.String + } else { + member.DisplayName = machineName.String + } + + members = append(members, member) + } + + if err := rows.Close(); err != nil { + return nil, errors.ThrowInternal(err, "QUERY-ZJ1Ii", "Errors.Query.CloseRows") + } + + return &Members{ + Members: members, + SearchResponse: SearchResponse{ + Count: count, + }, + }, nil + } +} diff --git a/internal/query/project_member_test.go b/internal/query/project_member_test.go new file mode 100644 index 0000000000..a4456d3af2 --- /dev/null +++ b/internal/query/project_member_test.go @@ -0,0 +1,277 @@ +package query + +import ( + "database/sql" + "database/sql/driver" + "errors" + "fmt" + "regexp" + "testing" + + "github.com/lib/pq" +) + +var ( + projectMembersQuery = regexp.QuoteMeta("SELECT" + + " members.creation_date" + + ", members.change_date" + + ", members.sequence" + + ", members.resource_owner" + + ", members.user_id" + + ", members.roles" + + ", zitadel.projections.login_names.login_name" + + ", zitadel.projections.users_humans.email" + + ", zitadel.projections.users_humans.first_name" + + ", zitadel.projections.users_humans.last_name" + + ", zitadel.projections.users_humans.display_name" + + ", zitadel.projections.users_machines.name" + + ", zitadel.projections.users_humans.avater_key" + + ", COUNT(*) OVER () " + + "FROM zitadel.projections.project_members as members " + + "LEFT JOIN zitadel.projections.users_humans " + + "ON members.user_id = zitadel.projections.users_humans.user_id " + + "LEFT JOIN zitadel.projections.users_machines " + + "ON members.user_id = zitadel.projections.users_machines.user_id " + + "LEFT JOIN zitadel.projections.login_names " + + "ON members.user_id = zitadel.projections.login_names.user_id " + + "WHERE zitadel.projections.login_names.is_primary = $1") + projectMembersColumns = []string{ + "creation_date", + "change_date", + "sequence", + "resource_owner", + "user_id", + "roles", + "login_name", + "email", + "first_name", + "last_name", + "display_name", + "name", + "avater_key", + "count", + } +) + +func Test_ProjectMemberPrepares(t *testing.T) { + type want struct { + sqlExpectations sqlExpectation + err checkErr + } + tests := []struct { + name string + prepare interface{} + want want + object interface{} + }{ + { + name: "prepareProjectMembersQuery no result", + prepare: prepareProjectMembersQuery, + want: want{ + sqlExpectations: mockQueries( + projectMembersQuery, + nil, + nil, + ), + }, + object: &Members{ + Members: []*Member{}, + }, + }, + { + name: "prepareProjectMembersQuery human found", + prepare: prepareProjectMembersQuery, + want: want{ + sqlExpectations: mockQueries( + projectMembersQuery, + projectMembersColumns, + [][]driver.Value{ + { + testNow, + testNow, + uint64(20211206), + "ro", + "user-id", + pq.StringArray{"role-1", "role-2"}, + "gigi@caos-ag.zitadel.ch", + "gigi@caos.ch", + "first-name", + "last-name", + "display name", + nil, + nil, + }, + }, + ), + }, + object: &Members{ + SearchResponse: SearchResponse{ + Count: 1, + }, + Members: []*Member{ + { + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211206, + ResourceOwner: "ro", + UserID: "user-id", + Roles: []string{"role-1", "role-2"}, + PreferredLoginName: "gigi@caos-ag.zitadel.ch", + Email: "gigi@caos.ch", + FirstName: "first-name", + LastName: "last-name", + DisplayName: "display name", + AvatarURL: "", + }, + }, + }, + }, + { + name: "prepareProjectMembersQuery machine found", + prepare: prepareProjectMembersQuery, + want: want{ + sqlExpectations: mockQueries( + projectMembersQuery, + projectMembersColumns, + [][]driver.Value{ + { + testNow, + testNow, + uint64(20211206), + "ro", + "user-id", + pq.StringArray{"role-1", "role-2"}, + "machine@caos-ag.zitadel.ch", + nil, + nil, + nil, + nil, + "machine-name", + nil, + }, + }, + ), + }, + object: &Members{ + SearchResponse: SearchResponse{ + Count: 1, + }, + Members: []*Member{ + { + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211206, + ResourceOwner: "ro", + UserID: "user-id", + Roles: []string{"role-1", "role-2"}, + PreferredLoginName: "machine@caos-ag.zitadel.ch", + Email: "", + FirstName: "", + LastName: "", + DisplayName: "machine-name", + AvatarURL: "", + }, + }, + }, + }, + { + name: "prepareProjectMembersQuery multiple users", + prepare: prepareProjectMembersQuery, + want: want{ + sqlExpectations: mockQueries( + projectMembersQuery, + projectMembersColumns, + [][]driver.Value{ + { + testNow, + testNow, + uint64(20211206), + "ro", + "user-id-1", + pq.StringArray{"role-1", "role-2"}, + "gigi@caos-ag.zitadel.ch", + "gigi@caos.ch", + "first-name", + "last-name", + "display name", + nil, + nil, + }, + { + testNow, + testNow, + uint64(20211206), + "ro", + "user-id-2", + pq.StringArray{"role-1", "role-2"}, + "machine@caos-ag.zitadel.ch", + nil, + nil, + nil, + nil, + "machine-name", + nil, + }, + }, + ), + }, + object: &Members{ + SearchResponse: SearchResponse{ + Count: 2, + }, + Members: []*Member{ + { + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211206, + ResourceOwner: "ro", + UserID: "user-id-1", + Roles: []string{"role-1", "role-2"}, + PreferredLoginName: "gigi@caos-ag.zitadel.ch", + Email: "gigi@caos.ch", + FirstName: "first-name", + LastName: "last-name", + DisplayName: "display name", + AvatarURL: "", + }, + { + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211206, + ResourceOwner: "ro", + UserID: "user-id-2", + Roles: []string{"role-1", "role-2"}, + PreferredLoginName: "machine@caos-ag.zitadel.ch", + Email: "", + FirstName: "", + LastName: "", + DisplayName: "machine-name", + AvatarURL: "", + }, + }, + }, + }, + { + name: "prepareProjectMembersQuery sql err", + prepare: prepareProjectMembersQuery, + want: want{ + sqlExpectations: mockQueryErr( + projectMembersQuery, + sql.ErrConnDone, + ), + err: func(err error) (error, bool) { + if !errors.Is(err, sql.ErrConnDone) { + return fmt.Errorf("err should be sql.ErrConnDone got: %w", err), false + } + return nil, true + }, + }, + object: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assertPrepare(t, tt.prepare, tt.object, tt.want.sqlExpectations, tt.want.err) + }) + } +} diff --git a/internal/query/projection/iam_member.go b/internal/query/projection/iam_member.go index d5064cd239..50808ff4b0 100644 --- a/internal/query/projection/iam_member.go +++ b/internal/query/projection/iam_member.go @@ -10,6 +10,7 @@ import ( "github.com/caos/zitadel/internal/eventstore/handler" "github.com/caos/zitadel/internal/eventstore/handler/crdb" "github.com/caos/zitadel/internal/repository/iam" + "github.com/caos/zitadel/internal/repository/user" ) type IAMMemberProjection struct { @@ -51,6 +52,15 @@ func (p *IAMMemberProjection) reducers() []handler.AggregateReducer { }, }, }, + { + Aggregate: user.AggregateType, + EventRedusers: []handler.EventReducer{ + { + Event: user.UserRemovedType, + Reduce: p.reduceUserRemoved, + }, + }, + }, } } @@ -75,7 +85,7 @@ func (p *IAMMemberProjection) reduceChanged(event eventstore.EventReader) (*hand logging.LogWithFields("HANDL-QsjwO", "seq", event.Sequence(), "expected", iam.MemberChangedEventType).Error("wrong event type") return nil, errors.ThrowInvalidArgument(nil, "HANDL-5WQcZ", "reduce.wrong.event.type") } - return reduceMemberChanged(e.MemberChangedEvent, withMemberCond(IAMMemberIAMIDCol, e.Aggregate().ID)) + return reduceMemberChanged(e.MemberChangedEvent) } func (p *IAMMemberProjection) reduceCascadeRemoved(event eventstore.EventReader) (*handler.Statement, error) { @@ -84,7 +94,7 @@ func (p *IAMMemberProjection) reduceCascadeRemoved(event eventstore.EventReader) logging.LogWithFields("HANDL-mOncs", "seq", event.Sequence(), "expected", iam.MemberCascadeRemovedEventType).Error("wrong event type") return nil, errors.ThrowInvalidArgument(nil, "HANDL-Dmdf2", "reduce.wrong.event.type") } - return reduceMemberCascadeRemoved(e.MemberCascadeRemovedEvent, withMemberCond(IAMMemberIAMIDCol, e.Aggregate().ID)) + return reduceMemberCascadeRemoved(e.MemberCascadeRemovedEvent) } func (p *IAMMemberProjection) reduceRemoved(event eventstore.EventReader) (*handler.Statement, error) { @@ -93,5 +103,14 @@ func (p *IAMMemberProjection) reduceRemoved(event eventstore.EventReader) (*hand logging.LogWithFields("HANDL-lW1Zv", "seq", event.Sequence(), "expected", iam.MemberRemovedEventType).Error("wrong event type") return nil, errors.ThrowInvalidArgument(nil, "HANDL-exVqy", "reduce.wrong.event.type") } - return reduceMemberRemoved(e.MemberRemovedEvent, withMemberCond(IAMMemberIAMIDCol, e.Aggregate().ID)) + return reduceMemberRemoved(e, withMemberCond(MemberUserIDCol, e.UserID)) +} + +func (p *IAMMemberProjection) reduceUserRemoved(event eventstore.EventReader) (*handler.Statement, error) { + e, ok := event.(*user.UserRemovedEvent) + if !ok { + logging.LogWithFields("HANDL-rBuvT", "seq", event.Sequence(), "expected", user.UserRemovedType).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-mkDHF", "reduce.wrong.event.type") + } + return reduceMemberRemoved(e, withMemberCond(MemberUserIDCol, e.Aggregate().ID)) } diff --git a/internal/query/projection/iam_member_test.go b/internal/query/projection/iam_member_test.go index 0fc0a571ff..38e8aa7919 100644 --- a/internal/query/projection/iam_member_test.go +++ b/internal/query/projection/iam_member_test.go @@ -8,6 +8,7 @@ import ( "github.com/caos/zitadel/internal/eventstore/handler" "github.com/caos/zitadel/internal/eventstore/repository" "github.com/caos/zitadel/internal/repository/iam" + "github.com/caos/zitadel/internal/repository/user" "github.com/lib/pq" ) @@ -78,13 +79,12 @@ func TestIAMMemberProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE zitadel.projections.iam_members SET (roles, change_date, sequence) = ($1, $2, $3) WHERE (user_id = $4) AND (iam_id = $5)", + expectedStmt: "UPDATE zitadel.projections.iam_members SET (roles, change_date, sequence) = ($1, $2, $3) WHERE (user_id = $4)", expectedArgs: []interface{}{ pq.StringArray{"role", "changed"}, anyArg{}, uint64(15), "user-id", - "agg-id", }, }, }, @@ -111,10 +111,9 @@ func TestIAMMemberProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM zitadel.projections.iam_members WHERE (user_id = $1) AND (iam_id = $2)", + expectedStmt: "DELETE FROM zitadel.projections.iam_members WHERE (user_id = $1)", expectedArgs: []interface{}{ "user-id", - "agg-id", }, }, }, @@ -141,9 +140,35 @@ func TestIAMMemberProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM zitadel.projections.iam_members WHERE (user_id = $1) AND (iam_id = $2)", + expectedStmt: "DELETE FROM zitadel.projections.iam_members WHERE (user_id = $1)", expectedArgs: []interface{}{ "user-id", + }, + }, + }, + }, + }, + }, + { + name: "user.UserRemoved", + args: args{ + event: getEvent(testEvent( + repository.EventType(user.UserRemovedType), + user.AggregateType, + []byte(`{}`), + ), user.UserRemovedEventMapper), + }, + reduce: (&IAMMemberProjection{}).reduceUserRemoved, + want: wantReduce{ + aggregateType: user.AggregateType, + sequence: 15, + previousSequence: 10, + projection: IAMMemberProjectionTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "DELETE FROM zitadel.projections.iam_members WHERE (user_id = $1)", + expectedArgs: []interface{}{ "agg-id", }, }, diff --git a/internal/query/projection/idp_login_policy_link.go b/internal/query/projection/idp_login_policy_link.go index 33503fb180..4cfbfd0984 100644 --- a/internal/query/projection/idp_login_policy_link.go +++ b/internal/query/projection/idp_login_policy_link.go @@ -47,6 +47,14 @@ func (p *IDPLoginPolicyLinkProjection) reducers() []handler.AggregateReducer { Event: org.LoginPolicyIDPProviderRemovedEventType, Reduce: p.reduceRemoved, }, + { + Event: org.OrgRemovedEventType, + Reduce: p.reduceOrgRemoved, + }, + { + Event: org.IDPConfigRemovedEventType, + Reduce: p.reduceIDPConfigRemoved, + }, }, }, { @@ -64,6 +72,10 @@ func (p *IDPLoginPolicyLinkProjection) reducers() []handler.AggregateReducer { Event: iam.LoginPolicyIDPProviderRemovedEventType, Reduce: p.reduceRemoved, }, + { + Event: iam.IDPConfigRemovedEventType, + Reduce: p.reduceIDPConfigRemoved, + }, }, }, } @@ -96,6 +108,7 @@ func (p *IDPLoginPolicyLinkProjection) reduceAdded(event eventstore.EventReader) logging.LogWithFields("HANDL-oce92", "seq", event.Sequence(), "expectedTypes", []eventstore.EventType{org.LoginPolicyIDPProviderAddedEventType, iam.LoginPolicyIDPProviderAddedEventType}).Error("wrong event type") return nil, errors.ThrowInvalidArgument(nil, "HANDL-Nlp55", "reduce.wrong.event.type") } + return crdb.NewCreateStatement(&idp, []handler.Column{ handler.NewCol(IDPLoginPolicyLinkIDPIDCol, idp.IDPConfigID), @@ -111,6 +124,7 @@ func (p *IDPLoginPolicyLinkProjection) reduceAdded(event eventstore.EventReader) func (p *IDPLoginPolicyLinkProjection) reduceRemoved(event eventstore.EventReader) (*handler.Statement, error) { var idp policy.IdentityProviderRemovedEvent + switch e := event.(type) { case *org.IdentityProviderRemovedEvent: idp = e.IdentityProviderRemovedEvent @@ -120,6 +134,7 @@ func (p *IDPLoginPolicyLinkProjection) reduceRemoved(event eventstore.EventReade logging.LogWithFields("HANDL-vAH3I", "seq", event.Sequence(), "expectedTypes", []eventstore.EventType{org.LoginPolicyIDPProviderRemovedEventType, iam.LoginPolicyIDPProviderRemovedEventType}).Error("wrong event type") return nil, errors.ThrowInvalidArgument(nil, "HANDL-tUMYY", "reduce.wrong.event.type") } + return crdb.NewDeleteStatement(&idp, []handler.Condition{ handler.NewCond(IDPLoginPolicyLinkIDPIDCol, idp.IDPConfigID), @@ -130,6 +145,7 @@ func (p *IDPLoginPolicyLinkProjection) reduceRemoved(event eventstore.EventReade func (p *IDPLoginPolicyLinkProjection) reduceCascadeRemoved(event eventstore.EventReader) (*handler.Statement, error) { var idp policy.IdentityProviderCascadeRemovedEvent + switch e := event.(type) { case *org.IdentityProviderCascadeRemovedEvent: idp = e.IdentityProviderCascadeRemovedEvent @@ -139,6 +155,7 @@ func (p *IDPLoginPolicyLinkProjection) reduceCascadeRemoved(event eventstore.Eve logging.LogWithFields("HANDL-7lZaf", "seq", event.Sequence(), "expectedTypes", []eventstore.EventType{org.LoginPolicyIDPProviderCascadeRemovedEventType, iam.LoginPolicyIDPProviderCascadeRemovedEventType}).Error("wrong event type") return nil, errors.ThrowInvalidArgument(nil, "HANDL-iCKSj", "reduce.wrong.event.type") } + return crdb.NewDeleteStatement(&idp, []handler.Condition{ handler.NewCond(IDPLoginPolicyLinkIDPIDCol, idp.IDPConfigID), @@ -146,3 +163,37 @@ func (p *IDPLoginPolicyLinkProjection) reduceCascadeRemoved(event eventstore.Eve }, ), nil } + +func (p *IDPLoginPolicyLinkProjection) reduceIDPConfigRemoved(event eventstore.EventReader) (*handler.Statement, error) { + var idpID string + + switch e := event.(type) { + case *org.IDPConfigRemovedEvent: + idpID = e.ConfigID + case *iam.IDPConfigRemovedEvent: + idpID = e.ConfigID + default: + logging.LogWithFields("HANDL-aJvob", "seq", event.Sequence(), "expectedTypes", []eventstore.EventType{org.IDPConfigRemovedEventType, iam.IDPConfigRemovedEventType}).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-u6tze", "reduce.wrong.event.type") + } + + return crdb.NewDeleteStatement(event, + []handler.Condition{ + handler.NewCond(IDPLoginPolicyLinkIDPIDCol, idpID), + handler.NewCond(IDPLoginPolicyLinkResourceOwnerCol, event.Aggregate().ResourceOwner), + }, + ), nil +} + +func (p *IDPLoginPolicyLinkProjection) reduceOrgRemoved(event eventstore.EventReader) (*handler.Statement, error) { + e, ok := event.(*org.OrgRemovedEvent) + if !ok { + logging.LogWithFields("HANDL-WTYC1", "seq", event.Sequence(), "expectedType", org.OrgRemovedEventType).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-QSoSe", "reduce.wrong.event.type") + } + return crdb.NewDeleteStatement(e, + []handler.Condition{ + handler.NewCond(IDPLoginPolicyLinkResourceOwnerCol, e.Aggregate().ID), + }, + ), nil +} diff --git a/internal/query/projection/idp_login_policy_link_test.go b/internal/query/projection/idp_login_policy_link_test.go index 036042cfb4..dafa5eeac2 100644 --- a/internal/query/projection/idp_login_policy_link_test.go +++ b/internal/query/projection/idp_login_policy_link_test.go @@ -218,6 +218,93 @@ func TestIDPLoginPolicyLinkProjection_reduces(t *testing.T) { }, }, }, + { + name: "reduceOrgRemoved", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.OrgRemovedEventType), + org.AggregateType, + []byte(`{}`), + ), org.OrgRemovedEventMapper), + }, + reduce: (&IDPLoginPolicyLinkProjection{}).reduceOrgRemoved, + want: wantReduce{ + aggregateType: org.AggregateType, + sequence: 15, + previousSequence: 10, + projection: IDPLoginPolicyLinkTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "DELETE FROM zitadel.projections.idp_login_policy_links WHERE (resource_owner = $1)", + expectedArgs: []interface{}{ + "agg-id", + }, + }, + }, + }, + }, + }, + { + name: "org.IDPConfigRemovedEvent", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.IDPConfigRemovedEventType), + org.AggregateType, + []byte(`{ + "idpConfigId": "idp-config-id" + }`), + ), org.IDPConfigRemovedEventMapper), + }, + reduce: (&IDPLoginPolicyLinkProjection{}).reduceIDPConfigRemoved, + want: wantReduce{ + aggregateType: org.AggregateType, + sequence: 15, + previousSequence: 10, + projection: IDPLoginPolicyLinkTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "DELETE FROM zitadel.projections.idp_login_policy_links WHERE (idp_id = $1) AND (resource_owner = $2)", + expectedArgs: []interface{}{ + "idp-config-id", + "ro-id", + }, + }, + }, + }, + }, + }, + { + name: "iam.IDPConfigRemovedEvent", + args: args{ + event: getEvent(testEvent( + repository.EventType(iam.IDPConfigRemovedEventType), + iam.AggregateType, + []byte(`{ + "idpConfigId": "idp-config-id" + }`), + ), iam.IDPConfigRemovedEventMapper), + }, + reduce: (&IDPLoginPolicyLinkProjection{}).reduceIDPConfigRemoved, + want: wantReduce{ + aggregateType: iam.AggregateType, + sequence: 15, + previousSequence: 10, + projection: IDPLoginPolicyLinkTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "DELETE FROM zitadel.projections.idp_login_policy_links WHERE (idp_id = $1) AND (resource_owner = $2)", + expectedArgs: []interface{}{ + "idp-config-id", + "ro-id", + }, + }, + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/query/projection/idp_user_link.go b/internal/query/projection/idp_user_link.go index c255c8bf96..87ac22ddd7 100644 --- a/internal/query/projection/idp_user_link.go +++ b/internal/query/projection/idp_user_link.go @@ -8,6 +8,8 @@ import ( "github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/eventstore/handler" "github.com/caos/zitadel/internal/eventstore/handler/crdb" + "github.com/caos/zitadel/internal/repository/iam" + "github.com/caos/zitadel/internal/repository/org" "github.com/caos/zitadel/internal/repository/user" ) @@ -40,6 +42,32 @@ func (p *IDPUserLinkProjection) reducers() []handler.AggregateReducer { Event: user.UserIDPLinkRemovedType, Reduce: p.reduceRemoved, }, + { + Event: user.UserRemovedType, + Reduce: p.reduceUserRemoved, + }, + }, + }, + { + Aggregate: org.AggregateType, + EventRedusers: []handler.EventReducer{ + { + Event: org.IDPConfigRemovedEventType, + Reduce: p.reduceIDPConfigRemoved, + }, + { + Event: org.OrgRemovedEventType, + Reduce: p.reduceOrgRemoved, + }, + }, + }, + { + Aggregate: iam.AggregateType, + EventRedusers: []handler.EventReducer{ + { + Event: iam.IDPConfigRemovedEventType, + Reduce: p.reduceIDPConfigRemoved, + }, }, }, } @@ -63,6 +91,7 @@ func (p *IDPUserLinkProjection) reduceAdded(event eventstore.EventReader) (*hand logging.LogWithFields("HANDL-v2qC3", "seq", event.Sequence(), "expectedType", user.UserIDPLinkAddedType).Error("wrong event type") return nil, errors.ThrowInvalidArgument(nil, "HANDL-DpmXq", "reduce.wrong.event.type") } + return crdb.NewCreateStatement(e, []handler.Column{ handler.NewCol(IDPUserLinkIDPIDCol, e.IDPConfigID), @@ -83,6 +112,7 @@ func (p *IDPUserLinkProjection) reduceRemoved(event eventstore.EventReader) (*ha logging.LogWithFields("HANDL-zX5m9", "seq", event.Sequence(), "expectedType", user.UserIDPLinkRemovedType).Error("wrong event type") return nil, errors.ThrowInvalidArgument(nil, "HANDL-AZmfJ", "reduce.wrong.event.type") } + return crdb.NewDeleteStatement(e, []handler.Condition{ handler.NewCond(IDPUserLinkIDPIDCol, e.IDPConfigID), @@ -98,6 +128,7 @@ func (p *IDPUserLinkProjection) reduceCascadeRemoved(event eventstore.EventReade logging.LogWithFields("HANDL-I0s2H", "seq", event.Sequence(), "expectedType", user.UserIDPLinkCascadeRemovedType).Error("wrong event type") return nil, errors.ThrowInvalidArgument(nil, "HANDL-jQpv9", "reduce.wrong.event.type") } + return crdb.NewDeleteStatement(e, []handler.Condition{ handler.NewCond(IDPUserLinkIDPIDCol, e.IDPConfigID), @@ -106,3 +137,52 @@ func (p *IDPUserLinkProjection) reduceCascadeRemoved(event eventstore.EventReade }, ), nil } + +func (p *IDPUserLinkProjection) reduceOrgRemoved(event eventstore.EventReader) (*handler.Statement, error) { + e, ok := event.(*org.OrgRemovedEvent) + if !ok { + logging.LogWithFields("HANDL-zX5m9", "seq", event.Sequence(), "expectedType", org.OrgRemovedEventType).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-AZmfJ", "reduce.wrong.event.type") + } + + return crdb.NewDeleteStatement(e, + []handler.Condition{ + handler.NewCond(IDPUserLinkResourceOwnerCol, e.Aggregate().ID), + }, + ), nil +} + +func (p *IDPUserLinkProjection) reduceUserRemoved(event eventstore.EventReader) (*handler.Statement, error) { + e, ok := event.(*user.UserRemovedEvent) + if !ok { + logging.LogWithFields("HANDL-yM6u6", "seq", event.Sequence(), "expectedType", user.UserRemovedType).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-uwlWE", "reduce.wrong.event.type") + } + + return crdb.NewDeleteStatement(e, + []handler.Condition{ + handler.NewCond(IDPUserLinkUserIDCol, e.Aggregate().ID), + }, + ), nil +} + +func (p *IDPUserLinkProjection) reduceIDPConfigRemoved(event eventstore.EventReader) (*handler.Statement, error) { + var idpID string + + switch e := event.(type) { + case *org.IDPConfigRemovedEvent: + idpID = e.ConfigID + case *iam.IDPConfigRemovedEvent: + idpID = e.ConfigID + default: + logging.LogWithFields("HANDL-7lZaf", "seq", event.Sequence(), "expectedTypes", []eventstore.EventType{org.IDPConfigRemovedEventType, iam.IDPConfigRemovedEventType}).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-iCKSj", "reduce.wrong.event.type") + } + + return crdb.NewDeleteStatement(event, + []handler.Condition{ + handler.NewCond(IDPUserLinkIDPIDCol, idpID), + handler.NewCond(IDPUserLinkResourceOwnerCol, event.Aggregate().ResourceOwner), + }, + ), nil +} diff --git a/internal/query/projection/idp_user_link_test.go b/internal/query/projection/idp_user_link_test.go index 97ef621e0d..b74aeb71f0 100644 --- a/internal/query/projection/idp_user_link_test.go +++ b/internal/query/projection/idp_user_link_test.go @@ -7,6 +7,8 @@ import ( "github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/eventstore/handler" "github.com/caos/zitadel/internal/eventstore/repository" + "github.com/caos/zitadel/internal/repository/iam" + "github.com/caos/zitadel/internal/repository/org" "github.com/caos/zitadel/internal/repository/user" ) @@ -122,6 +124,120 @@ func TestIDPUserLinkProjection_reduces(t *testing.T) { }, }, }, + { + name: "reduceOrgRemoved", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.OrgRemovedEventType), + org.AggregateType, + []byte(`{}`), + ), org.OrgRemovedEventMapper), + }, + reduce: (&IDPUserLinkProjection{}).reduceOrgRemoved, + want: wantReduce{ + aggregateType: org.AggregateType, + sequence: 15, + previousSequence: 10, + projection: IDPUserLinkTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "DELETE FROM zitadel.projections.idp_user_links WHERE (resource_owner = $1)", + expectedArgs: []interface{}{ + "agg-id", + }, + }, + }, + }, + }, + }, + { + name: "reduceUserRemoved", + args: args{ + event: getEvent(testEvent( + repository.EventType(user.UserRemovedType), + user.AggregateType, + []byte(`{}`), + ), user.UserRemovedEventMapper), + }, + reduce: (&IDPUserLinkProjection{}).reduceUserRemoved, + want: wantReduce{ + aggregateType: user.AggregateType, + sequence: 15, + previousSequence: 10, + projection: IDPUserLinkTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "DELETE FROM zitadel.projections.idp_user_links WHERE (user_id = $1)", + expectedArgs: []interface{}{ + "agg-id", + }, + }, + }, + }, + }, + }, + { + name: "org.IDPConfigRemovedEvent", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.IDPConfigRemovedEventType), + org.AggregateType, + []byte(`{ + "idpConfigId": "idp-config-id" + }`), + ), org.IDPConfigRemovedEventMapper), + }, + reduce: (&IDPUserLinkProjection{}).reduceIDPConfigRemoved, + want: wantReduce{ + aggregateType: org.AggregateType, + sequence: 15, + previousSequence: 10, + projection: IDPUserLinkTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "DELETE FROM zitadel.projections.idp_user_links WHERE (idp_id = $1) AND (resource_owner = $2)", + expectedArgs: []interface{}{ + "idp-config-id", + "ro-id", + }, + }, + }, + }, + }, + }, + { + name: "iam.IDPConfigRemovedEvent", + args: args{ + event: getEvent(testEvent( + repository.EventType(iam.IDPConfigRemovedEventType), + iam.AggregateType, + []byte(`{ + "idpConfigId": "idp-config-id" + }`), + ), iam.IDPConfigRemovedEventMapper), + }, + reduce: (&IDPUserLinkProjection{}).reduceIDPConfigRemoved, + want: wantReduce{ + aggregateType: iam.AggregateType, + sequence: 15, + previousSequence: 10, + projection: IDPUserLinkTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "DELETE FROM zitadel.projections.idp_user_links WHERE (idp_id = $1) AND (resource_owner = $2)", + expectedArgs: []interface{}{ + "idp-config-id", + "ro-id", + }, + }, + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/query/projection/login_name.go b/internal/query/projection/login_name.go index 87929684a8..3502919958 100644 --- a/internal/query/projection/login_name.go +++ b/internal/query/projection/login_name.go @@ -38,6 +38,10 @@ func (p *LoginNameProjection) reducers() []handler.AggregateReducer { { Aggregate: user.AggregateType, EventRedusers: []handler.EventReducer{ + { + Event: user.UserV1AddedType, + Reduce: p.reduceUserCreated, + }, { Event: user.HumanAddedType, Reduce: p.reduceUserCreated, @@ -46,6 +50,10 @@ func (p *LoginNameProjection) reducers() []handler.AggregateReducer { Event: user.HumanRegisteredType, Reduce: p.reduceUserCreated, }, + { + Event: user.UserV1RegisteredType, + Reduce: p.reduceUserCreated, + }, { Event: user.MachineAddedEventType, Reduce: p.reduceUserCreated, @@ -113,6 +121,8 @@ func (p *LoginNameProjection) reducers() []handler.AggregateReducer { } const ( + LoginNameCol = "login_name" + loginNameUserSuffix = "users" LoginNameUserIDCol = "id" LoginNameUserUserNameCol = "user_name" @@ -140,7 +150,7 @@ func (p *LoginNameProjection) reduceUserCreated(event eventstore.EventReader) (* case *user.MachineAddedEvent: userName = e.UserName default: - logging.LogWithFields("HANDL-tDUx3", "seq", event.Sequence(), "expectedTypes", []eventstore.EventType{user.HumanAddedType, user.HumanRegisteredType, user.MachineAddedEventType}).Error("wrong event type") + logging.LogWithFields("HANDL-tDUx3", "seq", event.Sequence(), "expectedTypes", []eventstore.EventType{user.UserV1AddedType, user.HumanAddedType, user.UserV1RegisteredType, user.HumanRegisteredType, user.MachineAddedEventType}).Error("wrong event type") return nil, errors.ThrowInvalidArgument(nil, "HANDL-ayo69", "reduce.wrong.event.type") } diff --git a/internal/query/projection/member.go b/internal/query/projection/member.go index cf6e1fabe5..7273ee6d3b 100644 --- a/internal/query/projection/member.go +++ b/internal/query/projection/member.go @@ -1,6 +1,7 @@ package projection import ( + "github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/eventstore/handler" "github.com/caos/zitadel/internal/eventstore/handler/crdb" "github.com/caos/zitadel/internal/repository/member" @@ -86,14 +87,13 @@ func reduceMemberCascadeRemoved(e member.MemberCascadeRemovedEvent, opts ...redu return crdb.NewDeleteStatement(&e, config.conds), nil } -func reduceMemberRemoved(e member.MemberRemovedEvent, opts ...reduceMemberOpt) (*handler.Statement, error) { +func reduceMemberRemoved(e eventstore.EventReader, opts ...reduceMemberOpt) (*handler.Statement, error) { config := reduceMemberConfig{ - conds: []handler.Condition{ - handler.NewCond(MemberUserIDCol, e.UserID), - }} + conds: []handler.Condition{}, + } for _, opt := range opts { config = opt(config) } - return crdb.NewDeleteStatement(&e, config.conds), nil + return crdb.NewDeleteStatement(e, config.conds), nil } diff --git a/internal/query/projection/org_member.go b/internal/query/projection/org_member.go index 131a1dbbf4..eceb40c446 100644 --- a/internal/query/projection/org_member.go +++ b/internal/query/projection/org_member.go @@ -10,6 +10,7 @@ import ( "github.com/caos/zitadel/internal/eventstore/handler" "github.com/caos/zitadel/internal/eventstore/handler/crdb" "github.com/caos/zitadel/internal/repository/org" + "github.com/caos/zitadel/internal/repository/user" ) type OrgMemberProjection struct { @@ -51,6 +52,24 @@ func (p *OrgMemberProjection) reducers() []handler.AggregateReducer { }, }, }, + { + Aggregate: user.AggregateType, + EventRedusers: []handler.EventReducer{ + { + Event: user.UserRemovedType, + Reduce: p.reduceUserRemoved, + }, + }, + }, + { + Aggregate: org.AggregateType, + EventRedusers: []handler.EventReducer{ + { + Event: org.OrgRemovedEventType, + Reduce: p.reduceOrgRemoved, + }, + }, + }, } } @@ -93,5 +112,30 @@ func (p *OrgMemberProjection) reduceRemoved(event eventstore.EventReader) (*hand logging.LogWithFields("HANDL-KPyxE", "seq", event.Sequence(), "expected", org.MemberRemovedEventType).Error("wrong event type") return nil, errors.ThrowInvalidArgument(nil, "HANDL-avatH", "reduce.wrong.event.type") } - return reduceMemberRemoved(e.MemberRemovedEvent, withMemberCond(OrgMemberOrgIDCol, e.Aggregate().ID)) + return reduceMemberRemoved(e, + withMemberCond(MemberUserIDCol, e.UserID), + withMemberCond(OrgMemberOrgIDCol, e.Aggregate().ID), + ) +} + +func (p *OrgMemberProjection) reduceUserRemoved(event eventstore.EventReader) (*handler.Statement, error) { + e, ok := event.(*user.UserRemovedEvent) + if !ok { + logging.LogWithFields("HANDL-f5pgn", "seq", event.Sequence(), "expected", user.UserRemovedType).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-eBMqH", "reduce.wrong.event.type") + } + return reduceMemberRemoved(e, withMemberCond(MemberUserIDCol, e.Aggregate().ID)) +} + +func (p *OrgMemberProjection) reduceOrgRemoved(event eventstore.EventReader) (*handler.Statement, error) { + //TODO: as soon as org deletion is implemented: + // Case: The user has resource owner A and an org has resource owner B + // if org B deleted it works + // if org A is deleted, the membership wouldn't be deleted + e, ok := event.(*org.OrgRemovedEvent) + if !ok { + logging.LogWithFields("HANDL-E5lDs", "seq", event.Sequence(), "expected", org.OrgRemovedEventType).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-jnGAV", "reduce.wrong.event.type") + } + return reduceMemberRemoved(e, withMemberCond(OrgMemberOrgIDCol, e.Aggregate().ID)) } diff --git a/internal/query/projection/org_member_test.go b/internal/query/projection/org_member_test.go index 20975393bb..a878efd049 100644 --- a/internal/query/projection/org_member_test.go +++ b/internal/query/projection/org_member_test.go @@ -8,6 +8,7 @@ import ( "github.com/caos/zitadel/internal/eventstore/handler" "github.com/caos/zitadel/internal/eventstore/repository" "github.com/caos/zitadel/internal/repository/org" + "github.com/caos/zitadel/internal/repository/user" "github.com/lib/pq" ) @@ -151,6 +152,60 @@ func TestOrgMemberProjection_reduces(t *testing.T) { }, }, }, + { + name: "user.UserRemovedEventType", + args: args{ + event: getEvent(testEvent( + repository.EventType(user.UserRemovedType), + user.AggregateType, + []byte(`{}`), + ), user.UserRemovedEventMapper), + }, + reduce: (&OrgMemberProjection{}).reduceUserRemoved, + want: wantReduce{ + aggregateType: user.AggregateType, + sequence: 15, + previousSequence: 10, + projection: OrgMemberProjectionTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "DELETE FROM zitadel.projections.org_members WHERE (user_id = $1)", + expectedArgs: []interface{}{ + "agg-id", + }, + }, + }, + }, + }, + }, + { + name: "org.OrgRemovedEventType", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.OrgRemovedEventType), + org.AggregateType, + []byte(`{}`), + ), org.OrgRemovedEventMapper), + }, + reduce: (&OrgMemberProjection{}).reduceOrgRemoved, + want: wantReduce{ + aggregateType: org.AggregateType, + sequence: 15, + previousSequence: 10, + projection: OrgMemberProjectionTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "DELETE FROM zitadel.projections.org_members WHERE (org_id = $1)", + expectedArgs: []interface{}{ + "agg-id", + }, + }, + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/query/projection/project_grant_member.go b/internal/query/projection/project_grant_member.go index eb5e4e9b68..e4bd77cfed 100644 --- a/internal/query/projection/project_grant_member.go +++ b/internal/query/projection/project_grant_member.go @@ -10,7 +10,9 @@ import ( "github.com/caos/zitadel/internal/eventstore/handler" "github.com/caos/zitadel/internal/eventstore/handler/crdb" "github.com/caos/zitadel/internal/repository/member" + "github.com/caos/zitadel/internal/repository/org" "github.com/caos/zitadel/internal/repository/project" + "github.com/caos/zitadel/internal/repository/user" ) type ProjectGrantMemberProjection struct { @@ -52,6 +54,42 @@ func (p *ProjectGrantMemberProjection) reducers() []handler.AggregateReducer { }, }, }, + { + Aggregate: user.AggregateType, + EventRedusers: []handler.EventReducer{ + { + Event: user.UserRemovedType, + Reduce: p.reduceUserRemoved, + }, + }, + }, + { + Aggregate: org.AggregateType, + EventRedusers: []handler.EventReducer{ + { + Event: org.OrgRemovedEventType, + Reduce: p.reduceOrgRemoved, + }, + }, + }, + { + Aggregate: project.AggregateType, + EventRedusers: []handler.EventReducer{ + { + Event: project.ProjectRemovedType, + Reduce: p.reduceProjectRemoved, + }, + }, + }, + { + Aggregate: project.AggregateType, + EventRedusers: []handler.EventReducer{ + { + Event: project.GrantRemovedType, + Reduce: p.reduceProjectGrantRemoved, + }, + }, + }, } } @@ -107,9 +145,52 @@ func (p *ProjectGrantMemberProjection) reduceRemoved(event eventstore.EventReade logging.LogWithFields("HANDL-6Z4dH", "seq", event.Sequence(), "expectedType", project.GrantMemberRemovedType).Error("wrong event type") return nil, errors.ThrowInvalidArgument(nil, "HANDL-MGNnA", "reduce.wrong.event.type") } - return reduceMemberRemoved( - *member.NewRemovedEvent(&e.BaseEvent, e.UserID), + return reduceMemberRemoved(e, + withMemberCond(MemberUserIDCol, e.UserID), withMemberCond(ProjectGrantMemberProjectIDCol, e.Aggregate().ID), withMemberCond(ProjectGrantMemberGrantIDCol, e.GrantID), ) } + +func (p *ProjectGrantMemberProjection) reduceUserRemoved(event eventstore.EventReader) (*handler.Statement, error) { + e, ok := event.(*user.UserRemovedEvent) + if !ok { + logging.LogWithFields("HANDL-UVMmT", "seq", event.Sequence(), "expected", user.UserRemovedType).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-rufJr", "reduce.wrong.event.type") + } + return reduceMemberRemoved(e, withMemberCond(MemberUserIDCol, e.Aggregate().ID)) +} + +func (p *ProjectGrantMemberProjection) reduceOrgRemoved(event eventstore.EventReader) (*handler.Statement, error) { + //TODO: as soon as org deletion is implemented: + // Case: The user has resource owner A and project has resource owner B + // if org B deleted it works + // if org A is deleted, the membership wouldn't be deleted + e, ok := event.(*org.OrgRemovedEvent) + if !ok { + logging.LogWithFields("HANDL-Sq9FV", "seq", event.Sequence(), "expected", org.OrgRemovedEventType).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-Zzp6o", "reduce.wrong.event.type") + } + return reduceMemberRemoved(e, withMemberCond(MemberResourceOwner, e.Aggregate().ID)) +} + +func (p *ProjectGrantMemberProjection) reduceProjectRemoved(event eventstore.EventReader) (*handler.Statement, error) { + e, ok := event.(*project.ProjectRemovedEvent) + if !ok { + logging.LogWithFields("HANDL-sGmCA", "seq", event.Sequence(), "expected", project.ProjectRemovedType).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-JLODy", "reduce.wrong.event.type") + } + return reduceMemberRemoved(e, withMemberCond(ProjectGrantMemberProjectIDCol, e.Aggregate().ID)) +} + +func (p *ProjectGrantMemberProjection) reduceProjectGrantRemoved(event eventstore.EventReader) (*handler.Statement, error) { + e, ok := event.(*project.GrantRemovedEvent) + if !ok { + logging.LogWithFields("HANDL-sHabO", "seq", event.Sequence(), "expected", project.GrantRemovedType).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-D1J9R", "reduce.wrong.event.type") + } + return reduceMemberRemoved(e, + withMemberCond(ProjectGrantMemberGrantIDCol, e.GrantID), + withMemberCond(ProjectGrantMemberProjectIDCol, e.Aggregate().ID), + ) +} diff --git a/internal/query/projection/project_grant_member_test.go b/internal/query/projection/project_grant_member_test.go index 46fc520392..46df25f6c5 100644 --- a/internal/query/projection/project_grant_member_test.go +++ b/internal/query/projection/project_grant_member_test.go @@ -7,7 +7,9 @@ import ( "github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/eventstore/handler" "github.com/caos/zitadel/internal/eventstore/repository" + "github.com/caos/zitadel/internal/repository/org" "github.com/caos/zitadel/internal/repository/project" + "github.com/caos/zitadel/internal/repository/user" "github.com/lib/pq" ) @@ -159,6 +161,115 @@ func TestProjectGrantMemberProjection_reduces(t *testing.T) { }, }, }, + { + name: "user.UserRemovedEventType", + args: args{ + event: getEvent(testEvent( + repository.EventType(user.UserRemovedType), + user.AggregateType, + []byte(`{}`), + ), user.UserRemovedEventMapper), + }, + reduce: (&ProjectGrantMemberProjection{}).reduceUserRemoved, + want: wantReduce{ + aggregateType: user.AggregateType, + sequence: 15, + previousSequence: 10, + projection: ProjectGrantMemberProjectionTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "DELETE FROM zitadel.projections.project_grant_members WHERE (user_id = $1)", + expectedArgs: []interface{}{ + "agg-id", + }, + }, + }, + }, + }, + }, + { + name: "org.OrgRemovedEventType", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.OrgRemovedEventType), + org.AggregateType, + []byte(`{}`), + ), org.OrgRemovedEventMapper), + }, + reduce: (&ProjectGrantMemberProjection{}).reduceOrgRemoved, + want: wantReduce{ + aggregateType: org.AggregateType, + sequence: 15, + previousSequence: 10, + projection: ProjectGrantMemberProjectionTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "DELETE FROM zitadel.projections.project_grant_members WHERE (resource_owner = $1)", + expectedArgs: []interface{}{ + "agg-id", + }, + }, + }, + }, + }, + }, + { + name: "project.ProjectRemovedEventType", + args: args{ + event: getEvent(testEvent( + repository.EventType(project.ProjectRemovedType), + project.AggregateType, + []byte(`{}`), + ), project.ProjectRemovedEventMapper), + }, + reduce: (&ProjectGrantMemberProjection{}).reduceProjectRemoved, + want: wantReduce{ + aggregateType: project.AggregateType, + sequence: 15, + previousSequence: 10, + projection: ProjectGrantMemberProjectionTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "DELETE FROM zitadel.projections.project_grant_members WHERE (project_id = $1)", + expectedArgs: []interface{}{ + "agg-id", + }, + }, + }, + }, + }, + }, + { + name: "project.GrantRemovedEventType", + args: args{ + event: getEvent(testEvent( + repository.EventType(project.GrantRemovedType), + project.AggregateType, + []byte(`{"grantId": "grant-id"}`), + ), project.GrantRemovedEventMapper), + }, + reduce: (&ProjectGrantMemberProjection{}).reduceProjectGrantRemoved, + want: wantReduce{ + aggregateType: project.AggregateType, + sequence: 15, + previousSequence: 10, + projection: ProjectGrantMemberProjectionTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "DELETE FROM zitadel.projections.project_grant_members WHERE (grant_id = $1) AND (project_id = $2)", + expectedArgs: []interface{}{ + "grant-id", + "agg-id", + }, + }, + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/query/projection/project_member.go b/internal/query/projection/project_member.go index 62b303fbf6..38cdc3f2a7 100644 --- a/internal/query/projection/project_member.go +++ b/internal/query/projection/project_member.go @@ -10,7 +10,9 @@ import ( "github.com/caos/zitadel/internal/eventstore/handler" "github.com/caos/zitadel/internal/eventstore/handler/crdb" "github.com/caos/zitadel/internal/repository/member" + "github.com/caos/zitadel/internal/repository/org" "github.com/caos/zitadel/internal/repository/project" + "github.com/caos/zitadel/internal/repository/user" ) type ProjectMemberProjection struct { @@ -52,6 +54,33 @@ func (p *ProjectMemberProjection) reducers() []handler.AggregateReducer { }, }, }, + { + Aggregate: user.AggregateType, + EventRedusers: []handler.EventReducer{ + { + Event: user.UserRemovedType, + Reduce: p.reduceUserRemoved, + }, + }, + }, + { + Aggregate: org.AggregateType, + EventRedusers: []handler.EventReducer{ + { + Event: org.OrgRemovedEventType, + Reduce: p.reduceOrgRemoved, + }, + }, + }, + { + Aggregate: project.AggregateType, + EventRedusers: []handler.EventReducer{ + { + Event: project.ProjectRemovedType, + Reduce: p.reduceProjectRemoved, + }, + }, + }, } } @@ -103,8 +132,39 @@ func (p *ProjectMemberProjection) reduceRemoved(event eventstore.EventReader) (* logging.LogWithFields("HANDL-X0yvM", "seq", event.Sequence(), "expectedType", project.MemberRemovedType).Error("wrong event type") return nil, errors.ThrowInvalidArgument(nil, "HANDL-eJZPh", "reduce.wrong.event.type") } - return reduceMemberRemoved( - *member.NewRemovedEvent(&e.BaseEvent, e.UserID), + return reduceMemberRemoved(e, + withMemberCond(MemberUserIDCol, e.UserID), withMemberCond(ProjectMemberProjectIDCol, e.Aggregate().ID), ) } + +func (p *ProjectMemberProjection) reduceUserRemoved(event eventstore.EventReader) (*handler.Statement, error) { + e, ok := event.(*user.UserRemovedEvent) + if !ok { + logging.LogWithFields("HANDL-g8eWd", "seq", event.Sequence(), "expected", user.UserRemovedType).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-aYA60", "reduce.wrong.event.type") + } + return reduceMemberRemoved(e, withMemberCond(MemberUserIDCol, e.Aggregate().ID)) +} + +func (p *ProjectMemberProjection) reduceOrgRemoved(event eventstore.EventReader) (*handler.Statement, error) { + //TODO: as soon as org deletion is implemented: + // Case: The user has resource owner A and project has resource owner B + // if org B deleted it works + // if org A is deleted, the membership wouldn't be deleted + e, ok := event.(*org.OrgRemovedEvent) + if !ok { + logging.LogWithFields("HANDL-q7H8D", "seq", event.Sequence(), "expected", org.OrgRemovedEventType).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-NGUEL", "reduce.wrong.event.type") + } + return reduceMemberRemoved(e, withMemberCond(MemberResourceOwner, e.Aggregate().ID)) +} + +func (p *ProjectMemberProjection) reduceProjectRemoved(event eventstore.EventReader) (*handler.Statement, error) { + e, ok := event.(*project.ProjectRemovedEvent) + if !ok { + logging.LogWithFields("HANDL-q7H8D", "seq", event.Sequence(), "expected", project.ProjectRemovedType).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-NGUEL", "reduce.wrong.event.type") + } + return reduceMemberRemoved(e, withMemberCond(ProjectMemberProjectIDCol, e.Aggregate().ID)) +} diff --git a/internal/query/projection/project_member_test.go b/internal/query/projection/project_member_test.go index 89c87d670a..ec910d970c 100644 --- a/internal/query/projection/project_member_test.go +++ b/internal/query/projection/project_member_test.go @@ -7,7 +7,9 @@ import ( "github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/eventstore/handler" "github.com/caos/zitadel/internal/eventstore/repository" + "github.com/caos/zitadel/internal/repository/org" "github.com/caos/zitadel/internal/repository/project" + "github.com/caos/zitadel/internal/repository/user" "github.com/lib/pq" ) @@ -151,6 +153,87 @@ func TestProjectMemberProjection_reduces(t *testing.T) { }, }, }, + { + name: "user.UserRemovedEventType", + args: args{ + event: getEvent(testEvent( + repository.EventType(user.UserRemovedType), + user.AggregateType, + []byte(`{}`), + ), user.UserRemovedEventMapper), + }, + reduce: (&ProjectMemberProjection{}).reduceUserRemoved, + want: wantReduce{ + aggregateType: user.AggregateType, + sequence: 15, + previousSequence: 10, + projection: ProjectMemberProjectionTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "DELETE FROM zitadel.projections.project_members WHERE (user_id = $1)", + expectedArgs: []interface{}{ + "agg-id", + }, + }, + }, + }, + }, + }, + { + name: "org.OrgRemovedEventType", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.OrgRemovedEventType), + org.AggregateType, + []byte(`{}`), + ), org.OrgRemovedEventMapper), + }, + reduce: (&ProjectMemberProjection{}).reduceOrgRemoved, + want: wantReduce{ + aggregateType: org.AggregateType, + sequence: 15, + previousSequence: 10, + projection: ProjectMemberProjectionTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "DELETE FROM zitadel.projections.project_members WHERE (resource_owner = $1)", + expectedArgs: []interface{}{ + "agg-id", + }, + }, + }, + }, + }, + }, + { + name: "project.ProjectRemovedEventType", + args: args{ + event: getEvent(testEvent( + repository.EventType(project.ProjectRemovedType), + project.AggregateType, + []byte(`{}`), + ), project.ProjectRemovedEventMapper), + }, + reduce: (&ProjectMemberProjection{}).reduceProjectRemoved, + want: wantReduce{ + aggregateType: project.AggregateType, + sequence: 15, + previousSequence: 10, + projection: ProjectMemberProjectionTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "DELETE FROM zitadel.projections.project_members WHERE (project_id = $1)", + expectedArgs: []interface{}{ + "agg-id", + }, + }, + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/query/user_membership.go b/internal/query/user_membership.go index 5b65f92cd2..bdbecaed4e 100644 --- a/internal/query/user_membership.go +++ b/internal/query/user_membership.go @@ -23,7 +23,6 @@ type Membership struct { ChangeDate time.Time Sequence uint64 ResourceOwner string - DisplayName string Org *OrgMembership IAM *IAMMembership @@ -33,19 +32,23 @@ type Membership struct { type OrgMembership struct { OrgID string + Name string } type IAMMembership struct { IAMID string + Name string } type ProjectMembership struct { ProjectID string + Name string } type ProjectGrantMembership struct { - ProjectID string - GrantID string + ProjectID string + ProjectName string + GrantID string } type MembershipSearchQuery struct { @@ -177,12 +180,12 @@ func prepareMembershipsQuery() (sq.SelectBuilder, func(*sql.Rows) (*Memberships, membershipIAMID.identifier(), membershipProjectID.identifier(), membershipGrantID.identifier(), - HumanDisplayNameCol.identifier(), - MachineNameCol.identifier(), + ProjectColumnName.identifier(), + OrgColumnName.identifier(), countColumn.identifier(), ).From(membershipFrom). - LeftJoin(join(HumanUserIDCol, membershipUserID)). - LeftJoin(join(MachineUserIDCol, membershipUserID)). + LeftJoin(join(ProjectColumnID, membershipProjectID)). + LeftJoin(join(OrgColumnID, membershipOrgID)). PlaceholderFormat(sq.Dollar), func(rows *sql.Rows) (*Memberships, error) { memberships := make([]*Membership, 0) @@ -196,8 +199,8 @@ func prepareMembershipsQuery() (sq.SelectBuilder, func(*sql.Rows) (*Memberships, projectID = sql.NullString{} grantID = sql.NullString{} roles = pq.StringArray{} - displayName = sql.NullString{} - machineName = sql.NullString{} + projectName = sql.NullString{} + orgName = sql.NullString{} ) err := rows.Scan( @@ -211,8 +214,8 @@ func prepareMembershipsQuery() (sq.SelectBuilder, func(*sql.Rows) (*Memberships, &iamID, &projectID, &grantID, - &displayName, - &machineName, + &projectName, + &orgName, &count, ) @@ -222,28 +225,26 @@ func prepareMembershipsQuery() (sq.SelectBuilder, func(*sql.Rows) (*Memberships, membership.Roles = roles - if displayName.Valid { - membership.DisplayName = displayName.String - } else if machineName.Valid { - membership.DisplayName = machineName.String - } - if orgID.Valid { membership.Org = &OrgMembership{ OrgID: orgID.String, + Name: orgName.String, } } else if iamID.Valid { membership.IAM = &IAMMembership{ IAMID: iamID.String, + Name: iamID.String, } } else if projectID.Valid && grantID.Valid { membership.ProjectGrant = &ProjectGrantMembership{ - ProjectID: projectID.String, - GrantID: grantID.String, + ProjectID: projectID.String, + ProjectName: projectName.String, + GrantID: grantID.String, } } else if projectID.Valid { membership.Project = &ProjectMembership{ ProjectID: projectID.String, + Name: projectName.String, } } diff --git a/internal/query/user_membership_test.go b/internal/query/user_membership_test.go index 4a81050284..bdfc70e99d 100644 --- a/internal/query/user_membership_test.go +++ b/internal/query/user_membership_test.go @@ -23,8 +23,8 @@ var ( ", memberships.iam_id" + ", memberships.project_id" + ", memberships.grant_id" + - ", zitadel.projections.users_humans.display_name" + - ", zitadel.projections.users_machines.name" + + ", zitadel.projections.projects.name" + + ", zitadel.projections.orgs.name" + ", COUNT(*) OVER ()" + " FROM (" + "SELECT members.user_id" + @@ -75,8 +75,8 @@ var ( ", members.grant_id" + " FROM zitadel.projections.project_grant_members as members" + ") AS memberships" + - " LEFT JOIN zitadel.projections.users_humans ON memberships.user_id = zitadel.projections.users_humans.user_id" + - " LEFT JOIN zitadel.projections.users_machines ON memberships.user_id = zitadel.projections.users_machines.user_id") + " LEFT JOIN zitadel.projections.projects ON memberships.project_id = zitadel.projections.projects.id" + + " LEFT JOIN zitadel.projections.orgs ON memberships.org_id = zitadel.projections.orgs.id") membershipCols = []string{ "user_id", "roles", @@ -88,8 +88,8 @@ var ( "iam_id", "project_id", "grant_id", - "display_name", - "name", + "name", //project name + "name", //org name "count", } ) @@ -118,50 +118,7 @@ func Test_MembershipPrepares(t *testing.T) { object: &Memberships{Memberships: []*Membership{}}, }, { - name: "prepareMembershipsQuery one org member human", - prepare: prepareMembershipsQuery, - want: want{ - sqlExpectations: mockQueries( - membershipsStmt, - membershipCols, - [][]driver.Value{ - { - "user-id", - pq.StringArray{"role1", "role2"}, - testNow, - testNow, - uint64(20211202), - "ro", - "org-id", - nil, - nil, - nil, - "display name", - nil, - }, - }, - ), - }, - object: &Memberships{ - SearchResponse: SearchResponse{ - Count: 1, - }, - Memberships: []*Membership{ - { - UserID: "user-id", - Roles: []string{"role1", "role2"}, - CreationDate: testNow, - ChangeDate: testNow, - Sequence: 20211202, - ResourceOwner: "ro", - Org: &OrgMembership{OrgID: "org-id"}, - DisplayName: "display name", - }, - }, - }, - }, - { - name: "prepareMembershipsQuery one org member machine", + name: "prepareMembershipsQuery one org member", prepare: prepareMembershipsQuery, want: want{ sqlExpectations: mockQueries( @@ -180,7 +137,7 @@ func Test_MembershipPrepares(t *testing.T) { nil, nil, nil, - "machine-name", + "org-name", }, }, ), @@ -197,57 +154,13 @@ func Test_MembershipPrepares(t *testing.T) { ChangeDate: testNow, Sequence: 20211202, ResourceOwner: "ro", - Org: &OrgMembership{OrgID: "org-id"}, - DisplayName: "machine-name", + Org: &OrgMembership{OrgID: "org-id", Name: "org-name"}, }, }, }, }, { - name: "prepareMembershipsQuery one iam member human", - prepare: prepareMembershipsQuery, - want: want{ - sqlExpectations: mockQueries( - membershipsStmt, - membershipCols, - [][]driver.Value{ - { - "user-id", - pq.StringArray{"role1", "role2"}, - testNow, - testNow, - uint64(20211202), - "ro", - nil, - "iam-id", - nil, - nil, - "display name", - nil, - }, - }, - ), - }, - object: &Memberships{ - SearchResponse: SearchResponse{ - Count: 1, - }, - Memberships: []*Membership{ - { - UserID: "user-id", - Roles: []string{"role1", "role2"}, - CreationDate: testNow, - ChangeDate: testNow, - Sequence: 20211202, - ResourceOwner: "ro", - IAM: &IAMMembership{IAMID: "iam-id"}, - DisplayName: "display name", - }, - }, - }, - }, - { - name: "prepareMembershipsQuery one iam member machine", + name: "prepareMembershipsQuery one iam member", prepare: prepareMembershipsQuery, want: want{ sqlExpectations: mockQueries( @@ -266,7 +179,7 @@ func Test_MembershipPrepares(t *testing.T) { nil, nil, nil, - "machine-name", + nil, }, }, ), @@ -283,14 +196,13 @@ func Test_MembershipPrepares(t *testing.T) { ChangeDate: testNow, Sequence: 20211202, ResourceOwner: "ro", - IAM: &IAMMembership{IAMID: "iam-id"}, - DisplayName: "machine-name", + IAM: &IAMMembership{IAMID: "iam-id", Name: "iam-id"}, }, }, }, }, { - name: "prepareMembershipsQuery one project member human", + name: "prepareMembershipsQuery one project member", prepare: prepareMembershipsQuery, want: want{ sqlExpectations: mockQueries( @@ -308,7 +220,7 @@ func Test_MembershipPrepares(t *testing.T) { nil, "project-id", nil, - "display name", + "project-name", nil, }, }, @@ -326,57 +238,13 @@ func Test_MembershipPrepares(t *testing.T) { ChangeDate: testNow, Sequence: 20211202, ResourceOwner: "ro", - Project: &ProjectMembership{ProjectID: "project-id"}, - DisplayName: "display name", + Project: &ProjectMembership{ProjectID: "project-id", Name: "project-name"}, }, }, }, }, { - name: "prepareMembershipsQuery one project member machine", - prepare: prepareMembershipsQuery, - want: want{ - sqlExpectations: mockQueries( - membershipsStmt, - membershipCols, - [][]driver.Value{ - { - "user-id", - pq.StringArray{"role1", "role2"}, - testNow, - testNow, - uint64(20211202), - "ro", - nil, - nil, - "project-id", - nil, - nil, - "machine-name", - }, - }, - ), - }, - object: &Memberships{ - SearchResponse: SearchResponse{ - Count: 1, - }, - Memberships: []*Membership{ - { - UserID: "user-id", - Roles: []string{"role1", "role2"}, - CreationDate: testNow, - ChangeDate: testNow, - Sequence: 20211202, - ResourceOwner: "ro", - Project: &ProjectMembership{ProjectID: "project-id"}, - DisplayName: "machine-name", - }, - }, - }, - }, - { - name: "prepareMembershipsQuery one project grant member human", + name: "prepareMembershipsQuery one project grant member", prepare: prepareMembershipsQuery, want: want{ sqlExpectations: mockQueries( @@ -394,7 +262,7 @@ func Test_MembershipPrepares(t *testing.T) { nil, "project-id", "grant-id", - "display name", + "project-name", nil, }, }, @@ -413,56 +281,10 @@ func Test_MembershipPrepares(t *testing.T) { Sequence: 20211202, ResourceOwner: "ro", ProjectGrant: &ProjectGrantMembership{ - GrantID: "grant-id", - ProjectID: "project-id", + GrantID: "grant-id", + ProjectID: "project-id", + ProjectName: "project-name", }, - DisplayName: "display name", - }, - }, - }, - }, - { - name: "prepareMembershipsQuery one project grant member machine", - prepare: prepareMembershipsQuery, - want: want{ - sqlExpectations: mockQueries( - membershipsStmt, - membershipCols, - [][]driver.Value{ - { - "user-id", - pq.StringArray{"role1", "role2"}, - testNow, - testNow, - uint64(20211202), - "ro", - nil, - nil, - "project-id", - "grant-id", - nil, - "machine-name", - }, - }, - ), - }, - object: &Memberships{ - SearchResponse: SearchResponse{ - Count: 1, - }, - Memberships: []*Membership{ - { - UserID: "user-id", - Roles: []string{"role1", "role2"}, - CreationDate: testNow, - ChangeDate: testNow, - Sequence: 20211202, - ResourceOwner: "ro", - ProjectGrant: &ProjectGrantMembership{ - GrantID: "grant-id", - ProjectID: "project-id", - }, - DisplayName: "machine-name", }, }, }, @@ -486,8 +308,8 @@ func Test_MembershipPrepares(t *testing.T) { nil, nil, nil, - "display name", nil, + "org-name", }, { "user-id", @@ -500,7 +322,7 @@ func Test_MembershipPrepares(t *testing.T) { "iam-id", nil, nil, - "display name", + nil, nil, }, { @@ -514,7 +336,7 @@ func Test_MembershipPrepares(t *testing.T) { nil, "project-id", nil, - "display name", + "project-name", nil, }, { @@ -528,7 +350,7 @@ func Test_MembershipPrepares(t *testing.T) { nil, "project-id", "grant-id", - "display name", + "project-name", nil, }, }, @@ -546,8 +368,7 @@ func Test_MembershipPrepares(t *testing.T) { ChangeDate: testNow, Sequence: 20211202, ResourceOwner: "ro", - Org: &OrgMembership{OrgID: "org-id"}, - DisplayName: "display name", + Org: &OrgMembership{OrgID: "org-id", Name: "org-name"}, }, { UserID: "user-id", @@ -556,8 +377,7 @@ func Test_MembershipPrepares(t *testing.T) { ChangeDate: testNow, Sequence: 20211202, ResourceOwner: "ro", - IAM: &IAMMembership{IAMID: "iam-id"}, - DisplayName: "display name", + IAM: &IAMMembership{IAMID: "iam-id", Name: "iam-id"}, }, { UserID: "user-id", @@ -566,8 +386,7 @@ func Test_MembershipPrepares(t *testing.T) { ChangeDate: testNow, Sequence: 20211202, ResourceOwner: "ro", - Project: &ProjectMembership{ProjectID: "project-id"}, - DisplayName: "display name", + Project: &ProjectMembership{ProjectID: "project-id", Name: "project-name"}, }, { UserID: "user-id", @@ -577,10 +396,10 @@ func Test_MembershipPrepares(t *testing.T) { Sequence: 20211202, ResourceOwner: "ro", ProjectGrant: &ProjectGrantMembership{ - ProjectID: "project-id", - GrantID: "grant-id", + ProjectID: "project-id", + GrantID: "grant-id", + ProjectName: "project-name", }, - DisplayName: "display name", }, }, },