From c542cab4f8c639149758af0b539a62b01bfbdda0 Mon Sep 17 00:00:00 2001 From: Silvan Date: Fri, 14 Jan 2022 10:45:50 +0100 Subject: [PATCH] feat(queries): user grants (#2838) * 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(projections): add user grant projection * fix(user_grant): handle state changes * add state to migration * fix(management): use query for user grant requests * merge eventstore-naming into user-grant-projection * feat(queries): user grants * fix(migrations): version * fix(api): user query for user grants * fix(query): event mappers for usergrant aggregate * fix(projection): correct aggregate for user grants * fix(queries): user grant roles as list contains * cleanup reducers * fix avater_key to avatar_key * tests * cleanup * cleanup * add resourceowner query * fix: user grant project name search query Co-authored-by: Livio Amstutz Co-authored-by: fabi --- docs/docs/apis/proto/user.md | 12 + internal/api/grpc/management/project.go | 26 +- internal/api/grpc/management/project_grant.go | 15 +- internal/api/grpc/management/server.go | 2 - internal/api/grpc/management/user.go | 37 +- internal/api/grpc/management/user_grant.go | 22 +- .../grpc/management/user_grant_converter.go | 50 +- internal/api/grpc/user/user_grant.go | 162 ++-- .../eventsourcing/eventstore/user_grant.go | 138 ---- .../eventsourcing/handler/handler.go | 1 - .../eventsourcing/handler/user_grant.go | 340 -------- .../repository/eventsourcing/repository.go | 2 - .../eventsourcing/view/user_grant.go | 90 -- internal/management/repository/repository.go | 1 - internal/management/repository/user_grant.go | 15 - internal/query/iam_member_test.go | 4 +- internal/query/org_member_test.go | 4 +- internal/query/project_grant_member_test.go | 4 +- internal/query/project_member_test.go | 4 +- internal/query/projection/user.go | 6 +- internal/query/projection/user_test.go | 28 +- internal/query/search_query.go | 97 ++- internal/query/search_query_test.go | 38 +- internal/query/user.go | 4 + internal/query/user_grant.go | 449 ++++++++++ internal/query/user_grant_test.go | 778 ++++++++++++++++++ internal/usergrant/model/user_grant_view.go | 8 - migrations/cockroach/V1.105__user_type.sql | 15 + proto/zitadel/user.proto | 10 + 29 files changed, 1546 insertions(+), 816 deletions(-) delete mode 100644 internal/management/repository/eventsourcing/eventstore/user_grant.go delete mode 100644 internal/management/repository/eventsourcing/handler/user_grant.go delete mode 100644 internal/management/repository/eventsourcing/view/user_grant.go delete mode 100644 internal/management/repository/user_grant.go create mode 100644 internal/query/user_grant.go create mode 100644 internal/query/user_grant_test.go create mode 100644 migrations/cockroach/V1.105__user_type.sql diff --git a/docs/docs/apis/proto/user.md b/docs/docs/apis/proto/user.md index a3c7e0596..087d31589 100644 --- a/docs/docs/apis/proto/user.md +++ b/docs/docs/apis/proto/user.md @@ -489,6 +489,7 @@ UserTypeQuery is always equals | [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) query.org_domain_query | UserGrantOrgDomainQuery | - | | | [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) query.project_name_query | UserGrantProjectNameQuery | - | | | [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) query.display_name_query | UserGrantDisplayNameQuery | - | | +| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) query.user_type_query | UserGrantUserTypeQuery | - | | @@ -528,6 +529,17 @@ UserTypeQuery is always equals +### UserGrantUserTypeQuery + + + +| Field | Type | Description | Validation | +| ----- | ---- | ----------- | ----------- | +| type | Type | - | | + + + + ### UserGrantWithGrantedQuery diff --git a/internal/api/grpc/management/project.go b/internal/api/grpc/management/project.go index 7346f859f..795250d24 100644 --- a/internal/api/grpc/management/project.go +++ b/internal/api/grpc/management/project.go @@ -8,6 +8,7 @@ import ( member_grpc "github.com/caos/zitadel/internal/api/grpc/member" object_grpc "github.com/caos/zitadel/internal/api/grpc/object" project_grpc "github.com/caos/zitadel/internal/api/grpc/project" + "github.com/caos/zitadel/internal/query" mgmt_pb "github.com/caos/zitadel/pkg/grpc/management" ) @@ -174,11 +175,17 @@ func (s *Server) ReactivateProject(ctx context.Context, req *mgmt_pb.ReactivateP } func (s *Server) RemoveProject(ctx context.Context, req *mgmt_pb.RemoveProjectRequest) (*mgmt_pb.RemoveProjectResponse, error) { - grants, err := s.usergrant.UserGrantsByProjectID(ctx, req.Id) + projectQuery, err := query.NewUserGrantProjectIDSearchQuery(req.Id) if err != nil { return nil, err } - details, err := s.command.RemoveProject(ctx, req.Id, authz.GetCtxData(ctx).OrgID, userGrantsToIDs(grants)...) + grants, err := s.query.UserGrants(ctx, &query.UserGrantsQueries{ + Queries: []query.SearchQuery{projectQuery}, + }) + if err != nil { + return nil, err + } + details, err := s.command.RemoveProject(ctx, req.Id, authz.GetCtxData(ctx).OrgID, userGrantsToIDs(grants.UserGrants)...) if err != nil { return nil, err } @@ -253,7 +260,18 @@ func (s *Server) UpdateProjectRole(ctx context.Context, req *mgmt_pb.UpdateProje } func (s *Server) RemoveProjectRole(ctx context.Context, req *mgmt_pb.RemoveProjectRoleRequest) (*mgmt_pb.RemoveProjectRoleResponse, error) { - userGrants, err := s.usergrant.UserGrantsByProjectIDAndRoleKey(ctx, req.ProjectId, req.RoleKey) + projectQuery, err := query.NewUserGrantProjectIDSearchQuery(req.ProjectId) + if err != nil { + return nil, err + } + rolesQuery, err := query.NewUserGrantGrantIDSearchQuery(req.RoleKey) + if err != nil { + return nil, err + } + userGrants, err := s.query.UserGrants(ctx, &query.UserGrantsQueries{ + Queries: []query.SearchQuery{projectQuery, rolesQuery}, + }) + if err != nil { return nil, err } @@ -261,7 +279,7 @@ func (s *Server) RemoveProjectRole(ctx context.Context, req *mgmt_pb.RemoveProje if err != nil { return nil, err } - details, err := s.command.RemoveProjectRole(ctx, req.ProjectId, req.RoleKey, authz.GetCtxData(ctx).OrgID, ProjectGrantsToIDs(projectGrants), userGrantsToIDs(userGrants)...) + details, err := s.command.RemoveProjectRole(ctx, req.ProjectId, req.RoleKey, authz.GetCtxData(ctx).OrgID, ProjectGrantsToIDs(projectGrants), userGrantsToIDs(userGrants.UserGrants)...) if err != nil { return nil, err } diff --git a/internal/api/grpc/management/project_grant.go b/internal/api/grpc/management/project_grant.go index 6b9542541..01da567e0 100644 --- a/internal/api/grpc/management/project_grant.go +++ b/internal/api/grpc/management/project_grant.go @@ -8,6 +8,7 @@ import ( member_grpc "github.com/caos/zitadel/internal/api/grpc/member" object_grpc "github.com/caos/zitadel/internal/api/grpc/object" proj_grpc "github.com/caos/zitadel/internal/api/grpc/project" + "github.com/caos/zitadel/internal/query" mgmt_pb "github.com/caos/zitadel/pkg/grpc/management" ) @@ -78,11 +79,21 @@ func (s *Server) AddProjectGrant(ctx context.Context, req *mgmt_pb.AddProjectGra } func (s *Server) UpdateProjectGrant(ctx context.Context, req *mgmt_pb.UpdateProjectGrantRequest) (*mgmt_pb.UpdateProjectGrantResponse, error) { - grants, err := s.usergrant.UserGrantsByProjectAndGrantID(ctx, req.ProjectId, req.GrantId) + projectQuery, err := query.NewUserGrantProjectIDSearchQuery(req.ProjectId) if err != nil { return nil, err } - grant, err := s.command.ChangeProjectGrant(ctx, UpdateProjectGrantRequestToDomain(req), authz.GetCtxData(ctx).OrgID, userGrantsToIDs(grants)...) + grantQuery, err := query.NewUserGrantGrantIDSearchQuery(req.GrantId) + if err != nil { + return nil, err + } + grants, err := s.query.UserGrants(ctx, &query.UserGrantsQueries{ + Queries: []query.SearchQuery{projectQuery, grantQuery}, + }) + if err != nil { + return nil, err + } + grant, err := s.command.ChangeProjectGrant(ctx, UpdateProjectGrantRequestToDomain(req), authz.GetCtxData(ctx).OrgID, userGrantsToIDs(grants.UserGrants)...) if err != nil { return nil, err } diff --git a/internal/api/grpc/management/server.go b/internal/api/grpc/management/server.go index a4db772d1..548a57956 100644 --- a/internal/api/grpc/management/server.go +++ b/internal/api/grpc/management/server.go @@ -26,7 +26,6 @@ type Server struct { project repository.ProjectRepository org repository.OrgRepository user repository.UserRepository - usergrant repository.UserGrantRepository iam repository.IamRepository authZ authz.Config systemDefaults systemdefaults.SystemDefaults @@ -44,7 +43,6 @@ func CreateServer(command *command.Commands, query *query.Queries, repo reposito project: repo, org: repo, user: repo, - usergrant: repo, iam: repo, systemDefaults: sd, assetAPIPrefix: assetAPIPrefix, diff --git a/internal/api/grpc/management/user.go b/internal/api/grpc/management/user.go index 6c447fbb2..a7112e12b 100644 --- a/internal/api/grpc/management/user.go +++ b/internal/api/grpc/management/user.go @@ -10,13 +10,10 @@ import ( change_grpc "github.com/caos/zitadel/internal/api/grpc/change" idp_grpc "github.com/caos/zitadel/internal/api/grpc/idp" "github.com/caos/zitadel/internal/api/grpc/metadata" - "github.com/caos/zitadel/internal/api/grpc/object" obj_grpc "github.com/caos/zitadel/internal/api/grpc/object" - "github.com/caos/zitadel/internal/api/grpc/user" user_grpc "github.com/caos/zitadel/internal/api/grpc/user" "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/query" - grant_model "github.com/caos/zitadel/internal/usergrant/model" mgmt_pb "github.com/caos/zitadel/pkg/grpc/management" ) @@ -257,21 +254,27 @@ func (s *Server) UnlockUser(ctx context.Context, req *mgmt_pb.UnlockUserRequest) } func (s *Server) RemoveUser(ctx context.Context, req *mgmt_pb.RemoveUserRequest) (*mgmt_pb.RemoveUserResponse, error) { - grants, err := s.usergrant.UserGrantsByUserID(ctx, req.Id) + userGrantUserQuery, err := query.NewUserGrantUserIDSearchQuery(req.Id) if err != nil { return nil, err } - userQuery, err := query.NewMembershipUserIDQuery(req.Id) - if err != nil { - return nil, err - } - memberships, err := s.query.Memberships(ctx, &query.MembershipSearchQuery{ - Queries: []query.SearchQuery{userQuery}, + grants, err := s.query.UserGrants(ctx, &query.UserGrantsQueries{ + Queries: []query.SearchQuery{userGrantUserQuery}, }) if err != nil { return nil, err } - objectDetails, err := s.command.RemoveUser(ctx, req.Id, authz.GetCtxData(ctx).OrgID, memberships.Memberships, userGrantsToIDs(grants)...) + membershipsUserQuery, err := query.NewMembershipUserIDQuery(req.Id) + if err != nil { + return nil, err + } + memberships, err := s.query.Memberships(ctx, &query.MembershipSearchQuery{ + Queries: []query.SearchQuery{membershipsUserQuery}, + }) + if err != nil { + return nil, err + } + objectDetails, err := s.command.RemoveUser(ctx, req.Id, authz.GetCtxData(ctx).OrgID, memberships.Memberships, userGrantsToIDs(grants.UserGrants)...) if err != nil { return nil, err } @@ -280,7 +283,7 @@ func (s *Server) RemoveUser(ctx context.Context, req *mgmt_pb.RemoveUserRequest) }, nil } -func userGrantsToIDs(userGrants []*grant_model.UserGrantView) []string { +func userGrantsToIDs(userGrants []*query.UserGrant) []string { converted := make([]string, len(userGrants)) for i, grant := range userGrants { converted[i] = grant.ID @@ -435,7 +438,7 @@ func (s *Server) RemoveHumanAvatar(ctx context.Context, req *mgmt_pb.RemoveHuman return nil, err } return &mgmt_pb.RemoveHumanAvatarResponse{ - Details: object.DomainToChangeDetailsPb(objectDetails), + Details: obj_grpc.DomainToChangeDetailsPb(objectDetails), }, nil } @@ -505,7 +508,7 @@ func (s *Server) ListHumanPasswordless(ctx context.Context, req *mgmt_pb.ListHum return nil, err } return &mgmt_pb.ListHumanPasswordlessResponse{ - Result: user.WebAuthNTokensViewToPb(tokens), + Result: user_grpc.WebAuthNTokensViewToPb(tokens), }, nil } @@ -516,7 +519,7 @@ func (s *Server) AddPasswordlessRegistration(ctx context.Context, req *mgmt_pb.A return nil, err } return &mgmt_pb.AddPasswordlessRegistrationResponse{ - Details: object.AddToDetailsPb(initCode.Sequence, initCode.ChangeDate, initCode.ResourceOwner), + Details: obj_grpc.AddToDetailsPb(initCode.Sequence, initCode.ChangeDate, initCode.ResourceOwner), Link: initCode.Link(s.systemDefaults.Notifications.Endpoints.PasswordlessRegistration), Expiration: durationpb.New(initCode.Expiration), }, nil @@ -529,7 +532,7 @@ func (s *Server) SendPasswordlessRegistration(ctx context.Context, req *mgmt_pb. return nil, err } return &mgmt_pb.SendPasswordlessRegistrationResponse{ - Details: object.AddToDetailsPb(initCode.Sequence, initCode.ChangeDate, initCode.ResourceOwner), + Details: obj_grpc.AddToDetailsPb(initCode.Sequence, initCode.ChangeDate, initCode.ResourceOwner), }, nil } @@ -606,7 +609,7 @@ func (s *Server) AddMachineKey(ctx context.Context, req *mgmt_pb.AddMachineKeyRe return &mgmt_pb.AddMachineKeyResponse{ KeyId: key.KeyID, KeyDetails: keyDetails, - Details: object.AddToDetailsPb( + Details: obj_grpc.AddToDetailsPb( key.Sequence, key.ChangeDate, key.ResourceOwner, diff --git a/internal/api/grpc/management/user_grant.go b/internal/api/grpc/management/user_grant.go index 3b0978ce1..9d5357ea0 100644 --- a/internal/api/grpc/management/user_grant.go +++ b/internal/api/grpc/management/user_grant.go @@ -6,31 +6,37 @@ import ( "github.com/caos/zitadel/internal/api/authz" obj_grpc "github.com/caos/zitadel/internal/api/grpc/object" "github.com/caos/zitadel/internal/api/grpc/user" - + "github.com/caos/zitadel/internal/query" mgmt_pb "github.com/caos/zitadel/pkg/grpc/management" ) func (s *Server) GetUserGrantByID(ctx context.Context, req *mgmt_pb.GetUserGrantByIDRequest) (*mgmt_pb.GetUserGrantByIDResponse, error) { - grant, err := s.usergrant.UserGrantByID(ctx, req.GrantId) + ownerQuery, err := query.NewUserGrantResourceOwnerSearchQuery(authz.GetCtxData(ctx).OrgID) + if err != nil { + return nil, err + } + grant, err := s.query.UserGrantByID(ctx, req.GrantId, ownerQuery) if err != nil { return nil, err } return &mgmt_pb.GetUserGrantByIDResponse{ - UserGrant: user.UserGrantToPb(grant), + UserGrant: user.UserGrantToPb(s.assetAPIPrefix, grant), }, nil } func (s *Server) ListUserGrants(ctx context.Context, req *mgmt_pb.ListUserGrantRequest) (*mgmt_pb.ListUserGrantResponse, error) { - r := ListUserGrantsRequestToModel(ctx, req) - r.AppendMyOrgQuery(authz.GetCtxData(ctx).OrgID) - res, err := s.usergrant.SearchUserGrants(ctx, r) + queries, err := ListUserGrantsRequestToQuery(ctx, req) + if err != nil { + return nil, err + } + res, err := s.query.UserGrants(ctx, queries) if err != nil { return nil, err } return &mgmt_pb.ListUserGrantResponse{ - Result: user.UserGrantsToPb(res.Result), + Result: user.UserGrantsToPb(s.assetAPIPrefix, res.UserGrants), Details: obj_grpc.ToListDetails( - res.TotalResult, + res.Count, res.Sequence, res.Timestamp, ), diff --git a/internal/api/grpc/management/user_grant_converter.go b/internal/api/grpc/management/user_grant_converter.go index 65f63d7b6..40ecf6771 100644 --- a/internal/api/grpc/management/user_grant_converter.go +++ b/internal/api/grpc/management/user_grant_converter.go @@ -2,29 +2,51 @@ package management import ( "context" + "github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/api/grpc/object" user_grpc "github.com/caos/zitadel/internal/api/grpc/user" "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/usergrant/model" + "github.com/caos/zitadel/internal/query" mgmt_pb "github.com/caos/zitadel/pkg/grpc/management" + "github.com/caos/zitadel/pkg/grpc/user" ) -func ListUserGrantsRequestToModel(ctx context.Context, req *mgmt_pb.ListUserGrantRequest) *model.UserGrantSearchRequest { - offset, limit, asc := object.ListQueryToModel(req.Query) - request := &model.UserGrantSearchRequest{ - Offset: offset, - Limit: limit, - Asc: asc, - Queries: user_grpc.UserGrantQueriesToModel(req.Queries), +func ListUserGrantsRequestToQuery(ctx context.Context, req *mgmt_pb.ListUserGrantRequest) (*query.UserGrantsQueries, error) { + queries, err := user_grpc.UserGrantQueriesToQuery(ctx, req.Queries) + if err != nil { + return nil, err } - request.Queries = append(request.Queries, &model.UserGrantSearchQuery{ - Key: model.UserGrantSearchKeyResourceOwner, - Method: domain.SearchMethodEquals, - Value: authz.GetCtxData(ctx).OrgID, - }) - return request + + if shouldAppendUserGrantOwnerQuery(req.Queries) { + ownerQuery, err := query.NewUserGrantResourceOwnerSearchQuery(authz.GetCtxData(ctx).OrgID) + if err != nil { + return nil, err + } + queries = append(queries, ownerQuery) + } + + offset, limit, asc := object.ListQueryToModel(req.Query) + request := &query.UserGrantsQueries{ + SearchRequest: query.SearchRequest{ + Offset: offset, + Limit: limit, + Asc: asc, + }, + Queries: queries, + } + + return request, nil +} + +func shouldAppendUserGrantOwnerQuery(queries []*user.UserGrantQuery) bool { + for _, query := range queries { + if _, ok := query.Query.(*user.UserGrantQuery_WithGrantedQuery); ok { + return false + } + } + return true } func AddUserGrantRequestToDomain(req *mgmt_pb.AddUserGrantRequest) *domain.UserGrant { diff --git a/internal/api/grpc/user/user_grant.go b/internal/api/grpc/user/user_grant.go index c9f3133af..bdb074398 100644 --- a/internal/api/grpc/user/user_grant.go +++ b/internal/api/grpc/user/user_grant.go @@ -1,38 +1,42 @@ package user import ( + "context" + "errors" + + "github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/api/grpc/object" "github.com/caos/zitadel/internal/domain" - usr_grant_model "github.com/caos/zitadel/internal/usergrant/model" + "github.com/caos/zitadel/internal/query" user_pb "github.com/caos/zitadel/pkg/grpc/user" ) -func UserGrantsToPb(grants []*usr_grant_model.UserGrantView) []*user_pb.UserGrant { +func UserGrantsToPb(assetPrefix string, grants []*query.UserGrant) []*user_pb.UserGrant { u := make([]*user_pb.UserGrant, len(grants)) for i, grant := range grants { - u[i] = UserGrantToPb(grant) + u[i] = UserGrantToPb(assetPrefix, grant) } return u } -func UserGrantToPb(grant *usr_grant_model.UserGrantView) *user_pb.UserGrant { +func UserGrantToPb(assetPrefix string, grant *query.UserGrant) *user_pb.UserGrant { return &user_pb.UserGrant{ Id: grant.ID, UserId: grant.UserID, - State: ModelUserGrantStateToPb(grant.State), - RoleKeys: grant.RoleKeys, - UserName: grant.UserName, + State: user_pb.UserGrantState_USER_GRANT_STATE_ACTIVE, + RoleKeys: grant.Roles, + ProjectId: grant.ProjectID, + OrgId: grant.ResourceOwner, + ProjectGrantId: grant.GrantID, + UserName: grant.Username, FirstName: grant.FirstName, LastName: grant.LastName, Email: grant.Email, DisplayName: grant.DisplayName, - OrgId: grant.ResourceOwner, OrgDomain: grant.OrgPrimaryDomain, OrgName: grant.OrgName, - ProjectId: grant.ProjectID, ProjectName: grant.ProjectName, - ProjectGrantId: grant.GrantID, - AvatarUrl: grant.AvatarURL, + AvatarUrl: domain.AvatarURL(assetPrefix, grant.UserResourceOwner, grant.AvatarURL), Details: object.ToViewDetailsPb( grant.Sequence, grant.CreationDate, @@ -42,15 +46,18 @@ func UserGrantToPb(grant *usr_grant_model.UserGrantView) *user_pb.UserGrant { } } -func UserGrantQueriesToModel(queries []*user_pb.UserGrantQuery) []*usr_grant_model.UserGrantSearchQuery { - q := make([]*usr_grant_model.UserGrantSearchQuery, len(queries)) +func UserGrantQueriesToQuery(ctx context.Context, queries []*user_pb.UserGrantQuery) (q []query.SearchQuery, err error) { + q = make([]query.SearchQuery, len(queries)) for i, query := range queries { - q[i] = UserGrantQueryToModel(query) + q[i], err = UserGrantQueryToQuery(ctx, query) + if err != nil { + return nil, err + } } - return q + return q, nil } -func UserGrantQueryToModel(query *user_pb.UserGrantQuery) *usr_grant_model.UserGrantSearchQuery { +func UserGrantQueryToQuery(ctx context.Context, query *user_pb.UserGrantQuery) (query.SearchQuery, error) { switch q := query.Query.(type) { case *user_pb.UserGrantQuery_DisplayNameQuery: return UserGrantDisplayNameQueryToModel(q.DisplayNameQuery) @@ -77,112 +84,77 @@ func UserGrantQueryToModel(query *user_pb.UserGrantQuery) *usr_grant_model.UserG case *user_pb.UserGrantQuery_UserNameQuery: return UserGrantUserNameQueryToModel(q.UserNameQuery) case *user_pb.UserGrantQuery_WithGrantedQuery: - return UserGrantWithGrantedQueryToModel(q.WithGrantedQuery) + return UserGrantWithGrantedQueryToModel(ctx, q.WithGrantedQuery) + case *user_pb.UserGrantQuery_UserTypeQuery: + return UserGrantUserTypeQueryToModel(q.UserTypeQuery) default: - return nil + return nil, errors.New("invalid query") } } -func UserGrantDisplayNameQueryToModel(q *user_pb.UserGrantDisplayNameQuery) *usr_grant_model.UserGrantSearchQuery { - return &usr_grant_model.UserGrantSearchQuery{ - Key: usr_grant_model.UserGrantSearchKeyDisplayName, - Method: object.TextMethodToModel(q.Method), - Value: q.DisplayName, - } +func UserGrantDisplayNameQueryToModel(q *user_pb.UserGrantDisplayNameQuery) (query.SearchQuery, error) { + return query.NewUserGrantDisplayNameQuery(q.DisplayName, object.TextMethodToQuery(q.Method)) } -func UserGrantEmailQueryToModel(q *user_pb.UserGrantEmailQuery) *usr_grant_model.UserGrantSearchQuery { - return &usr_grant_model.UserGrantSearchQuery{ - Key: usr_grant_model.UserGrantSearchKeyEmail, - Method: object.TextMethodToModel(q.Method), - Value: q.Email, - } +func UserGrantEmailQueryToModel(q *user_pb.UserGrantEmailQuery) (query.SearchQuery, error) { + return query.NewUserGrantEmailQuery(q.Email, object.TextMethodToQuery(q.Method)) } -func UserGrantFirstNameQueryToModel(q *user_pb.UserGrantFirstNameQuery) *usr_grant_model.UserGrantSearchQuery { - return &usr_grant_model.UserGrantSearchQuery{ - Key: usr_grant_model.UserGrantSearchKeyFirstName, - Method: object.TextMethodToModel(q.Method), - Value: q.FirstName, - } +func UserGrantFirstNameQueryToModel(q *user_pb.UserGrantFirstNameQuery) (query.SearchQuery, error) { + return query.NewUserGrantFirstNameQuery(q.FirstName, object.TextMethodToQuery(q.Method)) } -func UserGrantLastNameQueryToModel(q *user_pb.UserGrantLastNameQuery) *usr_grant_model.UserGrantSearchQuery { - return &usr_grant_model.UserGrantSearchQuery{ - Key: usr_grant_model.UserGrantSearchKeyLastName, - Method: object.TextMethodToModel(q.Method), - Value: q.LastName, - } +func UserGrantLastNameQueryToModel(q *user_pb.UserGrantLastNameQuery) (query.SearchQuery, error) { + return query.NewUserGrantLastNameQuery(q.LastName, object.TextMethodToQuery(q.Method)) } -func UserGrantOrgDomainQueryToModel(q *user_pb.UserGrantOrgDomainQuery) *usr_grant_model.UserGrantSearchQuery { - return &usr_grant_model.UserGrantSearchQuery{ - Key: usr_grant_model.UserGrantSearchKeyOrgDomain, - Method: object.TextMethodToModel(q.Method), - Value: q.OrgDomain, - } +func UserGrantOrgDomainQueryToModel(q *user_pb.UserGrantOrgDomainQuery) (query.SearchQuery, error) { + return query.NewUserGrantDomainQuery(q.OrgDomain, object.TextMethodToQuery(q.Method)) } -func UserGrantOrgNameQueryToModel(q *user_pb.UserGrantOrgNameQuery) *usr_grant_model.UserGrantSearchQuery { - return &usr_grant_model.UserGrantSearchQuery{ - Key: usr_grant_model.UserGrantSearchKeyOrgName, - Method: object.TextMethodToModel(q.Method), - Value: q.OrgName, - } +func UserGrantOrgNameQueryToModel(q *user_pb.UserGrantOrgNameQuery) (query.SearchQuery, error) { + return query.NewUserGrantOrgNameQuery(q.OrgName, object.TextMethodToQuery(q.Method)) } -func UserGrantProjectIDQueryToModel(q *user_pb.UserGrantProjectIDQuery) *usr_grant_model.UserGrantSearchQuery { - return &usr_grant_model.UserGrantSearchQuery{ - Key: usr_grant_model.UserGrantSearchKeyProjectID, - Method: domain.SearchMethodEquals, - Value: q.ProjectId, - } +func UserGrantProjectIDQueryToModel(q *user_pb.UserGrantProjectIDQuery) (query.SearchQuery, error) { + return query.NewUserGrantProjectIDSearchQuery(q.ProjectId) } -func UserGrantProjectGrantIDQueryToModel(q *user_pb.UserGrantProjectGrantIDQuery) *usr_grant_model.UserGrantSearchQuery { - return &usr_grant_model.UserGrantSearchQuery{ - Key: usr_grant_model.UserGrantSearchKeyGrantID, - Method: domain.SearchMethodEquals, - Value: q.ProjectGrantId, - } +func UserGrantProjectGrantIDQueryToModel(q *user_pb.UserGrantProjectGrantIDQuery) (query.SearchQuery, error) { + return query.NewUserGrantGrantIDSearchQuery(q.ProjectGrantId) } -func UserGrantProjectNameQueryToModel(q *user_pb.UserGrantProjectNameQuery) *usr_grant_model.UserGrantSearchQuery { - return &usr_grant_model.UserGrantSearchQuery{ - Key: usr_grant_model.UserGrantSearchKeyProjectName, - Method: object.TextMethodToModel(q.Method), - Value: q.ProjectName, - } +func UserGrantProjectNameQueryToModel(q *user_pb.UserGrantProjectNameQuery) (query.SearchQuery, error) { + return query.NewUserGrantProjectNameQuery(q.ProjectName, object.TextMethodToQuery(q.Method)) } -func UserGrantRoleKeyQueryToModel(q *user_pb.UserGrantRoleKeyQuery) *usr_grant_model.UserGrantSearchQuery { - return &usr_grant_model.UserGrantSearchQuery{ - Key: usr_grant_model.UserGrantSearchKeyRoleKey, - Method: domain.SearchMethodListContains, - Value: q.RoleKey, - } +func UserGrantRoleKeyQueryToModel(q *user_pb.UserGrantRoleKeyQuery) (query.SearchQuery, error) { + return query.NewUserGrantRoleQuery(q.RoleKey) } -func UserGrantUserIDQueryToModel(q *user_pb.UserGrantUserIDQuery) *usr_grant_model.UserGrantSearchQuery { - return &usr_grant_model.UserGrantSearchQuery{ - Key: usr_grant_model.UserGrantSearchKeyUserID, - Method: domain.SearchMethodEquals, - Value: q.UserId, - } +func UserGrantUserIDQueryToModel(q *user_pb.UserGrantUserIDQuery) (query.SearchQuery, error) { + return query.NewUserGrantUserIDSearchQuery(q.UserId) } -func UserGrantUserNameQueryToModel(q *user_pb.UserGrantUserNameQuery) *usr_grant_model.UserGrantSearchQuery { - return &usr_grant_model.UserGrantSearchQuery{ - Key: usr_grant_model.UserGrantSearchKeyUserName, - Method: object.TextMethodToModel(q.Method), - Value: q.UserName, - } +func UserGrantUserNameQueryToModel(q *user_pb.UserGrantUserNameQuery) (query.SearchQuery, error) { + return query.NewUserGrantUsernameQuery(q.UserName, object.TextMethodToQuery(q.Method)) } -func UserGrantWithGrantedQueryToModel(q *user_pb.UserGrantWithGrantedQuery) *usr_grant_model.UserGrantSearchQuery { - return &usr_grant_model.UserGrantSearchQuery{ - Key: usr_grant_model.UserGrantSearchKeyWithGranted, - Method: domain.SearchMethodEquals, - Value: q.WithGranted, +func UserGrantWithGrantedQueryToModel(ctx context.Context, q *user_pb.UserGrantWithGrantedQuery) (query.SearchQuery, error) { + return query.NewUserGrantWithGrantedQuery(authz.GetCtxData(ctx).OrgID) +} + +func UserGrantUserTypeQueryToModel(q *user_pb.UserGrantUserTypeQuery) (query.SearchQuery, error) { + return query.NewUserGrantUserTypeQuery(grantTypeToDomain(q.Type)) +} + +func grantTypeToDomain(typ user_pb.Type) domain.UserType { + switch typ { + case user_pb.Type_TYPE_HUMAN: + return domain.UserTypeHuman + case user_pb.Type_TYPE_MACHINE: + return domain.UserTypeMachine + default: + return domain.UserTypeUnspecified } } diff --git a/internal/management/repository/eventsourcing/eventstore/user_grant.go b/internal/management/repository/eventsourcing/eventstore/user_grant.go deleted file mode 100644 index 4f2d774f2..000000000 --- a/internal/management/repository/eventsourcing/eventstore/user_grant.go +++ /dev/null @@ -1,138 +0,0 @@ -package eventstore - -import ( - "context" - "github.com/caos/zitadel/internal/domain" - - "github.com/caos/logging" - "github.com/caos/zitadel/internal/api/authz" - "github.com/caos/zitadel/internal/management/repository/eventsourcing/view" - grant_model "github.com/caos/zitadel/internal/usergrant/model" - "github.com/caos/zitadel/internal/usergrant/repository/view/model" - "github.com/caos/zitadel/internal/view/repository" -) - -type UserGrantRepo struct { - SearchLimit uint64 - View *view.View - PrefixAvatarURL string -} - -func (repo *UserGrantRepo) UserGrantByID(ctx context.Context, grantID string) (*grant_model.UserGrantView, error) { - grant, err := repo.View.UserGrantByID(grantID) - if err != nil { - return nil, err - } - return model.UserGrantToModel(grant, repo.PrefixAvatarURL), nil -} - -func (repo *UserGrantRepo) UserGrantsByProjectID(ctx context.Context, projectID string) ([]*grant_model.UserGrantView, error) { - grants, err := repo.View.UserGrantsByProjectID(projectID) - if err != nil { - return nil, err - } - return model.UserGrantsToModel(grants, repo.PrefixAvatarURL), nil -} - -func (repo *UserGrantRepo) UserGrantsByProjectIDAndRoleKey(ctx context.Context, projectID, roleKey string) ([]*grant_model.UserGrantView, error) { - grants, err := repo.View.UserGrantsByProjectIDAndRoleKey(projectID, roleKey) - if err != nil { - return nil, err - } - return model.UserGrantsToModel(grants, repo.PrefixAvatarURL), nil -} - -func (repo *UserGrantRepo) UserGrantsByProjectAndGrantID(ctx context.Context, projectID, grantID string) ([]*grant_model.UserGrantView, error) { - grants, err := repo.View.UserGrantsByProjectAndGrantID(projectID, grantID) - if err != nil { - return nil, err - } - return model.UserGrantsToModel(grants, repo.PrefixAvatarURL), nil -} - -func (repo *UserGrantRepo) UserGrantsByUserID(ctx context.Context, userID string) ([]*grant_model.UserGrantView, error) { - grants, err := repo.View.UserGrantsByUserID(userID) - if err != nil { - return nil, err - } - return model.UserGrantsToModel(grants, repo.PrefixAvatarURL), nil -} - -func (repo *UserGrantRepo) SearchUserGrants(ctx context.Context, request *grant_model.UserGrantSearchRequest) (*grant_model.UserGrantSearchResponse, error) { - err := request.EnsureLimit(repo.SearchLimit) - if err != nil { - return nil, err - } - sequence, sequenceErr := repo.View.GetLatestUserGrantSequence() - logging.Log("EVENT-5Viwf").OnError(sequenceErr).Warn("could not read latest user grant sequence") - - result := handleSearchUserGrantPermissions(ctx, request, sequence) - if result != nil { - return result, nil - } - - grants, count, err := repo.View.SearchUserGrants(request) - if err != nil { - return nil, err - } - - result = &grant_model.UserGrantSearchResponse{ - Offset: request.Offset, - Limit: request.Limit, - TotalResult: count, - Result: model.UserGrantsToModel(grants, repo.PrefixAvatarURL), - } - if sequenceErr == nil { - result.Sequence = sequence.CurrentSequence - result.Timestamp = sequence.LastSuccessfulSpoolerRun - } - return result, nil -} - -func handleSearchUserGrantPermissions(ctx context.Context, request *grant_model.UserGrantSearchRequest, sequence *repository.CurrentSequence) *grant_model.UserGrantSearchResponse { - permissions := authz.GetAllPermissionsFromCtx(ctx) - if authz.HasGlobalExplicitPermission(permissions, projectReadPerm) { - return nil - } - - ids := authz.GetExplicitPermissionCtxIDs(permissions, projectReadPerm) - if _, query := request.GetSearchQuery(grant_model.UserGrantSearchKeyGrantID); query != nil { - result := checkContainsPermID(ids, query, request, sequence) - if result != nil { - return result - } - return nil - } - if _, query := request.GetSearchQuery(grant_model.UserGrantSearchKeyProjectID); query != nil { - result := checkContainsPermID(ids, query, request, sequence) - if result != nil { - return result - } - } - request.Queries = append(request.Queries, &grant_model.UserGrantSearchQuery{Key: grant_model.UserGrantSearchKeyProjectID, Method: domain.SearchMethodIsOneOf, Value: ids}) - return nil -} - -func checkContainsPermID(ids []string, query *grant_model.UserGrantSearchQuery, request *grant_model.UserGrantSearchRequest, sequence *repository.CurrentSequence) *grant_model.UserGrantSearchResponse { - containsID := false - for _, id := range ids { - if id == query.Value { - containsID = true - break - } - } - if !containsID { - result := &grant_model.UserGrantSearchResponse{ - Offset: request.Offset, - Limit: request.Limit, - TotalResult: uint64(0), - Result: []*grant_model.UserGrantView{}, - } - if sequence != nil { - result.Sequence = sequence.CurrentSequence - result.Timestamp = sequence.LastSuccessfulSpoolerRun - } - return result - } - return nil -} diff --git a/internal/management/repository/eventsourcing/handler/handler.go b/internal/management/repository/eventsourcing/handler/handler.go index 1764f8056..6a2de48ff 100644 --- a/internal/management/repository/eventsourcing/handler/handler.go +++ b/internal/management/repository/eventsourcing/handler/handler.go @@ -35,7 +35,6 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es return []query.Handler{ newUser(handler{view, bulkLimit, configs.cycleDuration("User"), errorCount, es}, defaults.IamID), - newUserGrant(handler{view, bulkLimit, configs.cycleDuration("UserGrant"), errorCount, es}), newMetadata( handler{view, bulkLimit, configs.cycleDuration("Metadata"), errorCount, es}), } diff --git a/internal/management/repository/eventsourcing/handler/user_grant.go b/internal/management/repository/eventsourcing/handler/user_grant.go deleted file mode 100644 index e51b273da..000000000 --- a/internal/management/repository/eventsourcing/handler/user_grant.go +++ /dev/null @@ -1,340 +0,0 @@ -package handler - -import ( - "context" - - "github.com/caos/logging" - - caos_errs "github.com/caos/zitadel/internal/errors" - v1 "github.com/caos/zitadel/internal/eventstore/v1" - es_models "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/eventstore/v1/query" - es_sdk "github.com/caos/zitadel/internal/eventstore/v1/sdk" - "github.com/caos/zitadel/internal/eventstore/v1/spooler" - org_model "github.com/caos/zitadel/internal/org/model" - org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model" - org_view "github.com/caos/zitadel/internal/org/repository/view" - proj_model "github.com/caos/zitadel/internal/project/model" - proj_es_model "github.com/caos/zitadel/internal/project/repository/eventsourcing/model" - proj_view "github.com/caos/zitadel/internal/project/repository/view" - usr_model "github.com/caos/zitadel/internal/user/model" - usr_es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" - "github.com/caos/zitadel/internal/user/repository/view" - usr_view_model "github.com/caos/zitadel/internal/user/repository/view/model" - grant_es_model "github.com/caos/zitadel/internal/usergrant/repository/eventsourcing/model" - view_model "github.com/caos/zitadel/internal/usergrant/repository/view/model" -) - -const ( - userGrantTable = "management.user_grants" -) - -type UserGrant struct { - handler - subscription *v1.Subscription -} - -func newUserGrant( - handler handler, -) *UserGrant { - h := &UserGrant{ - handler: handler, - } - - h.subscribe() - - return h -} - -func (m *UserGrant) subscribe() { - m.subscription = m.es.Subscribe(m.AggregateTypes()...) - go func() { - for event := range m.subscription.Events { - query.ReduceEvent(m, event) - } - }() -} - -func (u *UserGrant) ViewModel() string { - return userGrantTable -} - -func (u *UserGrant) Subscription() *v1.Subscription { - return u.subscription -} - -func (_ *UserGrant) AggregateTypes() []es_models.AggregateType { - return []es_models.AggregateType{grant_es_model.UserGrantAggregate, usr_es_model.UserAggregate, proj_es_model.ProjectAggregate, org_es_model.OrgAggregate} -} - -func (u *UserGrant) CurrentSequence() (uint64, error) { - sequence, err := u.view.GetLatestUserGrantSequence() - if err != nil { - return 0, err - } - return sequence.CurrentSequence, nil -} - -func (u *UserGrant) EventQuery() (*es_models.SearchQuery, error) { - sequence, err := u.view.GetLatestUserGrantSequence() - if err != nil { - return nil, err - } - return es_models.NewSearchQuery(). - AggregateTypeFilter(u.AggregateTypes()...). - LatestSequenceFilter(sequence.CurrentSequence), nil -} - -func (u *UserGrant) Reduce(event *es_models.Event) (err error) { - switch event.AggregateType { - case grant_es_model.UserGrantAggregate: - err = u.processUserGrant(event) - case usr_es_model.UserAggregate: - err = u.processUser(event) - case proj_es_model.ProjectAggregate: - err = u.processProject(event) - case org_es_model.OrgAggregate: - err = u.processOrg(event) - } - return err -} - -func (u *UserGrant) processUserGrant(event *es_models.Event) (err error) { - grant := new(view_model.UserGrantView) - switch event.Type { - case grant_es_model.UserGrantAdded: - err = grant.AppendEvent(event) - if err != nil { - return err - } - err = u.fillData(grant, event.ResourceOwner) - case grant_es_model.UserGrantChanged, - grant_es_model.UserGrantCascadeChanged, - grant_es_model.UserGrantDeactivated, - grant_es_model.UserGrantReactivated: - grant, err = u.view.UserGrantByID(event.AggregateID) - if err != nil { - return err - } - err = grant.AppendEvent(event) - case grant_es_model.UserGrantRemoved, grant_es_model.UserGrantCascadeRemoved: - return u.view.DeleteUserGrant(event.AggregateID, event) - default: - return u.view.ProcessedUserGrantSequence(event) - } - if err != nil { - return err - } - return u.view.PutUserGrant(grant, event) -} - -func (u *UserGrant) processUser(event *es_models.Event) (err error) { - switch event.Type { - case usr_es_model.UserProfileChanged, - usr_es_model.UserEmailChanged, - usr_es_model.HumanProfileChanged, - usr_es_model.HumanEmailChanged, - usr_es_model.MachineChanged, - usr_es_model.HumanAvatarAdded, - usr_es_model.HumanAvatarRemoved: - grants, err := u.view.UserGrantsByUserID(event.AggregateID) - if err != nil { - return err - } - if len(grants) == 0 { - return u.view.ProcessedUserGrantSequence(event) - } - user, err := u.getUserByID(event.AggregateID) - if err != nil { - return err - } - for _, grant := range grants { - u.fillUserData(grant, user) - } - return u.view.PutUserGrants(grants, event) - default: - return u.view.ProcessedUserGrantSequence(event) - } -} - -func (u *UserGrant) processProject(event *es_models.Event) (err error) { - switch event.Type { - case proj_es_model.ProjectChanged: - proj := new(proj_es_model.Project) - err := proj.SetData(event) - if err != nil { - return err - } - if proj.Name == "" { - return u.view.ProcessedUserGrantSequence(event) - } - grants, err := u.view.UserGrantsByProjectID(event.AggregateID) - if err != nil { - return err - } - for _, grant := range grants { - grant.ProjectName = proj.Name - } - return u.view.PutUserGrants(grants, event) - default: - return u.view.ProcessedUserGrantSequence(event) - } -} - -func (u *UserGrant) processOrg(event *es_models.Event) (err error) { - switch event.Type { - case org_es_model.OrgChanged: - grants, err := u.view.UserGrantsByOrgID(event.AggregateID) - if err != nil { - return err - } - if len(grants) == 0 { - return u.view.ProcessedUserGrantSequence(event) - } - org, err := u.getOrgByID(context.Background(), event.AggregateID) - if err != nil { - return err - } - for _, grant := range grants { - u.fillOrgData(grant, org) - } - return u.view.PutUserGrants(grants, event) - default: - return u.view.ProcessedUserGrantSequence(event) - } -} - -func (u *UserGrant) fillData(grant *view_model.UserGrantView, resourceOwner string) (err error) { - user, err := u.getUserByID(grant.UserID) - if err != nil { - return err - } - u.fillUserData(grant, user) - project, err := u.getProjectByID(context.Background(), grant.ProjectID) - if err != nil { - return err - } - u.fillProjectData(grant, project) - - org, err := u.getOrgByID(context.TODO(), resourceOwner) - if err != nil { - return err - } - u.fillOrgData(grant, org) - return nil -} - -func (u *UserGrant) fillUserData(grant *view_model.UserGrantView, user *usr_view_model.UserView) { - grant.UserName = user.UserName - grant.UserResourceOwner = user.ResourceOwner - if user.HumanView != nil { - grant.FirstName = user.FirstName - grant.LastName = user.LastName - grant.DisplayName = user.FirstName + " " + user.LastName - grant.Email = user.Email - grant.AvatarKey = user.AvatarKey - } - if user.MachineView != nil { - grant.DisplayName = user.MachineView.Name - } -} - -func (u *UserGrant) fillProjectData(grant *view_model.UserGrantView, project *proj_model.Project) { - grant.ProjectName = project.Name - grant.ProjectOwner = project.ResourceOwner -} - -func (u *UserGrant) fillOrgData(grant *view_model.UserGrantView, org *org_model.Org) { - grant.OrgName = org.Name - for _, domain := range org.Domains { - if domain.Primary { - grant.OrgPrimaryDomain = domain.Domain - break - } - } -} - -func (u *UserGrant) OnError(event *es_models.Event, err error) error { - logging.LogWithFields("SPOOL-8is4s", "id", event.AggregateID).WithError(err).Warn("something went wrong in user handler") - return spooler.HandleError(event, err, u.view.GetLatestUserGrantFailedEvent, u.view.ProcessedUserGrantFailedEvent, u.view.ProcessedUserGrantSequence, u.errorCountUntilSkip) -} - -func (u *UserGrant) OnSuccess() error { - return spooler.HandleSuccess(u.view.UpdateUserGrantSpoolerRunTimestamp) -} - -func (u *UserGrant) getUserByID(userID string) (*usr_view_model.UserView, error) { - user, usrErr := u.view.UserByID(userID) - if usrErr != nil && !caos_errs.IsNotFound(usrErr) { - return nil, usrErr - } - if user == nil { - user = &usr_view_model.UserView{} - } - events, err := u.getUserEvents(userID, user.Sequence) - if err != nil { - return user, usrErr - } - userCopy := *user - for _, event := range events { - if err := userCopy.AppendEvent(event); err != nil { - return user, nil - } - } - if userCopy.State == int32(usr_model.UserStateDeleted) { - return nil, caos_errs.ThrowNotFound(nil, "HANDLER-m9dos", "Errors.User.NotFound") - } - return &userCopy, nil -} - -func (u *UserGrant) getUserEvents(userID string, sequence uint64) ([]*es_models.Event, error) { - query, err := view.UserByIDQuery(userID, sequence) - if err != nil { - return nil, err - } - - return u.es.FilterEvents(context.Background(), query) -} - -func (u *UserGrant) getOrgByID(ctx context.Context, orgID string) (*org_model.Org, error) { - query, err := org_view.OrgByIDQuery(orgID, 0) - if err != nil { - return nil, err - } - - esOrg := &org_es_model.Org{ - ObjectRoot: es_models.ObjectRoot{ - AggregateID: orgID, - }, - } - err = es_sdk.Filter(ctx, u.Eventstore().FilterEvents, esOrg.AppendEvents, query) - if err != nil && !caos_errs.IsNotFound(err) { - return nil, err - } - if esOrg.Sequence == 0 { - return nil, caos_errs.ThrowNotFound(nil, "EVENT-kVLb2", "Errors.Org.NotFound") - } - - return org_es_model.OrgToModel(esOrg), nil -} - -func (u *UserGrant) getProjectByID(ctx context.Context, projID string) (*proj_model.Project, error) { - query, err := proj_view.ProjectByIDQuery(projID, 0) - if err != nil { - return nil, err - } - esProject := &proj_es_model.Project{ - ObjectRoot: es_models.ObjectRoot{ - AggregateID: projID, - }, - } - err = es_sdk.Filter(ctx, u.Eventstore().FilterEvents, esProject.AppendEvents, query) - if err != nil && !caos_errs.IsNotFound(err) { - return nil, err - } - if esProject.Sequence == 0 { - return nil, caos_errs.ThrowNotFound(nil, "EVENT-Dfb42", "Errors.Project.NotFound") - } - - return proj_es_model.ProjectToModel(esProject), nil -} diff --git a/internal/management/repository/eventsourcing/repository.go b/internal/management/repository/eventsourcing/repository.go index a8d0f7242..51ca363dd 100644 --- a/internal/management/repository/eventsourcing/repository.go +++ b/internal/management/repository/eventsourcing/repository.go @@ -29,7 +29,6 @@ type EsRepository struct { eventstore.OrgRepository eventstore.ProjectRepo eventstore.UserRepo - eventstore.UserGrantRepo eventstore.IAMRepository view *mgmt_view.View } @@ -76,7 +75,6 @@ func Start(conf Config, systemDefaults sd.SystemDefaults, roles []string, querie }, ProjectRepo: eventstore.ProjectRepo{es, conf.SearchLimit, view, roles, systemDefaults.IamID, assetsAPI, queries}, UserRepo: eventstore.UserRepo{es, conf.SearchLimit, view, systemDefaults, assetsAPI}, - UserGrantRepo: eventstore.UserGrantRepo{conf.SearchLimit, view, assetsAPI}, IAMRepository: eventstore.IAMRepository{IAMV2Query: queries}, view: view, }, nil diff --git a/internal/management/repository/eventsourcing/view/user_grant.go b/internal/management/repository/eventsourcing/view/user_grant.go deleted file mode 100644 index 6e012a4d8..000000000 --- a/internal/management/repository/eventsourcing/view/user_grant.go +++ /dev/null @@ -1,90 +0,0 @@ -package view - -import ( - "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/eventstore/v1/models" - grant_model "github.com/caos/zitadel/internal/usergrant/model" - "github.com/caos/zitadel/internal/usergrant/repository/view" - "github.com/caos/zitadel/internal/usergrant/repository/view/model" - "github.com/caos/zitadel/internal/view/repository" -) - -const ( - userGrantTable = "management.user_grants" -) - -func (v *View) UserGrantByID(grantID string) (*model.UserGrantView, error) { - return view.UserGrantByID(v.Db, userGrantTable, grantID) -} - -func (v *View) SearchUserGrants(request *grant_model.UserGrantSearchRequest) ([]*model.UserGrantView, uint64, error) { - return view.SearchUserGrants(v.Db, userGrantTable, request) -} - -func (v *View) UserGrantsByUserID(userID string) ([]*model.UserGrantView, error) { - return view.UserGrantsByUserID(v.Db, userGrantTable, userID) -} - -func (v *View) UserGrantsByProjectID(projectID string) ([]*model.UserGrantView, error) { - return view.UserGrantsByProjectID(v.Db, userGrantTable, projectID) -} - -func (v *View) UserGrantsByProjectAndGrantID(projectID, grantID string) ([]*model.UserGrantView, error) { - return view.UserGrantsByProjectAndGrantID(v.Db, userGrantTable, projectID, grantID) -} - -func (v *View) UserGrantsByOrgID(orgID string) ([]*model.UserGrantView, error) { - return view.UserGrantsByOrgID(v.Db, userGrantTable, orgID) -} - -func (v *View) UserGrantsByProjectIDAndRoleKey(projectID, roleKey string) ([]*model.UserGrantView, error) { - return view.UserGrantsByProjectIDAndRole(v.Db, userGrantTable, projectID, roleKey) -} - -func (v *View) UserGrantsByOrgIDAndProjectID(orgID, projectID string) ([]*model.UserGrantView, error) { - return view.UserGrantsByOrgIDAndProjectID(v.Db, userGrantTable, orgID, projectID) -} - -func (v *View) PutUserGrant(grant *model.UserGrantView, event *models.Event) error { - err := view.PutUserGrant(v.Db, userGrantTable, grant) - if err != nil { - return err - } - return v.ProcessedUserGrantSequence(event) -} - -func (v *View) PutUserGrants(grants []*model.UserGrantView, event *models.Event) error { - err := view.PutUserGrants(v.Db, userGrantTable, grants...) - if err != nil { - return err - } - return v.ProcessedUserGrantSequence(event) -} - -func (v *View) DeleteUserGrant(grantID string, event *models.Event) error { - err := view.DeleteUserGrant(v.Db, userGrantTable, grantID) - if err != nil && !errors.IsNotFound(err) { - return err - } - return v.ProcessedUserGrantSequence(event) -} - -func (v *View) GetLatestUserGrantSequence() (*repository.CurrentSequence, error) { - return v.latestSequence(userGrantTable) -} - -func (v *View) ProcessedUserGrantSequence(event *models.Event) error { - return v.saveCurrentSequence(userGrantTable, event) -} - -func (v *View) UpdateUserGrantSpoolerRunTimestamp() error { - return v.updateSpoolerRunSequence(userGrantTable) -} - -func (v *View) GetLatestUserGrantFailedEvent(sequence uint64) (*repository.FailedEvent, error) { - return v.latestFailedEvent(userGrantTable, sequence) -} - -func (v *View) ProcessedUserGrantFailedEvent(failedEvent *repository.FailedEvent) error { - return v.saveFailedEvent(failedEvent) -} diff --git a/internal/management/repository/repository.go b/internal/management/repository/repository.go index 0ed204471..de3079698 100644 --- a/internal/management/repository/repository.go +++ b/internal/management/repository/repository.go @@ -5,6 +5,5 @@ type Repository interface { ProjectRepository OrgRepository UserRepository - UserGrantRepository IamRepository } diff --git a/internal/management/repository/user_grant.go b/internal/management/repository/user_grant.go deleted file mode 100644 index 2225c7e57..000000000 --- a/internal/management/repository/user_grant.go +++ /dev/null @@ -1,15 +0,0 @@ -package repository - -import ( - "context" - "github.com/caos/zitadel/internal/usergrant/model" -) - -type UserGrantRepository interface { - UserGrantByID(ctx context.Context, grantID string) (*model.UserGrantView, error) - SearchUserGrants(ctx context.Context, request *model.UserGrantSearchRequest) (*model.UserGrantSearchResponse, error) - UserGrantsByProjectID(ctx context.Context, projectID string) ([]*model.UserGrantView, error) - UserGrantsByProjectAndGrantID(ctx context.Context, projectID, grantID string) ([]*model.UserGrantView, error) - UserGrantsByProjectIDAndRoleKey(ctx context.Context, projectID, roleKey string) ([]*model.UserGrantView, error) - UserGrantsByUserID(ctx context.Context, userID string) ([]*model.UserGrantView, error) -} diff --git a/internal/query/iam_member_test.go b/internal/query/iam_member_test.go index c9d4860ac..6857d8c48 100644 --- a/internal/query/iam_member_test.go +++ b/internal/query/iam_member_test.go @@ -25,7 +25,7 @@ var ( ", zitadel.projections.users_humans.last_name" + ", zitadel.projections.users_humans.display_name" + ", zitadel.projections.users_machines.name" + - ", zitadel.projections.users_humans.avater_key" + + ", zitadel.projections.users_humans.avatar_key" + ", COUNT(*) OVER () " + "FROM zitadel.projections.iam_members as members " + "LEFT JOIN zitadel.projections.users_humans " + @@ -48,7 +48,7 @@ var ( "last_name", "display_name", "name", - "avater_key", + "avatar_key", "count", } ) diff --git a/internal/query/org_member_test.go b/internal/query/org_member_test.go index a57cedcbf..7fb61e2fa 100644 --- a/internal/query/org_member_test.go +++ b/internal/query/org_member_test.go @@ -25,7 +25,7 @@ var ( ", zitadel.projections.users_humans.last_name" + ", zitadel.projections.users_humans.display_name" + ", zitadel.projections.users_machines.name" + - ", zitadel.projections.users_humans.avater_key" + + ", zitadel.projections.users_humans.avatar_key" + ", COUNT(*) OVER () " + "FROM zitadel.projections.org_members as members " + "LEFT JOIN zitadel.projections.users_humans " + @@ -48,7 +48,7 @@ var ( "last_name", "display_name", "name", - "avater_key", + "avatar_key", "count", } ) diff --git a/internal/query/project_grant_member_test.go b/internal/query/project_grant_member_test.go index 50b29b44a..d80e117e3 100644 --- a/internal/query/project_grant_member_test.go +++ b/internal/query/project_grant_member_test.go @@ -25,7 +25,7 @@ var ( ", zitadel.projections.users_humans.last_name" + ", zitadel.projections.users_humans.display_name" + ", zitadel.projections.users_machines.name" + - ", zitadel.projections.users_humans.avater_key" + + ", zitadel.projections.users_humans.avatar_key" + ", COUNT(*) OVER () " + "FROM zitadel.projections.project_grant_members as members " + "LEFT JOIN zitadel.projections.users_humans " + @@ -48,7 +48,7 @@ var ( "last_name", "display_name", "name", - "avater_key", + "avatar_key", "count", } ) diff --git a/internal/query/project_member_test.go b/internal/query/project_member_test.go index a4456d3af..3278ed761 100644 --- a/internal/query/project_member_test.go +++ b/internal/query/project_member_test.go @@ -25,7 +25,7 @@ var ( ", zitadel.projections.users_humans.last_name" + ", zitadel.projections.users_humans.display_name" + ", zitadel.projections.users_machines.name" + - ", zitadel.projections.users_humans.avater_key" + + ", zitadel.projections.users_humans.avatar_key" + ", COUNT(*) OVER () " + "FROM zitadel.projections.project_members as members " + "LEFT JOIN zitadel.projections.users_humans " + @@ -48,7 +48,7 @@ var ( "last_name", "display_name", "name", - "avater_key", + "avatar_key", "count", } ) diff --git a/internal/query/projection/user.go b/internal/query/projection/user.go index 1da5cdbc9..75ee0d835 100644 --- a/internal/query/projection/user.go +++ b/internal/query/projection/user.go @@ -40,6 +40,7 @@ const ( UserStateCol = "state" UserSequenceCol = "sequence" UserUsernameCol = "username" + UserTypeCol = "type" ) const ( @@ -53,7 +54,7 @@ const ( HumanDisplayNameCol = "display_name" HumanPreferredLanguageCol = "preferred_language" HumanGenderCol = "gender" - HumanAvaterURLCol = "avater_key" + HumanAvaterURLCol = "avatar_key" // email HumanEmailCol = "email" @@ -203,6 +204,7 @@ func (p *UserProjection) reduceHumanAdded(event eventstore.Event) (*handler.Stat handler.NewCol(UserStateCol, domain.UserStateInitial), handler.NewCol(UserSequenceCol, e.Sequence()), handler.NewCol(UserUsernameCol, e.UserName), + handler.NewCol(UserTypeCol, domain.UserTypeHuman), }, ), crdb.AddCreateStatement( @@ -239,6 +241,7 @@ func (p *UserProjection) reduceHumanRegistered(event eventstore.Event) (*handler handler.NewCol(UserStateCol, domain.UserStateInitial), handler.NewCol(UserSequenceCol, e.Sequence()), handler.NewCol(UserUsernameCol, e.UserName), + handler.NewCol(UserTypeCol, domain.UserTypeHuman), }, ), crdb.AddCreateStatement( @@ -656,6 +659,7 @@ func (p *UserProjection) reduceMachineAdded(event eventstore.Event) (*handler.St handler.NewCol(UserStateCol, domain.UserStateInitial), handler.NewCol(UserSequenceCol, e.Sequence()), handler.NewCol(UserUsernameCol, e.UserName), + handler.NewCol(UserTypeCol, domain.UserTypeMachine), }, ), crdb.AddCreateStatement( diff --git a/internal/query/projection/user_test.go b/internal/query/projection/user_test.go index 2b386d1e8..ba52dbfbc 100644 --- a/internal/query/projection/user_test.go +++ b/internal/query/projection/user_test.go @@ -50,7 +50,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO zitadel.projections.users (id, creation_date, change_date, resource_owner, state, sequence, username) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO zitadel.projections.users (id, creation_date, change_date, resource_owner, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -59,6 +59,7 @@ func TestUserProjection_reduces(t *testing.T) { domain.UserStateInitial, uint64(15), "user-name", + domain.UserTypeHuman, }, }, { @@ -107,7 +108,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO zitadel.projections.users (id, creation_date, change_date, resource_owner, state, sequence, username) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO zitadel.projections.users (id, creation_date, change_date, resource_owner, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -116,6 +117,7 @@ func TestUserProjection_reduces(t *testing.T) { domain.UserStateInitial, uint64(15), "user-name", + domain.UserTypeHuman, }, }, { @@ -159,7 +161,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO zitadel.projections.users (id, creation_date, change_date, resource_owner, state, sequence, username) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO zitadel.projections.users (id, creation_date, change_date, resource_owner, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -168,6 +170,7 @@ func TestUserProjection_reduces(t *testing.T) { domain.UserStateInitial, uint64(15), "user-name", + domain.UserTypeHuman, }, }, { @@ -216,7 +219,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO zitadel.projections.users (id, creation_date, change_date, resource_owner, state, sequence, username) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO zitadel.projections.users (id, creation_date, change_date, resource_owner, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -225,6 +228,7 @@ func TestUserProjection_reduces(t *testing.T) { domain.UserStateInitial, uint64(15), "user-name", + domain.UserTypeHuman, }, }, { @@ -273,7 +277,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO zitadel.projections.users (id, creation_date, change_date, resource_owner, state, sequence, username) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO zitadel.projections.users (id, creation_date, change_date, resource_owner, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -282,6 +286,7 @@ func TestUserProjection_reduces(t *testing.T) { domain.UserStateInitial, uint64(15), "user-name", + domain.UserTypeHuman, }, }, { @@ -325,7 +330,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO zitadel.projections.users (id, creation_date, change_date, resource_owner, state, sequence, username) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO zitadel.projections.users (id, creation_date, change_date, resource_owner, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -334,6 +339,7 @@ func TestUserProjection_reduces(t *testing.T) { domain.UserStateInitial, uint64(15), "user-name", + domain.UserTypeHuman, }, }, { @@ -1031,7 +1037,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE zitadel.projections.users_humans SET (avater_key) = ($1) WHERE (user_id = $2)", + expectedStmt: "UPDATE zitadel.projections.users_humans SET (avatar_key) = ($1) WHERE (user_id = $2)", expectedArgs: []interface{}{ "users/agg-id/avatar", "agg-id", @@ -1067,7 +1073,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE zitadel.projections.users_humans SET (avater_key) = ($1) WHERE (user_id = $2)", + expectedStmt: "UPDATE zitadel.projections.users_humans SET (avatar_key) = ($1) WHERE (user_id = $2)", expectedArgs: []interface{}{ nil, "agg-id", @@ -1098,7 +1104,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO zitadel.projections.users (id, creation_date, change_date, resource_owner, state, sequence, username) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO zitadel.projections.users (id, creation_date, change_date, resource_owner, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -1107,6 +1113,7 @@ func TestUserProjection_reduces(t *testing.T) { domain.UserStateInitial, uint64(15), "username", + domain.UserTypeMachine, }, }, { @@ -1143,7 +1150,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO zitadel.projections.users (id, creation_date, change_date, resource_owner, state, sequence, username) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedStmt: "INSERT INTO zitadel.projections.users (id, creation_date, change_date, resource_owner, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -1152,6 +1159,7 @@ func TestUserProjection_reduces(t *testing.T) { domain.UserStateInitial, uint64(15), "username", + domain.UserTypeMachine, }, }, { diff --git a/internal/query/search_query.go b/internal/query/search_query.go index 4c634aaa1..175c48ce8 100644 --- a/internal/query/search_query.go +++ b/internal/query/search_query.go @@ -44,6 +44,7 @@ const sqlPlaceholder = "?" type SearchQuery interface { toQuery(sq.SelectBuilder) sq.SelectBuilder + comp() sq.Sqlizer } type NotNullQuery struct { @@ -60,7 +61,34 @@ func NewNotNullQuery(col Column) (*NotNullQuery, error) { } func (q *NotNullQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder { - return query.Where(sq.NotEq{q.Column.identifier(): nil}) + return query.Where(q.comp()) +} + +func (q *NotNullQuery) comp() sq.Sqlizer { + return sq.NotEq{q.Column.identifier(): nil} +} + +type orQuery struct { + queries []SearchQuery +} + +func newOrQuery(queries ...SearchQuery) (*orQuery, error) { + if len(queries) == 0 { + return nil, ErrMissingColumn + } + return &orQuery{queries: queries}, nil +} + +func (q *orQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder { + return query.Where(q.comp()) +} + +func (q *orQuery) comp() sq.Sqlizer { + or := make(sq.Or, len(q.queries)) + for i, query := range q.queries { + or[i] = query.comp() + } + return or } type TextQuery struct { @@ -90,32 +118,31 @@ func NewTextQuery(col Column, value string, compare TextComparison) (*TextQuery, } func (q *TextQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder { - where, args := q.comp() - return query.Where(where, args...) + return query.Where(q.comp()) } -func (s *TextQuery) comp() (comparison interface{}, args []interface{}) { +func (s *TextQuery) comp() sq.Sqlizer { switch s.Compare { case TextEquals: - return sq.Eq{s.Column.identifier(): s.Text}, nil + return sq.Eq{s.Column.identifier(): s.Text} case TextEqualsIgnoreCase: - return sq.ILike{s.Column.identifier(): s.Text}, nil + return sq.ILike{s.Column.identifier(): s.Text} case TextStartsWith: - return sq.Like{s.Column.identifier(): s.Text + "%"}, nil + return sq.Like{s.Column.identifier(): s.Text + "%"} case TextStartsWithIgnoreCase: - return sq.ILike{s.Column.identifier(): s.Text + "%"}, nil + return sq.ILike{s.Column.identifier(): s.Text + "%"} case TextEndsWith: - return sq.Like{s.Column.identifier(): "%" + s.Text}, nil + return sq.Like{s.Column.identifier(): "%" + s.Text} case TextEndsWithIgnoreCase: - return sq.ILike{s.Column.identifier(): "%" + s.Text}, nil + return sq.ILike{s.Column.identifier(): "%" + s.Text} case TextContains: - return sq.Like{s.Column.identifier(): "%" + s.Text + "%"}, nil + return sq.Like{s.Column.identifier(): "%" + s.Text + "%"} case TextContainsIgnoreCase: - return sq.ILike{s.Column.identifier(): "%" + s.Text + "%"}, nil + return sq.ILike{s.Column.identifier(): "%" + s.Text + "%"} case TextListContains: - return s.Column.identifier() + " @> ? ", []interface{}{pq.StringArray{s.Text}} + return &listContains{col: s.Column, args: []interface{}{pq.StringArray{s.Text}}} } - return nil, nil + return nil } type TextComparison int @@ -187,24 +214,23 @@ func NewNumberQuery(c Column, value interface{}, compare NumberComparison) (*Num } func (q *NumberQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder { - where, args := q.comp() - return query.Where(where, args...) + return query.Where(q.comp()) } -func (s *NumberQuery) comp() (comparison interface{}, args []interface{}) { +func (s *NumberQuery) comp() sq.Sqlizer { switch s.Compare { case NumberEquals: - return sq.Eq{s.Column.identifier(): s.Number}, nil + return sq.Eq{s.Column.identifier(): s.Number} case NumberNotEquals: - return sq.NotEq{s.Column.identifier(): s.Number}, nil + return sq.NotEq{s.Column.identifier(): s.Number} case NumberLess: - return sq.Lt{s.Column.identifier(): s.Number}, nil + return sq.Lt{s.Column.identifier(): s.Number} case NumberGreater: - return sq.Gt{s.Column.identifier(): s.Number}, nil + return sq.Gt{s.Column.identifier(): s.Number} case NumberListContains: - return s.Column.identifier() + " @> ? ", []interface{}{pq.Array(s.Number)} + return &listContains{col: s.Column, args: []interface{}{pq.GenericArray{s.Number}}} } - return nil, nil + return nil } type NumberComparison int @@ -258,16 +284,15 @@ func NewListQuery(column Column, value []interface{}, compare ListComparison) (* } func (q *ListQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder { - where, args := q.comp() - return query.Where(where, args...) + return query.Where(q.comp()) } -func (s *ListQuery) comp() (interface{}, []interface{}) { +func (s *ListQuery) comp() sq.Sqlizer { switch s.Compare { case ListIn: - return sq.Eq{s.Column.identifier(): s.List}, nil + return sq.Eq{s.Column.identifier(): s.List} } - return nil, nil + return nil } type ListComparison int @@ -300,12 +325,11 @@ func NewBoolQuery(c Column, value bool) (*BoolQuery, error) { } func (q *BoolQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder { - where, args := q.comp() - return query.Where(where, args...) + return query.Where(q.comp()) } -func (s *BoolQuery) comp() (comparison interface{}, args []interface{}) { - return sq.Eq{s.Column.identifier(): s.Value}, nil +func (s *BoolQuery) comp() sq.Sqlizer { + return sq.Eq{s.Column.identifier(): s.Value} } var ( @@ -367,3 +391,12 @@ func (c Column) isZero() bool { func join(join, from Column) string { return join.table.identifier() + " ON " + from.identifier() + " = " + join.identifier() } + +type listContains struct { + col Column + args []interface{} +} + +func (q *listContains) ToSql() (string, []interface{}, error) { + return q.col.identifier() + " @> ? ", q.args, nil +} diff --git a/internal/query/search_query_test.go b/internal/query/search_query_test.go index 390cfaabd..952a2eb37 100644 --- a/internal/query/search_query_test.go +++ b/internal/query/search_query_test.go @@ -230,7 +230,6 @@ func TestTextQuery_comp(t *testing.T) { } type want struct { query interface{} - args []interface{} isNil bool } tests := []struct { @@ -247,7 +246,6 @@ func TestTextQuery_comp(t *testing.T) { }, want: want{ query: sq.Eq{"test_table.test_col": "Hurst"}, - args: nil, }, }, { @@ -259,7 +257,6 @@ func TestTextQuery_comp(t *testing.T) { }, want: want{ query: sq.ILike{"test_table.test_col": "Hurst"}, - args: nil, }, }, { @@ -271,7 +268,6 @@ func TestTextQuery_comp(t *testing.T) { }, want: want{ query: sq.Like{"test_table.test_col": "Hurst%"}, - args: nil, }, }, { @@ -283,7 +279,6 @@ func TestTextQuery_comp(t *testing.T) { }, want: want{ query: sq.ILike{"test_table.test_col": "Hurst%"}, - args: nil, }, }, { @@ -295,7 +290,6 @@ func TestTextQuery_comp(t *testing.T) { }, want: want{ query: sq.Like{"test_table.test_col": "%Hurst"}, - args: nil, }, }, { @@ -307,7 +301,6 @@ func TestTextQuery_comp(t *testing.T) { }, want: want{ query: sq.ILike{"test_table.test_col": "%Hurst"}, - args: nil, }, }, { @@ -319,7 +312,6 @@ func TestTextQuery_comp(t *testing.T) { }, want: want{ query: sq.Like{"test_table.test_col": "%Hurst%"}, - args: nil, }, }, { @@ -331,7 +323,6 @@ func TestTextQuery_comp(t *testing.T) { }, want: want{ query: sq.ILike{"test_table.test_col": "%Hurst%"}, - args: nil, }, }, { @@ -342,8 +333,10 @@ func TestTextQuery_comp(t *testing.T) { Compare: TextListContains, }, want: want{ - query: "test_table.test_col @> ? ", - args: []interface{}{pq.StringArray{"Hurst"}}, + query: &listContains{ + col: testCol, + args: []interface{}{pq.StringArray{"Hurst"}}, + }, }, }, { @@ -376,7 +369,7 @@ func TestTextQuery_comp(t *testing.T) { Text: tt.fields.Text, Compare: tt.fields.Compare, } - query, args := s.comp() + query := s.comp() if query == nil && tt.want.isNil { return } else if tt.want.isNil && query != nil { @@ -386,10 +379,6 @@ func TestTextQuery_comp(t *testing.T) { if !reflect.DeepEqual(query, tt.want.query) { t.Errorf("wrong query: want: %v, (%T), got: %v, (%T)", tt.want.query, tt.want.query, query, query) } - - if !reflect.DeepEqual(args, tt.want.args) { - t.Errorf("wrong args: want: %v, (%T), got: %v (%T)", tt.want.args, tt.want.args, args, args) - } }) } } @@ -589,7 +578,6 @@ func TestNumberQuery_comp(t *testing.T) { } type want struct { query interface{} - args []interface{} isNil bool } tests := []struct { @@ -606,7 +594,6 @@ func TestNumberQuery_comp(t *testing.T) { }, want: want{ query: sq.Eq{"test_table.test_col": 42}, - args: nil, }, }, { @@ -618,7 +605,6 @@ func TestNumberQuery_comp(t *testing.T) { }, want: want{ query: sq.NotEq{"test_table.test_col": 42}, - args: nil, }, }, { @@ -630,7 +616,6 @@ func TestNumberQuery_comp(t *testing.T) { }, want: want{ query: sq.Lt{"test_table.test_col": 42}, - args: nil, }, }, { @@ -642,7 +627,6 @@ func TestNumberQuery_comp(t *testing.T) { }, want: want{ query: sq.Gt{"test_table.test_col": 42}, - args: nil, }, }, { @@ -653,8 +637,10 @@ func TestNumberQuery_comp(t *testing.T) { Compare: NumberListContains, }, want: want{ - query: "test_table.test_col @> ? ", - args: []interface{}{pq.Array(42)}, + query: &listContains{ + col: testCol, + args: []interface{}{pq.GenericArray{42}}, + }, }, }, { @@ -687,7 +673,7 @@ func TestNumberQuery_comp(t *testing.T) { Number: tt.fields.Number, Compare: tt.fields.Compare, } - query, args := s.comp() + query := s.comp() if query == nil && tt.want.isNil { return } else if tt.want.isNil && query != nil { @@ -697,10 +683,6 @@ func TestNumberQuery_comp(t *testing.T) { if !reflect.DeepEqual(query, tt.want.query) { t.Errorf("wrong query: want: %v, (%T), got: %v, (%T)", tt.want.query, tt.want.query, query, query) } - - if !reflect.DeepEqual(args, tt.want.args) { - t.Errorf("wrong args: want: %v, (%T), got: %v (%T)", tt.want.args, tt.want.args, args, args) - } }) } } diff --git a/internal/query/user.go b/internal/query/user.go index c2819a642..1b00d892d 100644 --- a/internal/query/user.go +++ b/internal/query/user.go @@ -39,6 +39,10 @@ var ( name: projection.UserUsernameCol, table: userTable, } + UserTypeCol = Column{ + name: projection.UserTypeCol, + table: userTable, + } ) var ( diff --git a/internal/query/user_grant.go b/internal/query/user_grant.go new file mode 100644 index 000000000..847db6951 --- /dev/null +++ b/internal/query/user_grant.go @@ -0,0 +1,449 @@ +package query + +import ( + "context" + "database/sql" + errs "errors" + "time" + + sq "github.com/Masterminds/squirrel" + "github.com/lib/pq" + + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/query/projection" +) + +type UserGrant struct { + ID string + CreationDate time.Time + ChangeDate time.Time + Sequence uint64 + Roles []string + GrantID string + State domain.UserGrantState + + UserID string + Username string + UserType domain.UserType + UserResourceOwner string + FirstName string + LastName string + Email string + DisplayName string + AvatarURL string + + ResourceOwner string + OrgName string + OrgPrimaryDomain string + + ProjectID string + ProjectName string +} + +type UserGrants struct { + SearchResponse + UserGrants []*UserGrant +} + +type UserGrantsQueries struct { + SearchRequest + Queries []SearchQuery +} + +func (q *UserGrantsQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder { + query = q.SearchRequest.toQuery(query) + for _, q := range q.Queries { + query = q.toQuery(query) + } + return query +} + +func NewUserGrantUserIDSearchQuery(id string) (SearchQuery, error) { + return NewTextQuery(UserGrantUserID, id, TextEquals) +} + +func NewUserGrantProjectIDSearchQuery(id string) (SearchQuery, error) { + return NewTextQuery(UserGrantProjectID, id, TextEquals) +} + +func NewUserGrantProjectOwnerSearchQuery(id string) (SearchQuery, error) { + return NewTextQuery(ProjectColumnResourceOwner, id, TextEquals) +} + +func NewUserGrantResourceOwnerSearchQuery(id string) (SearchQuery, error) { + return NewTextQuery(UserGrantResourceOwner, id, TextEquals) +} + +func NewUserGrantGrantIDSearchQuery(id string) (SearchQuery, error) { + return NewTextQuery(UserGrantGrantID, id, TextEquals) +} + +func NewUserGrantUserTypeQuery(typ domain.UserType) (SearchQuery, error) { + return NewNumberQuery(UserTypeCol, typ, NumberEquals) +} + +func NewUserGrantDisplayNameQuery(displayName string, method TextComparison) (SearchQuery, error) { + return NewTextQuery(HumanDisplayNameCol, displayName, method) +} + +func NewUserGrantEmailQuery(email string, method TextComparison) (SearchQuery, error) { + return NewTextQuery(HumanEmailCol, email, method) +} + +func NewUserGrantFirstNameQuery(value string, method TextComparison) (SearchQuery, error) { + return NewTextQuery(HumanFirstNameCol, value, method) +} + +func NewUserGrantLastNameQuery(value string, method TextComparison) (SearchQuery, error) { + return NewTextQuery(HumanLastNameCol, value, method) +} + +func NewUserGrantUsernameQuery(value string, method TextComparison) (SearchQuery, error) { + return NewTextQuery(UserUsernameCol, value, method) +} + +func NewUserGrantDomainQuery(value string, method TextComparison) (SearchQuery, error) { + return NewTextQuery(OrgColumnDomain, value, method) +} + +func NewUserGrantOrgNameQuery(value string, method TextComparison) (SearchQuery, error) { + return NewTextQuery(OrgColumnName, value, method) +} + +func NewUserGrantProjectNameQuery(value string, method TextComparison) (SearchQuery, error) { + return NewTextQuery(ProjectColumnName, value, method) +} + +func NewUserGrantRoleQuery(value string) (SearchQuery, error) { + return NewTextQuery(UserGrantRoles, value, TextListContains) +} + +func NewUserGrantWithGrantedQuery(owner string) (SearchQuery, error) { + orgQuery, err := NewUserGrantResourceOwnerSearchQuery(owner) + if err != nil { + return nil, err + } + projectQuery, err := NewUserGrantProjectOwnerSearchQuery(owner) + if err != nil { + return nil, err + } + return newOrQuery(orgQuery, projectQuery) +} + +func NewUserGrantContainsRolesSearchQuery(roles ...string) (SearchQuery, error) { + r := make([]interface{}, len(roles)) + for i, role := range roles { + r[i] = role + } + return NewListQuery(UserGrantRoles, r, ListIn) +} + +var ( + userGrantTable = table{ + name: projection.UserGrantProjectionTable, + } + UserGrantID = Column{ + name: projection.UserGrantID, + table: userGrantTable, + } + UserGrantResourceOwner = Column{ + name: projection.UserGrantResourceOwner, + table: userGrantTable, + } + UserGrantCreationDate = Column{ + name: projection.UserGrantCreationDate, + table: userGrantTable, + } + UserGrantChangeDate = Column{ + name: projection.UserGrantChangeDate, + table: userGrantTable, + } + UserGrantSequence = Column{ + name: projection.UserGrantSequence, + table: userGrantTable, + } + UserGrantUserID = Column{ + name: projection.UserGrantUserID, + table: userGrantTable, + } + UserGrantProjectID = Column{ + name: projection.UserGrantProjectID, + table: userGrantTable, + } + UserGrantGrantID = Column{ + name: projection.UserGrantGrantID, + table: userGrantTable, + } + UserGrantRoles = Column{ + name: projection.UserGrantRoles, + table: userGrantTable, + } + UserGrantState = Column{ + name: projection.UserGrantState, + table: userGrantTable, + } +) + +func (q *Queries) UserGrantByID(ctx context.Context, id string, queries ...SearchQuery) (*UserGrant, error) { + query, scan := prepareUserGrantQuery() + for _, q := range queries { + query = q.toQuery(query) + } + stmt, args, err := query.Where(sq.Eq{ + UserGrantID.identifier(): id, + }).ToSql() + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-Fa1KW", "Errors.Query.SQLStatement") + } + + row := q.client.QueryRowContext(ctx, stmt, args...) + return scan(row) +} + +func (q *Queries) UserGrants(ctx context.Context, queries *UserGrantsQueries) (*UserGrants, error) { + query, scan := prepareUserGrantsQuery() + stmt, args, err := queries.toQuery(query).ToSql() + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-wXnQR", "Errors.Query.SQLStatement") + } + + latestSequence, err := q.latestSequence(ctx, userGrantTable) + if err != nil { + return nil, err + } + + rows, err := q.client.QueryContext(ctx, stmt, args...) + if err != nil { + return nil, err + } + grants, err := scan(rows) + if err != nil { + return nil, err + } + + grants.LatestSequence = latestSequence + return grants, nil +} + +func prepareUserGrantQuery() (sq.SelectBuilder, func(*sql.Row) (*UserGrant, error)) { + return sq.Select( + UserGrantID.identifier(), + UserGrantCreationDate.identifier(), + UserGrantChangeDate.identifier(), + UserGrantSequence.identifier(), + UserGrantGrantID.identifier(), + UserGrantRoles.identifier(), + UserGrantState.identifier(), + + UserGrantUserID.identifier(), + UserUsernameCol.identifier(), + UserTypeCol.identifier(), + UserResourceOwnerCol.identifier(), + HumanFirstNameCol.identifier(), + HumanLastNameCol.identifier(), + HumanEmailCol.identifier(), + HumanDisplayNameCol.identifier(), + HumanAvaterURLCol.identifier(), + + UserGrantResourceOwner.identifier(), + OrgColumnName.identifier(), + OrgColumnDomain.identifier(), + + UserGrantProjectID.identifier(), + ProjectColumnName.identifier(), + ). + From(userGrantTable.identifier()). + LeftJoin(join(UserIDCol, UserGrantUserID)). + LeftJoin(join(HumanUserIDCol, UserGrantUserID)). + LeftJoin(join(OrgColumnID, UserGrantResourceOwner)). + LeftJoin(join(ProjectColumnID, UserGrantProjectID)). + PlaceholderFormat(sq.Dollar), + func(row *sql.Row) (*UserGrant, error) { + g := new(UserGrant) + + var ( + roles = pq.StringArray{} + username sql.NullString + firstName sql.NullString + userType sql.NullInt32 + userOwner sql.NullString + lastName sql.NullString + email sql.NullString + displayName sql.NullString + avatarURL sql.NullString + + orgName sql.NullString + orgDomain sql.NullString + + projectName sql.NullString + ) + + err := row.Scan( + &g.ID, + &g.CreationDate, + &g.ChangeDate, + &g.Sequence, + &g.GrantID, + &roles, + &g.State, + + &g.UserID, + &username, + &userType, + &userOwner, + &firstName, + &lastName, + &email, + &displayName, + &avatarURL, + + &g.ResourceOwner, + &orgName, + &orgDomain, + + &g.ProjectID, + &projectName, + ) + if err != nil { + if errs.Is(err, sql.ErrNoRows) { + return nil, errors.ThrowNotFound(err, "QUERY-wIPkA", "Errors.UserGrant.NotFound") + } + return nil, errors.ThrowInternal(err, "QUERY-oQPcP", "Errors.Internal") + } + + g.Roles = roles + g.Username = username.String + g.UserType = domain.UserType(userType.Int32) + g.UserResourceOwner = userOwner.String + g.FirstName = firstName.String + g.LastName = lastName.String + g.Email = email.String + g.DisplayName = displayName.String + g.AvatarURL = avatarURL.String + g.OrgName = orgName.String + g.OrgPrimaryDomain = orgDomain.String + g.ProjectName = projectName.String + + return g, nil + } +} + +func prepareUserGrantsQuery() (sq.SelectBuilder, func(*sql.Rows) (*UserGrants, error)) { + return sq.Select( + UserGrantID.identifier(), + UserGrantCreationDate.identifier(), + UserGrantChangeDate.identifier(), + UserGrantSequence.identifier(), + UserGrantGrantID.identifier(), + UserGrantRoles.identifier(), + UserGrantState.identifier(), + + UserGrantUserID.identifier(), + UserUsernameCol.identifier(), + UserTypeCol.identifier(), + UserResourceOwnerCol.identifier(), + HumanFirstNameCol.identifier(), + HumanLastNameCol.identifier(), + HumanEmailCol.identifier(), + HumanDisplayNameCol.identifier(), + HumanAvaterURLCol.identifier(), + + UserGrantResourceOwner.identifier(), + OrgColumnName.identifier(), + OrgColumnDomain.identifier(), + + UserGrantProjectID.identifier(), + ProjectColumnName.identifier(), + + countColumn.identifier(), + ). + From(userGrantTable.identifier()). + LeftJoin(join(UserIDCol, UserGrantUserID)). + LeftJoin(join(HumanUserIDCol, UserGrantUserID)). + LeftJoin(join(OrgColumnID, UserGrantResourceOwner)). + LeftJoin(join(ProjectColumnID, UserGrantProjectID)). + PlaceholderFormat(sq.Dollar), + func(rows *sql.Rows) (*UserGrants, error) { + userGrants := make([]*UserGrant, 0) + var count uint64 + for rows.Next() { + g := new(UserGrant) + + var ( + roles = pq.StringArray{} + username sql.NullString + userType sql.NullInt32 + userOwner sql.NullString + firstName sql.NullString + lastName sql.NullString + email sql.NullString + displayName sql.NullString + avatarURL sql.NullString + + orgName sql.NullString + orgDomain sql.NullString + + projectName sql.NullString + ) + + err := rows.Scan( + &g.ID, + &g.CreationDate, + &g.ChangeDate, + &g.Sequence, + &g.GrantID, + &roles, + &g.State, + + &g.UserID, + &username, + &userType, + &userOwner, + &firstName, + &lastName, + &email, + &displayName, + &avatarURL, + + &g.ResourceOwner, + &orgName, + &orgDomain, + + &g.ProjectID, + &projectName, + + &count, + ) + if err != nil { + return nil, err + } + + g.Roles = roles + g.Username = username.String + g.UserType = domain.UserType(userType.Int32) + g.UserResourceOwner = userOwner.String + g.FirstName = firstName.String + g.LastName = lastName.String + g.Email = email.String + g.DisplayName = displayName.String + g.AvatarURL = avatarURL.String + g.OrgName = orgName.String + g.OrgPrimaryDomain = orgDomain.String + g.ProjectName = projectName.String + + userGrants = append(userGrants, g) + } + + if err := rows.Close(); err != nil { + return nil, errors.ThrowInternal(err, "QUERY-iGvmP", "Errors.Query.CloseRows") + } + + return &UserGrants{ + UserGrants: userGrants, + SearchResponse: SearchResponse{ + Count: count, + }, + }, nil + } +} diff --git a/internal/query/user_grant_test.go b/internal/query/user_grant_test.go new file mode 100644 index 000000000..ec08d6c5f --- /dev/null +++ b/internal/query/user_grant_test.go @@ -0,0 +1,778 @@ +package query + +import ( + "database/sql" + "database/sql/driver" + "errors" + "fmt" + "regexp" + "testing" + + "github.com/lib/pq" + + "github.com/caos/zitadel/internal/domain" + errs "github.com/caos/zitadel/internal/errors" +) + +var ( + userGrantStmt = regexp.QuoteMeta( + "SELECT zitadel.projections.user_grants.id" + + ", zitadel.projections.user_grants.creation_date" + + ", zitadel.projections.user_grants.change_date" + + ", zitadel.projections.user_grants.sequence" + + ", zitadel.projections.user_grants.grant_id" + + ", zitadel.projections.user_grants.roles" + + ", zitadel.projections.user_grants.state" + + ", zitadel.projections.user_grants.user_id" + + ", zitadel.projections.users.username" + + ", zitadel.projections.users.type" + + ", zitadel.projections.users.resource_owner" + + ", zitadel.projections.users_humans.first_name" + + ", zitadel.projections.users_humans.last_name" + + ", zitadel.projections.users_humans.email" + + ", zitadel.projections.users_humans.display_name" + + ", zitadel.projections.users_humans.avatar_key" + + ", zitadel.projections.user_grants.resource_owner" + + ", zitadel.projections.orgs.name" + + ", zitadel.projections.orgs.primary_domain" + + ", zitadel.projections.user_grants.project_id" + + ", zitadel.projections.projects.name" + + " FROM zitadel.projections.user_grants" + + " LEFT JOIN zitadel.projections.users ON zitadel.projections.user_grants.user_id = zitadel.projections.users.id" + + " LEFT JOIN zitadel.projections.users_humans ON zitadel.projections.user_grants.user_id = zitadel.projections.users_humans.user_id" + + " LEFT JOIN zitadel.projections.orgs ON zitadel.projections.user_grants.resource_owner = zitadel.projections.orgs.id" + + " LEFT JOIN zitadel.projections.projects ON zitadel.projections.user_grants.project_id = zitadel.projections.projects.id") + userGrantCols = []string{ + "id", + "creation_date", + "change_date", + "sequence", + "grant_id", + "roles", + "state", + "user_id", + "username", + "type", + "resource_owner", //user resource owner + "first_name", + "last_name", + "email", + "display_name", + "avatar_key", + "resource_owner", //user_grant resource owner + "name", //org name + "primary_domain", + "project_id", + "name", //project name + } + userGrantsStmt = regexp.QuoteMeta( + "SELECT zitadel.projections.user_grants.id" + + ", zitadel.projections.user_grants.creation_date" + + ", zitadel.projections.user_grants.change_date" + + ", zitadel.projections.user_grants.sequence" + + ", zitadel.projections.user_grants.grant_id" + + ", zitadel.projections.user_grants.roles" + + ", zitadel.projections.user_grants.state" + + ", zitadel.projections.user_grants.user_id" + + ", zitadel.projections.users.username" + + ", zitadel.projections.users.type" + + ", zitadel.projections.users.resource_owner" + + ", zitadel.projections.users_humans.first_name" + + ", zitadel.projections.users_humans.last_name" + + ", zitadel.projections.users_humans.email" + + ", zitadel.projections.users_humans.display_name" + + ", zitadel.projections.users_humans.avatar_key" + + ", zitadel.projections.user_grants.resource_owner" + + ", zitadel.projections.orgs.name" + + ", zitadel.projections.orgs.primary_domain" + + ", zitadel.projections.user_grants.project_id" + + ", zitadel.projections.projects.name" + + ", COUNT(*) OVER ()" + + " FROM zitadel.projections.user_grants" + + " LEFT JOIN zitadel.projections.users ON zitadel.projections.user_grants.user_id = zitadel.projections.users.id" + + " LEFT JOIN zitadel.projections.users_humans ON zitadel.projections.user_grants.user_id = zitadel.projections.users_humans.user_id" + + " LEFT JOIN zitadel.projections.orgs ON zitadel.projections.user_grants.resource_owner = zitadel.projections.orgs.id" + + " LEFT JOIN zitadel.projections.projects ON zitadel.projections.user_grants.project_id = zitadel.projections.projects.id") + userGrantsCols = append( + userGrantCols, + "count", + ) +) + +func Test_UserGrantPrepares(t *testing.T) { + type want struct { + sqlExpectations sqlExpectation + err checkErr + } + tests := []struct { + name string + prepare interface{} + want want + object interface{} + }{ + { + name: "prepareUserGrantQuery no result", + prepare: prepareUserGrantQuery, + want: want{ + sqlExpectations: mockQueries( + userGrantStmt, + nil, + nil, + ), + err: func(err error) (error, bool) { + if !errs.IsNotFound(err) { + return fmt.Errorf("err should be zitadel.NotFoundError got: %w", err), false + } + return nil, true + }, + }, + object: (*UserGrant)(nil), + }, + { + name: "prepareUserGrantQuery found", + prepare: prepareUserGrantQuery, + want: want{ + sqlExpectations: mockQuery( + userGrantStmt, + userGrantCols, + []driver.Value{ + "id", + testNow, + testNow, + 20211111, + "grant-id", + pq.StringArray{"role-key"}, + domain.UserGrantStateActive, + "user-id", + "username", + domain.UserTypeHuman, + "resource-owner", + "first-name", + "last-name", + "email", + "display-name", + "avatar-key", + "ro", + "org-name", + "primary-domain", + "project-id", + "project-name", + }, + ), + }, + object: &UserGrant{ + ID: "id", + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211111, + Roles: []string{"role-key"}, + GrantID: "grant-id", + State: domain.UserGrantStateActive, + UserID: "user-id", + Username: "username", + UserType: domain.UserTypeHuman, + UserResourceOwner: "resource-owner", + FirstName: "first-name", + LastName: "last-name", + Email: "email", + DisplayName: "display-name", + AvatarURL: "avatar-key", + ResourceOwner: "ro", + OrgName: "org-name", + OrgPrimaryDomain: "primary-domain", + ProjectID: "project-id", + ProjectName: "project-name", + }, + }, + { + name: "prepareUserGrantQuery machine user found", + prepare: prepareUserGrantQuery, + want: want{ + sqlExpectations: mockQuery( + userGrantStmt, + userGrantCols, + []driver.Value{ + "id", + testNow, + testNow, + 20211111, + "grant-id", + pq.StringArray{"role-key"}, + domain.UserGrantStateActive, + "user-id", + "username", + domain.UserTypeMachine, + "resource-owner", + nil, + nil, + nil, + nil, + nil, + "ro", + "org-name", + "primary-domain", + "project-id", + "project-name", + }, + ), + }, + object: &UserGrant{ + ID: "id", + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211111, + Roles: []string{"role-key"}, + GrantID: "grant-id", + State: domain.UserGrantStateActive, + UserID: "user-id", + Username: "username", + UserType: domain.UserTypeMachine, + UserResourceOwner: "resource-owner", + FirstName: "", + LastName: "", + Email: "", + DisplayName: "", + AvatarURL: "", + ResourceOwner: "ro", + OrgName: "org-name", + OrgPrimaryDomain: "primary-domain", + ProjectID: "project-id", + ProjectName: "project-name", + }, + }, + { + name: "prepareUserGrantQuery (no org) found", + prepare: prepareUserGrantQuery, + want: want{ + sqlExpectations: mockQuery( + userGrantStmt, + userGrantCols, + []driver.Value{ + "id", + testNow, + testNow, + 20211111, + "grant-id", + pq.StringArray{"role-key"}, + domain.UserGrantStateActive, + "user-id", + "username", + domain.UserTypeHuman, + "resource-owner", + "first-name", + "last-name", + "email", + "display-name", + "avatar-key", + "ro", + nil, + nil, + "project-id", + "project-name", + }, + ), + }, + object: &UserGrant{ + ID: "id", + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211111, + Roles: []string{"role-key"}, + GrantID: "grant-id", + State: domain.UserGrantStateActive, + UserID: "user-id", + Username: "username", + UserType: domain.UserTypeHuman, + UserResourceOwner: "resource-owner", + FirstName: "first-name", + LastName: "last-name", + Email: "email", + DisplayName: "display-name", + AvatarURL: "avatar-key", + ResourceOwner: "ro", + OrgName: "", + OrgPrimaryDomain: "", + ProjectID: "project-id", + ProjectName: "project-name", + }, + }, + { + name: "prepareUserGrantQuery (no project) found", + prepare: prepareUserGrantQuery, + want: want{ + sqlExpectations: mockQuery( + userGrantStmt, + userGrantCols, + []driver.Value{ + "id", + testNow, + testNow, + 20211111, + "grant-id", + pq.StringArray{"role-key"}, + domain.UserGrantStateActive, + "user-id", + "username", + domain.UserTypeHuman, + "resource-owner", + "first-name", + "last-name", + "email", + "display-name", + "avatar-key", + "ro", + "org-name", + "primary-domain", + "project-id", + nil, + }, + ), + }, + object: &UserGrant{ + ID: "id", + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211111, + Roles: []string{"role-key"}, + GrantID: "grant-id", + State: domain.UserGrantStateActive, + UserID: "user-id", + Username: "username", + UserType: domain.UserTypeHuman, + UserResourceOwner: "resource-owner", + FirstName: "first-name", + LastName: "last-name", + Email: "email", + DisplayName: "display-name", + AvatarURL: "avatar-key", + ResourceOwner: "ro", + OrgName: "org-name", + OrgPrimaryDomain: "primary-domain", + ProjectID: "project-id", + ProjectName: "", + }, + }, + { + name: "prepareUserGrantQuery sql err", + prepare: prepareUserGrantQuery, + want: want{ + sqlExpectations: mockQueryErr( + userGrantStmt, + 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, + }, + { + name: "prepareUserGrantsQuery no result", + prepare: prepareUserGrantsQuery, + want: want{ + sqlExpectations: mockQueries( + userGrantsStmt, + nil, + nil, + ), + }, + object: &UserGrants{UserGrants: []*UserGrant{}}, + }, + { + name: "prepareUserGrantsQuery one grant", + prepare: prepareUserGrantsQuery, + want: want{ + sqlExpectations: mockQueries( + userGrantsStmt, + userGrantsCols, + [][]driver.Value{ + { + "id", + testNow, + testNow, + 20211111, + "grant-id", + pq.StringArray{"role-key"}, + domain.UserGrantStateActive, + "user-id", + "username", + domain.UserTypeHuman, + "resource-owner", + "first-name", + "last-name", + "email", + "display-name", + "avatar-key", + "ro", + "org-name", + "primary-domain", + "project-id", + "project-name", + }, + }, + ), + }, + object: &UserGrants{ + SearchResponse: SearchResponse{ + Count: 1, + }, + UserGrants: []*UserGrant{ + { + ID: "id", + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211111, + Roles: []string{"role-key"}, + GrantID: "grant-id", + State: domain.UserGrantStateActive, + UserID: "user-id", + Username: "username", + UserType: domain.UserTypeHuman, + UserResourceOwner: "resource-owner", + FirstName: "first-name", + LastName: "last-name", + Email: "email", + DisplayName: "display-name", + AvatarURL: "avatar-key", + ResourceOwner: "ro", + OrgName: "org-name", + OrgPrimaryDomain: "primary-domain", + ProjectID: "project-id", + ProjectName: "project-name", + }, + }, + }, + }, + { + name: "prepareUserGrantsQuery one grant (machine user)", + prepare: prepareUserGrantsQuery, + want: want{ + sqlExpectations: mockQueries( + userGrantsStmt, + userGrantsCols, + [][]driver.Value{ + { + "id", + testNow, + testNow, + 20211111, + "grant-id", + pq.StringArray{"role-key"}, + domain.UserGrantStateActive, + "user-id", + "username", + domain.UserTypeMachine, + "resource-owner", + nil, + nil, + nil, + nil, + nil, + "ro", + "org-name", + "primary-domain", + "project-id", + "project-name", + }, + }, + ), + }, + object: &UserGrants{ + SearchResponse: SearchResponse{ + Count: 1, + }, + UserGrants: []*UserGrant{ + { + ID: "id", + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211111, + Roles: []string{"role-key"}, + GrantID: "grant-id", + State: domain.UserGrantStateActive, + UserID: "user-id", + Username: "username", + UserType: domain.UserTypeMachine, + UserResourceOwner: "resource-owner", + FirstName: "", + LastName: "", + Email: "", + DisplayName: "", + AvatarURL: "", + ResourceOwner: "ro", + OrgName: "org-name", + OrgPrimaryDomain: "primary-domain", + ProjectID: "project-id", + ProjectName: "project-name", + }, + }, + }, + }, + { + name: "prepareUserGrantsQuery one grant (no org)", + prepare: prepareUserGrantsQuery, + want: want{ + sqlExpectations: mockQueries( + userGrantsStmt, + userGrantsCols, + [][]driver.Value{ + { + "id", + testNow, + testNow, + 20211111, + "grant-id", + pq.StringArray{"role-key"}, + domain.UserGrantStateActive, + "user-id", + "username", + domain.UserTypeMachine, + "resource-owner", + "first-name", + "last-name", + "email", + "display-name", + "avatar-key", + "ro", + nil, + nil, + "project-id", + "project-name", + }, + }, + ), + }, + object: &UserGrants{ + SearchResponse: SearchResponse{ + Count: 1, + }, + UserGrants: []*UserGrant{ + { + ID: "id", + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211111, + Roles: []string{"role-key"}, + GrantID: "grant-id", + State: domain.UserGrantStateActive, + UserID: "user-id", + Username: "username", + UserType: domain.UserTypeMachine, + UserResourceOwner: "resource-owner", + FirstName: "first-name", + LastName: "last-name", + Email: "email", + DisplayName: "display-name", + AvatarURL: "avatar-key", + ResourceOwner: "ro", + OrgName: "", + OrgPrimaryDomain: "", + ProjectID: "project-id", + ProjectName: "project-name", + }, + }, + }, + }, + { + name: "prepareUserGrantsQuery one grant (no project)", + prepare: prepareUserGrantsQuery, + want: want{ + sqlExpectations: mockQueries( + userGrantsStmt, + userGrantsCols, + [][]driver.Value{ + { + "id", + testNow, + testNow, + 20211111, + "grant-id", + pq.StringArray{"role-key"}, + domain.UserGrantStateActive, + "user-id", + "username", + domain.UserTypeHuman, + "resource-owner", + "first-name", + "last-name", + "email", + "display-name", + "avatar-key", + "ro", + "org-name", + "primary-domain", + "project-id", + nil, + }, + }, + ), + }, + object: &UserGrants{ + SearchResponse: SearchResponse{ + Count: 1, + }, + UserGrants: []*UserGrant{ + { + ID: "id", + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211111, + Roles: []string{"role-key"}, + GrantID: "grant-id", + State: domain.UserGrantStateActive, + UserID: "user-id", + Username: "username", + UserType: domain.UserTypeHuman, + UserResourceOwner: "resource-owner", + FirstName: "first-name", + LastName: "last-name", + Email: "email", + DisplayName: "display-name", + AvatarURL: "avatar-key", + ResourceOwner: "ro", + OrgName: "org-name", + OrgPrimaryDomain: "primary-domain", + ProjectID: "project-id", + ProjectName: "", + }, + }, + }, + }, + { + name: "prepareUserGrantsQuery multiple grants", + prepare: prepareUserGrantsQuery, + want: want{ + sqlExpectations: mockQueries( + userGrantsStmt, + userGrantsCols, + [][]driver.Value{ + { + "id", + testNow, + testNow, + 20211111, + "grant-id", + pq.StringArray{"role-key"}, + domain.UserGrantStateActive, + "user-id", + "username", + domain.UserTypeHuman, + "resource-owner", + "first-name", + "last-name", + "email", + "display-name", + "avatar-key", + "ro", + "org-name", + "primary-domain", + "project-id", + "project-name", + }, + { + "id", + testNow, + testNow, + 20211111, + "grant-id", + pq.StringArray{"role-key"}, + domain.UserGrantStateActive, + "user-id", + "username", + domain.UserTypeHuman, + "resource-owner", + "first-name", + "last-name", + "email", + "display-name", + "avatar-key", + "ro", + "org-name", + "primary-domain", + "project-id", + "project-name", + }, + }, + ), + }, + object: &UserGrants{ + SearchResponse: SearchResponse{ + Count: 2, + }, + UserGrants: []*UserGrant{ + { + ID: "id", + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211111, + Roles: []string{"role-key"}, + GrantID: "grant-id", + State: domain.UserGrantStateActive, + UserID: "user-id", + Username: "username", + UserType: domain.UserTypeHuman, + UserResourceOwner: "resource-owner", + FirstName: "first-name", + LastName: "last-name", + Email: "email", + DisplayName: "display-name", + AvatarURL: "avatar-key", + ResourceOwner: "ro", + OrgName: "org-name", + OrgPrimaryDomain: "primary-domain", + ProjectID: "project-id", + ProjectName: "project-name", + }, + { + ID: "id", + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211111, + Roles: []string{"role-key"}, + GrantID: "grant-id", + State: domain.UserGrantStateActive, + UserID: "user-id", + Username: "username", + UserType: domain.UserTypeHuman, + UserResourceOwner: "resource-owner", + FirstName: "first-name", + LastName: "last-name", + Email: "email", + DisplayName: "display-name", + AvatarURL: "avatar-key", + ResourceOwner: "ro", + OrgName: "org-name", + OrgPrimaryDomain: "primary-domain", + ProjectID: "project-id", + ProjectName: "project-name", + }, + }, + }, + }, + { + name: "prepareUserGrantsQuery sql err", + prepare: prepareUserGrantsQuery, + want: want{ + sqlExpectations: mockQueryErr( + userGrantsStmt, + 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/usergrant/model/user_grant_view.go b/internal/usergrant/model/user_grant_view.go index a49ac5062..f285079e4 100644 --- a/internal/usergrant/model/user_grant_view.go +++ b/internal/usergrant/model/user_grant_view.go @@ -102,11 +102,3 @@ func (r *UserGrantSearchRequest) GetSearchQuery(key UserGrantSearchKey) (int, *U } return -1, nil } - -func (r *UserGrantSearchRequest) AppendMyOrgQuery(orgID string) { - r.Queries = append(r.Queries, &UserGrantSearchQuery{Key: UserGrantSearchKeyResourceOwner, Method: domain.SearchMethodEquals, Value: orgID}) -} - -func (r *UserGrantSearchRequest) AppendProjectIDQuery(projectID string) { - r.Queries = append(r.Queries, &UserGrantSearchQuery{Key: UserGrantSearchKeyProjectID, Method: domain.SearchMethodEquals, Value: projectID}) -} diff --git a/migrations/cockroach/V1.105__user_type.sql b/migrations/cockroach/V1.105__user_type.sql new file mode 100644 index 000000000..01a67b6e4 --- /dev/null +++ b/migrations/cockroach/V1.105__user_type.sql @@ -0,0 +1,15 @@ +ALTER TABLE zitadel.projections.users ADD COLUMN type INT2; + +-- human is 1 +-- machine is 2 +WITH doa AS ( + SELECT u.id, IF(h.user_id IS NULL, 2, 1) as type + FROM zitadel.projections.users u + LEFT JOIN zitadel.projections.users_humans h + ON h.user_id = u.id + LEFT JOIN zitadel.projections.users_machines m + ON m.user_id = u.id +) +UPDATE zitadel.projections.users SET type = doa.type FROM doa WHERE doa.id = zitadel.projections.users.id; + +ALTER TABLE zitadel.projections.users_humans RENAME COLUMN avater_key to avatar_key; diff --git a/proto/zitadel/user.proto b/proto/zitadel/user.proto index 8dd905977..b6317b41d 100644 --- a/proto/zitadel/user.proto +++ b/proto/zitadel/user.proto @@ -684,6 +684,7 @@ message UserGrantQuery { UserGrantOrgDomainQuery org_domain_query = 11; UserGrantProjectNameQuery project_name_query = 12; UserGrantDisplayNameQuery display_name_query = 13; + UserGrantUserTypeQuery user_type_query = 14; } } @@ -853,4 +854,13 @@ message UserGrantDisplayNameQuery { ]; } +message UserGrantUserTypeQuery { + Type type = 1 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "type of user" + example: "\"TYPE_HUMAN\""; + } + ]; +} + //PLANNED: login name query \ No newline at end of file