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 <livio.a@gmail.com>
Co-authored-by: fabi <fabienne.gerschwiler@gmail.com>
This commit is contained in:
Silvan 2022-01-14 10:45:50 +01:00 committed by GitHub
parent a63a995269
commit c542cab4f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 1546 additions and 816 deletions

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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,

View File

@ -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,

View File

@ -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,
),

View File

@ -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 {
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
}
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 := &model.UserGrantSearchRequest{
request := &query.UserGrantsQueries{
SearchRequest: query.SearchRequest{
Offset: offset,
Limit: limit,
Asc: asc,
Queries: user_grpc.UserGrantQueriesToModel(req.Queries),
},
Queries: queries,
}
request.Queries = append(request.Queries, &model.UserGrantSearchQuery{
Key: model.UserGrantSearchKeyResourceOwner,
Method: domain.SearchMethodEquals,
Value: authz.GetCtxData(ctx).OrgID,
})
return request
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 {

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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}),
}

View File

@ -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
}

View File

@ -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

View File

@ -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)
}

View File

@ -5,6 +5,5 @@ type Repository interface {
ProjectRepository
OrgRepository
UserRepository
UserGrantRepository
IamRepository
}

View File

@ -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)
}

View File

@ -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",
}
)

View File

@ -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",
}
)

View File

@ -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",
}
)

View File

@ -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",
}
)

View File

@ -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(

View File

@ -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,
},
},
{

View File

@ -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
}

View File

@ -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,10 +333,12 @@ func TestTextQuery_comp(t *testing.T) {
Compare: TextListContains,
},
want: want{
query: "test_table.test_col @> ? ",
query: &listContains{
col: testCol,
args: []interface{}{pq.StringArray{"Hurst"}},
},
},
},
{
name: "too high comparison",
fields: fields{
@ -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)
}
})
}
}

View File

@ -39,6 +39,10 @@ var (
name: projection.UserUsernameCol,
table: userTable,
}
UserTypeCol = Column{
name: projection.UserTypeCol,
table: userTable,
}
)
var (

View File

@ -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
}
}

View File

@ -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)
})
}
}

View File

@ -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})
}

View File

@ -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;

View File

@ -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