mirror of
https://github.com/zitadel/zitadel.git
synced 2025-01-13 07:33:41 +00:00
feat(queries): user membership (#2768)
* 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 * fix(query): member queries and user avatar column * member cols * fix(queries): membership stmt * fix user test * fix user test
This commit is contained in:
parent
2cdb297138
commit
2265fffd8e
@ -29,18 +29,18 @@ func (s *Server) ListMyProjectPermissions(ctx context.Context, _ *auth_pb.ListMy
|
||||
}
|
||||
|
||||
func (s *Server) ListMyMemberships(ctx context.Context, req *auth_pb.ListMyMembershipsRequest) (*auth_pb.ListMyMembershipsResponse, error) {
|
||||
request, err := ListMyMembershipsRequestToModel(req)
|
||||
request, err := ListMyMembershipsRequestToModel(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response, err := s.repo.SearchMyUserMemberships(ctx, request)
|
||||
response, err := s.query.Memberships(ctx, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &auth_pb.ListMyMembershipsResponse{
|
||||
Result: user_grpc.MembershipsToMembershipsPb(response.Result),
|
||||
Result: user_grpc.MembershipsToMembershipsPb(response.Memberships),
|
||||
Details: obj_grpc.ToListDetails(
|
||||
response.TotalResult,
|
||||
response.Count,
|
||||
response.Sequence,
|
||||
response.Timestamp,
|
||||
),
|
||||
|
@ -1,23 +1,33 @@
|
||||
package auth
|
||||
|
||||
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"
|
||||
user_model "github.com/caos/zitadel/internal/user/model"
|
||||
"github.com/caos/zitadel/internal/query"
|
||||
auth_pb "github.com/caos/zitadel/pkg/grpc/auth"
|
||||
)
|
||||
|
||||
func ListMyMembershipsRequestToModel(req *auth_pb.ListMyMembershipsRequest) (*user_model.UserMembershipSearchRequest, error) {
|
||||
func ListMyMembershipsRequestToModel(ctx context.Context, req *auth_pb.ListMyMembershipsRequest) (*query.MembershipSearchQuery, error) {
|
||||
offset, limit, asc := object.ListQueryToModel(req.Query)
|
||||
queries, err := user_grpc.MembershipQueriesToModel(req.Queries)
|
||||
queries, err := user_grpc.MembershipQueriesToQuery(req.Queries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user_model.UserMembershipSearchRequest{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
//SortingColumn: //TODO: sorting
|
||||
userQuery, err := query.NewMembershipUserIDQuery(authz.GetCtxData(ctx).UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queries = append(queries, userQuery)
|
||||
return &query.MembershipSearchQuery{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
//SortingColumn: //TODO: sorting
|
||||
},
|
||||
Queries: queries,
|
||||
}, nil
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/api/grpc/change"
|
||||
"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/org"
|
||||
user_grpc "github.com/caos/zitadel/internal/api/grpc/user"
|
||||
@ -102,7 +101,7 @@ func (s *Server) UpdateMyUserName(ctx context.Context, req *auth_pb.UpdateMyUser
|
||||
return nil, err
|
||||
}
|
||||
return &auth_pb.UpdateMyUserNameResponse{
|
||||
Details: object.DomainToChangeDetailsPb(objectDetails),
|
||||
Details: obj_grpc.DomainToChangeDetailsPb(objectDetails),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -121,7 +120,7 @@ func (s *Server) ListMyUserGrants(ctx context.Context, req *auth_pb.ListMyUserGr
|
||||
}
|
||||
return &auth_pb.ListMyUserGrantsResponse{
|
||||
Result: UserGrantsToPb(res.Result),
|
||||
Details: object.ToListDetails(
|
||||
Details: obj_grpc.ToListDetails(
|
||||
res.TotalResult,
|
||||
res.Sequence,
|
||||
res.Timestamp,
|
||||
@ -140,13 +139,13 @@ func (s *Server) ListMyProjectOrgs(ctx context.Context, req *auth_pb.ListMyProje
|
||||
}
|
||||
return &auth_pb.ListMyProjectOrgsResponse{
|
||||
//TODO: not all details
|
||||
Details: object.ToListDetails(res.TotalResult, 0, time.Time{}),
|
||||
Details: obj_grpc.ToListDetails(res.TotalResult, 0, time.Time{}),
|
||||
Result: org.OrgsToPb(res.Result),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ListMyProjectOrgsRequestToModel(req *auth_pb.ListMyProjectOrgsRequest) (*grant_model.UserGrantSearchRequest, error) {
|
||||
offset, limit, asc := object.ListQueryToModel(req.Query)
|
||||
offset, limit, asc := obj_grpc.ListQueryToModel(req.Query)
|
||||
queries, err := org.OrgQueriesToUserGrantModel(req.Queries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -634,18 +634,18 @@ func (s *Server) RemoveHumanLinkedIDP(ctx context.Context, req *mgmt_pb.RemoveHu
|
||||
}
|
||||
|
||||
func (s *Server) ListUserMemberships(ctx context.Context, req *mgmt_pb.ListUserMembershipsRequest) (*mgmt_pb.ListUserMembershipsResponse, error) {
|
||||
request, err := ListUserMembershipsRequestToModel(req)
|
||||
request, err := ListUserMembershipsRequestToModel(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response, err := s.user.SearchUserMemberships(ctx, request)
|
||||
response, err := s.query.Memberships(ctx, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.ListUserMembershipsResponse{
|
||||
Result: user_grpc.MembershipsToMembershipsPb(response.Result),
|
||||
Result: user_grpc.MembershipsToMembershipsPb(response.Memberships),
|
||||
Details: obj_grpc.ToListDetails(
|
||||
response.TotalResult,
|
||||
response.Count,
|
||||
response.Sequence,
|
||||
response.Timestamp,
|
||||
),
|
||||
|
@ -255,21 +255,27 @@ func ListHumanLinkedIDPsRequestToQuery(ctx context.Context, req *mgmt_pb.ListHum
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ListUserMembershipsRequestToModel(req *mgmt_pb.ListUserMembershipsRequest) (*user_model.UserMembershipSearchRequest, error) {
|
||||
func ListUserMembershipsRequestToModel(ctx context.Context, req *mgmt_pb.ListUserMembershipsRequest) (*query.MembershipSearchQuery, error) {
|
||||
offset, limit, asc := object.ListQueryToModel(req.Query)
|
||||
queries, err := user_grpc.MembershipQueriesToModel(req.Queries)
|
||||
queries, err := user_grpc.MembershipQueriesToQuery(req.Queries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queries = append(queries, &user_model.UserMembershipSearchQuery{
|
||||
Key: user_model.UserMembershipSearchKeyUserID,
|
||||
Method: domain.SearchMethodEquals,
|
||||
Value: req.UserId,
|
||||
})
|
||||
return &user_model.UserMembershipSearchRequest{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
userQuery, err := query.NewMembershipUserIDQuery(req.UserId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ownerQuery, err := query.NewMembershipResourceOwnerQuery(authz.GetCtxData(ctx).OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queries = append(queries, userQuery, ownerQuery)
|
||||
return &query.MembershipSearchQuery{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
},
|
||||
//SortingColumn: //TODO: sorting
|
||||
Queries: queries,
|
||||
}, nil
|
||||
|
@ -124,7 +124,7 @@ func TextMethodToQuery(method object_pb.TextQueryMethod) query.TextComparison {
|
||||
|
||||
func ListQueryToModel(query *object_pb.ListQuery) (offset, limit uint64, asc bool) {
|
||||
if query == nil {
|
||||
return
|
||||
return 0, 0, false
|
||||
}
|
||||
return query.Offset, uint64(query.Limit), query.Asc
|
||||
}
|
||||
|
@ -4,34 +4,35 @@ import (
|
||||
"github.com/caos/zitadel/internal/api/grpc/object"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/query"
|
||||
user_model "github.com/caos/zitadel/internal/user/model"
|
||||
user_pb "github.com/caos/zitadel/pkg/grpc/user"
|
||||
)
|
||||
|
||||
func MembershipQueriesToModel(queries []*user_pb.MembershipQuery) (_ []*user_model.UserMembershipSearchQuery, err error) {
|
||||
q := make([]*user_model.UserMembershipSearchQuery, 0)
|
||||
func MembershipQueriesToQuery(queries []*user_pb.MembershipQuery) (_ []query.SearchQuery, err error) {
|
||||
q := make([]query.SearchQuery, 0)
|
||||
for _, query := range queries {
|
||||
qs, err := MembershipQueryToModel(query)
|
||||
qs, err := MembershipQueryToQuery(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q = append(q, qs...)
|
||||
q = append(q, qs)
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func MembershipQueryToModel(query *user_pb.MembershipQuery) ([]*user_model.UserMembershipSearchQuery, error) {
|
||||
switch q := query.Query.(type) {
|
||||
func MembershipQueryToQuery(req *user_pb.MembershipQuery) (query.SearchQuery, error) {
|
||||
switch q := req.Query.(type) {
|
||||
case *user_pb.MembershipQuery_OrgQuery:
|
||||
return MembershipOrgQueryToModel(q.OrgQuery), nil
|
||||
return query.NewMembershipOrgIDQuery(q.OrgQuery.OrgId)
|
||||
case *user_pb.MembershipQuery_ProjectQuery:
|
||||
return MembershipProjectQueryToModel(q.ProjectQuery), nil
|
||||
return query.NewMembershipProjectIDQuery(q.ProjectQuery.ProjectId)
|
||||
case *user_pb.MembershipQuery_ProjectGrantQuery:
|
||||
return MembershipProjectGrantQueryToModel(q.ProjectGrantQuery), nil
|
||||
return query.NewMembershipProjectGrantIDQuery(q.ProjectGrantQuery.ProjectGrantId)
|
||||
case *user_pb.MembershipQuery_IamQuery:
|
||||
return MembershipIAMQueryToModel(q.IamQuery), nil
|
||||
return query.NewMembershipIsIAMQuery()
|
||||
default:
|
||||
return nil, errors.ThrowInvalidArgument(nil, "USER-dsg3z", "List.Query.Invalid")
|
||||
return nil, errors.ThrowInvalidArgument(nil, "USER-dsg3z", "Errors.List.Query.Invalid")
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,7 +92,7 @@ func MembershipProjectGrantQueryToModel(q *user_pb.MembershipProjectGrantQuery)
|
||||
}
|
||||
}
|
||||
|
||||
func MembershipsToMembershipsPb(memberships []*user_model.UserMembershipView) []*user_pb.Membership {
|
||||
func MembershipsToMembershipsPb(memberships []*query.Membership) []*user_pb.Membership {
|
||||
converted := make([]*user_pb.Membership, len(memberships))
|
||||
for i, membership := range memberships {
|
||||
converted[i] = MembershipToMembershipPb(membership)
|
||||
@ -99,7 +100,7 @@ func MembershipsToMembershipsPb(memberships []*user_model.UserMembershipView) []
|
||||
return converted
|
||||
}
|
||||
|
||||
func MembershipToMembershipPb(membership *user_model.UserMembershipView) *user_pb.Membership {
|
||||
func MembershipToMembershipPb(membership *query.Membership) *user_pb.Membership {
|
||||
return &user_pb.Membership{
|
||||
UserId: membership.UserID,
|
||||
Type: memberTypeToPb(membership),
|
||||
@ -114,25 +115,23 @@ func MembershipToMembershipPb(membership *user_model.UserMembershipView) *user_p
|
||||
}
|
||||
}
|
||||
|
||||
func memberTypeToPb(membership *user_model.UserMembershipView) user_pb.MembershipType {
|
||||
switch membership.MemberType {
|
||||
case user_model.MemberTypeOrganisation:
|
||||
func memberTypeToPb(membership *query.Membership) user_pb.MembershipType {
|
||||
if membership.Org != nil {
|
||||
return &user_pb.Membership_OrgId{
|
||||
OrgId: membership.AggregateID,
|
||||
OrgId: membership.Org.OrgID,
|
||||
}
|
||||
case user_model.MemberTypeProject:
|
||||
} else if membership.Project != nil {
|
||||
return &user_pb.Membership_ProjectId{
|
||||
ProjectId: membership.AggregateID,
|
||||
ProjectId: membership.Project.ProjectID,
|
||||
}
|
||||
case user_model.MemberTypeProjectGrant:
|
||||
} else if membership.ProjectGrant != nil {
|
||||
return &user_pb.Membership_ProjectGrantId{
|
||||
ProjectGrantId: membership.ObjectID,
|
||||
ProjectGrantId: membership.ProjectGrant.GrantID,
|
||||
}
|
||||
case user_model.MemberTypeIam:
|
||||
} else if membership.IAM != nil {
|
||||
return &user_pb.Membership_Iam{
|
||||
Iam: true, //TODO: ?
|
||||
Iam: true,
|
||||
}
|
||||
default:
|
||||
return nil //TODO: ?
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -38,11 +38,16 @@ func prepareLatestSequence() (sq.SelectBuilder, func(*sql.Row) (*LatestSequence,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *Queries) latestSequence(ctx context.Context, projection table) (*LatestSequence, error) {
|
||||
func (q *Queries) latestSequence(ctx context.Context, projections ...table) (*LatestSequence, error) {
|
||||
query, scan := prepareLatestSequence()
|
||||
stmt, args, err := query.Where(sq.Eq{
|
||||
CurrentSequenceColProjectionName.identifier(): projection.name,
|
||||
}).ToSql()
|
||||
or := make(sq.Or, len(projections))
|
||||
for i, projection := range projections {
|
||||
or[i] = sq.Eq{CurrentSequenceColProjectionName.identifier(): projection.name}
|
||||
}
|
||||
stmt, args, err := query.
|
||||
Where(or).
|
||||
OrderBy(CurrentSequenceColCurrentSequence.identifier()).
|
||||
ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-5CfX9", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
38
internal/query/iam_member.go
Normal file
38
internal/query/iam_member.go
Normal file
@ -0,0 +1,38 @@
|
||||
package query
|
||||
|
||||
import "github.com/caos/zitadel/internal/query/projection"
|
||||
|
||||
var (
|
||||
iamMemberTable = table{
|
||||
name: projection.IAMMemberProjectionTable,
|
||||
alias: "members",
|
||||
}
|
||||
IAMMemberUserID = Column{
|
||||
name: projection.MemberUserIDCol,
|
||||
table: iamMemberTable,
|
||||
}
|
||||
IAMMemberRoles = Column{
|
||||
name: projection.MemberRolesCol,
|
||||
table: iamMemberTable,
|
||||
}
|
||||
IAMMemberCreationDate = Column{
|
||||
name: projection.MemberCreationDate,
|
||||
table: iamMemberTable,
|
||||
}
|
||||
IAMMemberChangeDate = Column{
|
||||
name: projection.MemberChangeDate,
|
||||
table: iamMemberTable,
|
||||
}
|
||||
IAMMemberSequence = Column{
|
||||
name: projection.MemberSequence,
|
||||
table: iamMemberTable,
|
||||
}
|
||||
IAMMemberResourceOwner = Column{
|
||||
name: projection.MemberResourceOwner,
|
||||
table: iamMemberTable,
|
||||
}
|
||||
IAMMemberIAMID = Column{
|
||||
name: projection.IAMMemberIAMIDCol,
|
||||
table: iamMemberTable,
|
||||
}
|
||||
)
|
@ -2,9 +2,67 @@ package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
|
||||
"github.com/caos/zitadel/internal/query/projection"
|
||||
"github.com/caos/zitadel/internal/telemetry/tracing"
|
||||
)
|
||||
|
||||
type MembersQuery struct {
|
||||
SearchRequest
|
||||
Queries []SearchQuery
|
||||
}
|
||||
|
||||
func (q *MembersQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
|
||||
query = q.SearchRequest.toQuery(query)
|
||||
for _, q := range q.Queries {
|
||||
query = q.toQuery(query)
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
func NewMemberEmailSearchQuery(method TextComparison, value string) (SearchQuery, error) {
|
||||
return NewTextQuery(HumanEmailCol, value, method)
|
||||
}
|
||||
|
||||
func NewMemberFirstNameSearchQuery(method TextComparison, value string) (SearchQuery, error) {
|
||||
return NewTextQuery(HumanFirstNameCol, value, method)
|
||||
}
|
||||
|
||||
func NewMemberLastNameSearchQuery(method TextComparison, value string) (SearchQuery, error) {
|
||||
return NewTextQuery(HumanLastNameCol, value, method)
|
||||
}
|
||||
|
||||
func NewMemberUserIDSearchQuery(value string) (SearchQuery, error) {
|
||||
return NewTextQuery(memberUserID, value, TextEquals)
|
||||
}
|
||||
func NewMemberResourceOwnerSearchQuery(value string) (SearchQuery, error) {
|
||||
return NewTextQuery(memberResourceOwner, value, TextEquals)
|
||||
}
|
||||
|
||||
type Members struct {
|
||||
SearchResponse
|
||||
Members []*Member
|
||||
}
|
||||
|
||||
type Member struct {
|
||||
CreationDate time.Time
|
||||
ChangeDate time.Time
|
||||
Sequence uint64
|
||||
ResourceOwner string
|
||||
|
||||
UserID string
|
||||
Roles []string
|
||||
PreferredLoginName string
|
||||
Email string
|
||||
FirstName string
|
||||
LastName string
|
||||
DisplayName string
|
||||
AvatarURL string
|
||||
}
|
||||
|
||||
func (r *Queries) IAMMemberByID(ctx context.Context, iamID, userID string) (member *IAMMemberReadModel, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
@ -17,3 +75,18 @@ func (r *Queries) IAMMemberByID(ctx context.Context, iamID, userID string) (memb
|
||||
|
||||
return member, nil
|
||||
}
|
||||
|
||||
var (
|
||||
memberTableAlias = table{
|
||||
name: "members",
|
||||
alias: "members",
|
||||
}
|
||||
memberUserID = Column{
|
||||
name: projection.MemberUserIDCol,
|
||||
table: memberTableAlias,
|
||||
}
|
||||
memberResourceOwner = Column{
|
||||
name: projection.MemberResourceOwner,
|
||||
table: memberTableAlias,
|
||||
}
|
||||
)
|
||||
|
38
internal/query/org_member.go
Normal file
38
internal/query/org_member.go
Normal file
@ -0,0 +1,38 @@
|
||||
package query
|
||||
|
||||
import "github.com/caos/zitadel/internal/query/projection"
|
||||
|
||||
var (
|
||||
orgMemberTable = table{
|
||||
name: projection.OrgMemberProjectionTable,
|
||||
alias: "members",
|
||||
}
|
||||
OrgMemberUserID = Column{
|
||||
name: projection.MemberUserIDCol,
|
||||
table: orgMemberTable,
|
||||
}
|
||||
OrgMemberRoles = Column{
|
||||
name: projection.MemberRolesCol,
|
||||
table: orgMemberTable,
|
||||
}
|
||||
OrgMemberCreationDate = Column{
|
||||
name: projection.MemberCreationDate,
|
||||
table: orgMemberTable,
|
||||
}
|
||||
OrgMemberChangeDate = Column{
|
||||
name: projection.MemberChangeDate,
|
||||
table: orgMemberTable,
|
||||
}
|
||||
OrgMemberSequence = Column{
|
||||
name: projection.MemberSequence,
|
||||
table: orgMemberTable,
|
||||
}
|
||||
OrgMemberResourceOwner = Column{
|
||||
name: projection.MemberResourceOwner,
|
||||
table: orgMemberTable,
|
||||
}
|
||||
OrgMemberOrgID = Column{
|
||||
name: projection.OrgMemberOrgIDCol,
|
||||
table: orgMemberTable,
|
||||
}
|
||||
)
|
42
internal/query/project_grant_member.go
Normal file
42
internal/query/project_grant_member.go
Normal file
@ -0,0 +1,42 @@
|
||||
package query
|
||||
|
||||
import "github.com/caos/zitadel/internal/query/projection"
|
||||
|
||||
var (
|
||||
projectGrantMemberTable = table{
|
||||
name: projection.ProjectGrantMemberProjectionTable,
|
||||
alias: "members",
|
||||
}
|
||||
ProjectGrantMemberUserID = Column{
|
||||
name: projection.MemberUserIDCol,
|
||||
table: projectGrantMemberTable,
|
||||
}
|
||||
ProjectGrantMemberRoles = Column{
|
||||
name: projection.MemberRolesCol,
|
||||
table: projectGrantMemberTable,
|
||||
}
|
||||
ProjectGrantMemberCreationDate = Column{
|
||||
name: projection.MemberCreationDate,
|
||||
table: projectGrantMemberTable,
|
||||
}
|
||||
ProjectGrantMemberChangeDate = Column{
|
||||
name: projection.MemberChangeDate,
|
||||
table: projectGrantMemberTable,
|
||||
}
|
||||
ProjectGrantMemberSequence = Column{
|
||||
name: projection.MemberSequence,
|
||||
table: projectGrantMemberTable,
|
||||
}
|
||||
ProjectGrantMemberResourceOwner = Column{
|
||||
name: projection.MemberResourceOwner,
|
||||
table: projectGrantMemberTable,
|
||||
}
|
||||
ProjectGrantMemberProjectID = Column{
|
||||
name: projection.ProjectGrantMemberProjectIDCol,
|
||||
table: projectGrantMemberTable,
|
||||
}
|
||||
ProjectGrantMemberGrantID = Column{
|
||||
name: projection.ProjectGrantMemberGrantIDCol,
|
||||
table: projectGrantMemberTable,
|
||||
}
|
||||
)
|
38
internal/query/project_member.go
Normal file
38
internal/query/project_member.go
Normal file
@ -0,0 +1,38 @@
|
||||
package query
|
||||
|
||||
import "github.com/caos/zitadel/internal/query/projection"
|
||||
|
||||
var (
|
||||
projectMemberTable = table{
|
||||
name: projection.ProjectMemberProjectionTable,
|
||||
alias: "members",
|
||||
}
|
||||
ProjectMemberUserID = Column{
|
||||
name: projection.MemberUserIDCol,
|
||||
table: projectMemberTable,
|
||||
}
|
||||
ProjectMemberRoles = Column{
|
||||
name: projection.MemberRolesCol,
|
||||
table: projectMemberTable,
|
||||
}
|
||||
ProjectMemberCreationDate = Column{
|
||||
name: projection.MemberCreationDate,
|
||||
table: projectMemberTable,
|
||||
}
|
||||
ProjectMemberChangeDate = Column{
|
||||
name: projection.MemberChangeDate,
|
||||
table: projectMemberTable,
|
||||
}
|
||||
ProjectMemberSequence = Column{
|
||||
name: projection.MemberSequence,
|
||||
table: projectMemberTable,
|
||||
}
|
||||
ProjectMemberResourceOwner = Column{
|
||||
name: projection.MemberResourceOwner,
|
||||
table: projectMemberTable,
|
||||
}
|
||||
ProjectMemberProjectID = Column{
|
||||
name: projection.ProjectMemberProjectIDCol,
|
||||
table: projectMemberTable,
|
||||
}
|
||||
)
|
@ -47,7 +47,7 @@ const (
|
||||
HumanUserIDCol = "user_id"
|
||||
|
||||
// profile
|
||||
HumanFistNameCol = "first_name"
|
||||
HumanFirstNameCol = "first_name"
|
||||
HumanLastNameCol = "last_name"
|
||||
HumanNickNameCol = "nick_name"
|
||||
HumanDisplayNameCol = "display_name"
|
||||
@ -208,7 +208,7 @@ func (p *UserProjection) reduceHumanAdded(event eventstore.EventReader) (*handle
|
||||
crdb.AddCreateStatement(
|
||||
[]handler.Column{
|
||||
handler.NewCol(HumanUserIDCol, e.Aggregate().ID),
|
||||
handler.NewCol(HumanFistNameCol, e.FirstName),
|
||||
handler.NewCol(HumanFirstNameCol, e.FirstName),
|
||||
handler.NewCol(HumanLastNameCol, e.LastName),
|
||||
handler.NewCol(HumanNickNameCol, &sql.NullString{String: e.NickName, Valid: e.NickName != ""}),
|
||||
handler.NewCol(HumanDisplayNameCol, &sql.NullString{String: e.DisplayName, Valid: e.DisplayName != ""}),
|
||||
@ -244,7 +244,7 @@ func (p *UserProjection) reduceHumanRegistered(event eventstore.EventReader) (*h
|
||||
crdb.AddCreateStatement(
|
||||
[]handler.Column{
|
||||
handler.NewCol(HumanUserIDCol, e.Aggregate().ID),
|
||||
handler.NewCol(HumanFistNameCol, e.FirstName),
|
||||
handler.NewCol(HumanFirstNameCol, e.FirstName),
|
||||
handler.NewCol(HumanLastNameCol, e.LastName),
|
||||
handler.NewCol(HumanNickNameCol, &sql.NullString{String: e.NickName, Valid: e.NickName != ""}),
|
||||
handler.NewCol(HumanDisplayNameCol, &sql.NullString{String: e.DisplayName, Valid: e.DisplayName != ""}),
|
||||
@ -381,7 +381,7 @@ func (p *UserProjection) reduceHumanProfileChanged(event eventstore.EventReader)
|
||||
}
|
||||
cols := make([]handler.Column, 0, 6)
|
||||
if e.FirstName != "" {
|
||||
cols = append(cols, handler.NewCol(HumanFistNameCol, e.FirstName))
|
||||
cols = append(cols, handler.NewCol(HumanFirstNameCol, e.FirstName))
|
||||
}
|
||||
|
||||
if e.LastName != "" {
|
||||
|
@ -46,6 +46,23 @@ type SearchQuery interface {
|
||||
toQuery(sq.SelectBuilder) sq.SelectBuilder
|
||||
}
|
||||
|
||||
type NotNullQuery struct {
|
||||
Column Column
|
||||
}
|
||||
|
||||
func NewNotNullQuery(col Column) (*NotNullQuery, error) {
|
||||
if col.isZero() {
|
||||
return nil, ErrMissingColumn
|
||||
}
|
||||
return &NotNullQuery{
|
||||
Column: col,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (q *NotNullQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
|
||||
return query.Where(sq.NotEq{q.Column.identifier(): nil})
|
||||
}
|
||||
|
||||
type TextQuery struct {
|
||||
Column Column
|
||||
Text string
|
||||
|
@ -2,7 +2,120 @@ package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/query/projection"
|
||||
)
|
||||
|
||||
var (
|
||||
userTable = table{
|
||||
name: projection.UserTable,
|
||||
}
|
||||
UserIDCol = Column{
|
||||
name: projection.UserIDCol,
|
||||
table: userTable,
|
||||
}
|
||||
UserCreationDateCol = Column{
|
||||
name: projection.UserCreationDateCol,
|
||||
table: userTable,
|
||||
}
|
||||
UserChangeDateCol = Column{
|
||||
name: projection.UserChangeDateCol,
|
||||
table: userTable,
|
||||
}
|
||||
UserResourceOwnerCol = Column{
|
||||
name: projection.UserResourceOwnerCol,
|
||||
table: userTable,
|
||||
}
|
||||
UserStateCol = Column{
|
||||
name: projection.UserStateCol,
|
||||
table: userTable,
|
||||
}
|
||||
UserSequenceCol = Column{
|
||||
name: projection.UserSequenceCol,
|
||||
table: userTable,
|
||||
}
|
||||
UserUsernameCol = Column{
|
||||
name: projection.UserUsernameCol,
|
||||
table: userTable,
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
humanTable = table{
|
||||
name: projection.UserHumanTable,
|
||||
}
|
||||
// profile
|
||||
HumanUserIDCol = Column{
|
||||
name: projection.HumanUserIDCol,
|
||||
table: humanTable,
|
||||
}
|
||||
HumanFirstNameCol = Column{
|
||||
name: projection.HumanFirstNameCol,
|
||||
table: humanTable,
|
||||
}
|
||||
HumanLastNameCol = Column{
|
||||
name: projection.HumanLastNameCol,
|
||||
table: humanTable,
|
||||
}
|
||||
HumanNickNameCol = Column{
|
||||
name: projection.HumanNickNameCol,
|
||||
table: humanTable,
|
||||
}
|
||||
HumanDisplayNameCol = Column{
|
||||
name: projection.HumanDisplayNameCol,
|
||||
table: humanTable,
|
||||
}
|
||||
HumanPreferredLanguageCol = Column{
|
||||
name: projection.HumanPreferredLanguageCol,
|
||||
table: humanTable,
|
||||
}
|
||||
HumanGenderCol = Column{
|
||||
name: projection.HumanGenderCol,
|
||||
table: humanTable,
|
||||
}
|
||||
HumanAvaterURLCol = Column{
|
||||
name: projection.HumanAvaterURLCol,
|
||||
table: humanTable,
|
||||
}
|
||||
|
||||
// email
|
||||
HumanEmailCol = Column{
|
||||
name: projection.HumanEmailCol,
|
||||
table: humanTable,
|
||||
}
|
||||
HumanIsEmailVerifiedCol = Column{
|
||||
name: projection.HumanIsEmailVerifiedCol,
|
||||
table: humanTable,
|
||||
}
|
||||
|
||||
// phone
|
||||
HumanPhoneCol = Column{
|
||||
name: projection.HumanPhoneCol,
|
||||
table: humanTable,
|
||||
}
|
||||
HumanIsPhoneVerifiedCol = Column{
|
||||
name: projection.HumanIsPhoneVerifiedCol,
|
||||
table: humanTable,
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
machineTable = table{
|
||||
name: projection.UserMachineTable,
|
||||
}
|
||||
MachineUserIDCol = Column{
|
||||
name: projection.MachineUserIDCol,
|
||||
table: machineTable,
|
||||
}
|
||||
MachineNameCol = Column{
|
||||
name: projection.MachineNameCol,
|
||||
table: machineTable,
|
||||
}
|
||||
MachineDescriptionCol = Column{
|
||||
name: projection.MachineDescriptionCol,
|
||||
table: machineTable,
|
||||
}
|
||||
)
|
||||
|
||||
func (q *Queries) UserEvents(ctx context.Context, orgID, userID string, sequence uint64) ([]eventstore.EventReader, error) {
|
||||
|
330
internal/query/user_membership.go
Normal file
330
internal/query/user_membership.go
Normal file
@ -0,0 +1,330 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/query/projection"
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
type Memberships struct {
|
||||
SearchResponse
|
||||
Memberships []*Membership
|
||||
}
|
||||
|
||||
type Membership struct {
|
||||
UserID string
|
||||
Roles []string
|
||||
CreationDate time.Time
|
||||
ChangeDate time.Time
|
||||
Sequence uint64
|
||||
ResourceOwner string
|
||||
DisplayName string
|
||||
|
||||
Org *OrgMembership
|
||||
IAM *IAMMembership
|
||||
Project *ProjectMembership
|
||||
ProjectGrant *ProjectGrantMembership
|
||||
}
|
||||
|
||||
type OrgMembership struct {
|
||||
OrgID string
|
||||
}
|
||||
|
||||
type IAMMembership struct {
|
||||
IAMID string
|
||||
}
|
||||
|
||||
type ProjectMembership struct {
|
||||
ProjectID string
|
||||
}
|
||||
|
||||
type ProjectGrantMembership struct {
|
||||
ProjectID string
|
||||
GrantID string
|
||||
}
|
||||
|
||||
type MembershipSearchQuery struct {
|
||||
SearchRequest
|
||||
Queries []SearchQuery
|
||||
}
|
||||
|
||||
func NewMembershipUserIDQuery(userID string) (SearchQuery, error) {
|
||||
return NewTextQuery(membershipUserID.setTable(membershipAlias), userID, TextEquals)
|
||||
}
|
||||
|
||||
func NewMembershipResourceOwnerQuery(value string) (SearchQuery, error) {
|
||||
return NewTextQuery(membershipResourceOwner.setTable(membershipAlias), value, TextEquals)
|
||||
}
|
||||
|
||||
func NewMembershipOrgIDQuery(value string) (SearchQuery, error) {
|
||||
return NewTextQuery(membershipOrgID, value, TextEquals)
|
||||
}
|
||||
|
||||
func NewMembershipProjectIDQuery(value string) (SearchQuery, error) {
|
||||
return NewTextQuery(membershipProjectID, value, TextEquals)
|
||||
}
|
||||
|
||||
func NewMembershipProjectGrantIDQuery(value string) (SearchQuery, error) {
|
||||
return NewTextQuery(membershipGrantID, value, TextEquals)
|
||||
}
|
||||
|
||||
func NewMembershipIsIAMQuery() (SearchQuery, error) {
|
||||
return NewNotNullQuery(membershipIAMID)
|
||||
}
|
||||
|
||||
func (q *MembershipSearchQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
|
||||
query = q.SearchRequest.toQuery(query)
|
||||
for _, q := range q.Queries {
|
||||
query = q.toQuery(query)
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
func (q *Queries) Memberships(ctx context.Context, queries *MembershipSearchQuery) (*Memberships, error) {
|
||||
query, scan := prepareMembershipsQuery()
|
||||
stmt, args, err := queries.toQuery(query).ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInvalidArgument(err, "QUERY-T84X9", "Errors.Query.InvalidRequest")
|
||||
}
|
||||
latestSequence, err := q.latestSequence(ctx, orgMemberTable, iamMemberTable, projectMemberTable, projectGrantMemberTable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := q.client.QueryContext(ctx, stmt, args...)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-eAV2x", "Errors.Internal")
|
||||
}
|
||||
memberships, err := scan(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
memberships.LatestSequence = latestSequence
|
||||
return memberships, nil
|
||||
}
|
||||
|
||||
var (
|
||||
//membershipAlias is a hack to satisfy checks in the queries
|
||||
membershipAlias = table{
|
||||
name: "memberships",
|
||||
}
|
||||
membershipUserID = Column{
|
||||
name: projection.MemberUserIDCol,
|
||||
table: membershipAlias,
|
||||
}
|
||||
membershipRoles = Column{
|
||||
name: projection.MemberRolesCol,
|
||||
table: membershipAlias,
|
||||
}
|
||||
membershipCreationDate = Column{
|
||||
name: projection.MemberCreationDate,
|
||||
table: membershipAlias,
|
||||
}
|
||||
membershipChangeDate = Column{
|
||||
name: projection.MemberChangeDate,
|
||||
table: membershipAlias,
|
||||
}
|
||||
membershipSequence = Column{
|
||||
name: projection.MemberSequence,
|
||||
table: membershipAlias,
|
||||
}
|
||||
membershipResourceOwner = Column{
|
||||
name: projection.MemberResourceOwner,
|
||||
table: membershipAlias,
|
||||
}
|
||||
membershipOrgID = Column{
|
||||
name: projection.OrgMemberOrgIDCol,
|
||||
table: membershipAlias,
|
||||
}
|
||||
membershipIAMID = Column{
|
||||
name: projection.IAMMemberIAMIDCol,
|
||||
table: membershipAlias,
|
||||
}
|
||||
membershipProjectID = Column{
|
||||
name: projection.ProjectMemberProjectIDCol,
|
||||
table: membershipAlias,
|
||||
}
|
||||
membershipGrantID = Column{
|
||||
name: projection.ProjectGrantMemberGrantIDCol,
|
||||
table: membershipAlias,
|
||||
}
|
||||
|
||||
membershipFrom = "(" +
|
||||
prepareOrgMember() +
|
||||
" UNION ALL " +
|
||||
prepareIAMMember() +
|
||||
" UNION ALL " +
|
||||
prepareProjectMember() +
|
||||
" UNION ALL " +
|
||||
prepareProjectGrantMember() +
|
||||
") AS " + membershipAlias.identifier()
|
||||
)
|
||||
|
||||
func prepareMembershipsQuery() (sq.SelectBuilder, func(*sql.Rows) (*Memberships, error)) {
|
||||
return sq.Select(
|
||||
membershipUserID.identifier(),
|
||||
membershipRoles.identifier(),
|
||||
membershipCreationDate.identifier(),
|
||||
membershipChangeDate.identifier(),
|
||||
membershipSequence.identifier(),
|
||||
membershipResourceOwner.identifier(),
|
||||
membershipOrgID.identifier(),
|
||||
membershipIAMID.identifier(),
|
||||
membershipProjectID.identifier(),
|
||||
membershipGrantID.identifier(),
|
||||
HumanDisplayNameCol.identifier(),
|
||||
MachineNameCol.identifier(),
|
||||
countColumn.identifier(),
|
||||
).From(membershipFrom).
|
||||
LeftJoin(join(HumanUserIDCol, membershipUserID)).
|
||||
LeftJoin(join(MachineUserIDCol, membershipUserID)).
|
||||
PlaceholderFormat(sq.Dollar),
|
||||
func(rows *sql.Rows) (*Memberships, error) {
|
||||
memberships := make([]*Membership, 0)
|
||||
var count uint64
|
||||
for rows.Next() {
|
||||
|
||||
var (
|
||||
membership = new(Membership)
|
||||
orgID = sql.NullString{}
|
||||
iamID = sql.NullString{}
|
||||
projectID = sql.NullString{}
|
||||
grantID = sql.NullString{}
|
||||
roles = pq.StringArray{}
|
||||
displayName = sql.NullString{}
|
||||
machineName = sql.NullString{}
|
||||
)
|
||||
|
||||
err := rows.Scan(
|
||||
&membership.UserID,
|
||||
&roles,
|
||||
&membership.CreationDate,
|
||||
&membership.ChangeDate,
|
||||
&membership.Sequence,
|
||||
&membership.ResourceOwner,
|
||||
&orgID,
|
||||
&iamID,
|
||||
&projectID,
|
||||
&grantID,
|
||||
&displayName,
|
||||
&machineName,
|
||||
&count,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
membership.Roles = roles
|
||||
|
||||
if displayName.Valid {
|
||||
membership.DisplayName = displayName.String
|
||||
} else if machineName.Valid {
|
||||
membership.DisplayName = machineName.String
|
||||
}
|
||||
|
||||
if orgID.Valid {
|
||||
membership.Org = &OrgMembership{
|
||||
OrgID: orgID.String,
|
||||
}
|
||||
} else if iamID.Valid {
|
||||
membership.IAM = &IAMMembership{
|
||||
IAMID: iamID.String,
|
||||
}
|
||||
} else if projectID.Valid && grantID.Valid {
|
||||
membership.ProjectGrant = &ProjectGrantMembership{
|
||||
ProjectID: projectID.String,
|
||||
GrantID: grantID.String,
|
||||
}
|
||||
} else if projectID.Valid {
|
||||
membership.Project = &ProjectMembership{
|
||||
ProjectID: projectID.String,
|
||||
}
|
||||
}
|
||||
|
||||
memberships = append(memberships, membership)
|
||||
}
|
||||
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-N34NV", "Errors.Query.CloseRows")
|
||||
}
|
||||
|
||||
return &Memberships{
|
||||
Memberships: memberships,
|
||||
SearchResponse: SearchResponse{
|
||||
Count: count,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func prepareOrgMember() string {
|
||||
stmt, _ := sq.Select(
|
||||
OrgMemberUserID.identifier(),
|
||||
OrgMemberRoles.identifier(),
|
||||
OrgMemberCreationDate.identifier(),
|
||||
OrgMemberChangeDate.identifier(),
|
||||
OrgMemberSequence.identifier(),
|
||||
OrgMemberResourceOwner.identifier(),
|
||||
OrgMemberOrgID.identifier(),
|
||||
"NULL::STRING AS "+membershipIAMID.name,
|
||||
"NULL::STRING AS "+membershipProjectID.name,
|
||||
"NULL::STRING AS "+membershipGrantID.name,
|
||||
).From(orgMemberTable.identifier()).MustSql()
|
||||
return stmt
|
||||
}
|
||||
|
||||
func prepareIAMMember() string {
|
||||
stmt, _ := sq.Select(
|
||||
IAMMemberUserID.identifier(),
|
||||
IAMMemberRoles.identifier(),
|
||||
IAMMemberCreationDate.identifier(),
|
||||
IAMMemberChangeDate.identifier(),
|
||||
IAMMemberSequence.identifier(),
|
||||
IAMMemberResourceOwner.identifier(),
|
||||
"NULL::STRING AS "+membershipOrgID.name,
|
||||
IAMMemberIAMID.identifier(),
|
||||
"NULL::STRING AS "+membershipProjectID.name,
|
||||
"NULL::STRING AS "+membershipGrantID.name,
|
||||
).From(iamMemberTable.identifier()).MustSql()
|
||||
return stmt
|
||||
}
|
||||
|
||||
func prepareProjectMember() string {
|
||||
stmt, _ := sq.Select(
|
||||
ProjectMemberUserID.identifier(),
|
||||
ProjectMemberRoles.identifier(),
|
||||
ProjectMemberCreationDate.identifier(),
|
||||
ProjectMemberChangeDate.identifier(),
|
||||
ProjectMemberSequence.identifier(),
|
||||
ProjectMemberResourceOwner.identifier(),
|
||||
"NULL::STRING AS "+membershipOrgID.name,
|
||||
"NULL::STRING AS "+membershipIAMID.name,
|
||||
ProjectMemberProjectID.identifier(),
|
||||
"NULL::STRING AS "+membershipGrantID.name,
|
||||
).From(projectMemberTable.identifier()).MustSql()
|
||||
|
||||
return stmt
|
||||
}
|
||||
|
||||
func prepareProjectGrantMember() string {
|
||||
stmt, _ := sq.Select(
|
||||
ProjectGrantMemberUserID.identifier(),
|
||||
ProjectGrantMemberRoles.identifier(),
|
||||
ProjectGrantMemberCreationDate.identifier(),
|
||||
ProjectGrantMemberChangeDate.identifier(),
|
||||
ProjectGrantMemberSequence.identifier(),
|
||||
ProjectGrantMemberResourceOwner.identifier(),
|
||||
"NULL::STRING AS "+membershipOrgID.name,
|
||||
"NULL::STRING AS "+membershipIAMID.name,
|
||||
ProjectGrantMemberProjectID.identifier(),
|
||||
ProjectGrantMemberGrantID.identifier(),
|
||||
).From(projectGrantMemberTable.identifier()).MustSql()
|
||||
|
||||
return stmt
|
||||
}
|
611
internal/query/user_membership_test.go
Normal file
611
internal/query/user_membership_test.go
Normal file
@ -0,0 +1,611 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
var (
|
||||
membershipsStmt = regexp.QuoteMeta(
|
||||
"SELECT memberships.user_id" +
|
||||
", memberships.roles" +
|
||||
", memberships.creation_date" +
|
||||
", memberships.change_date" +
|
||||
", memberships.sequence" +
|
||||
", memberships.resource_owner" +
|
||||
", memberships.org_id" +
|
||||
", memberships.iam_id" +
|
||||
", memberships.project_id" +
|
||||
", memberships.grant_id" +
|
||||
", zitadel.projections.users_humans.display_name" +
|
||||
", zitadel.projections.users_machines.name" +
|
||||
", COUNT(*) OVER ()" +
|
||||
" FROM (" +
|
||||
"SELECT members.user_id" +
|
||||
", members.roles" +
|
||||
", members.creation_date" +
|
||||
", members.change_date" +
|
||||
", members.sequence" +
|
||||
", members.resource_owner" +
|
||||
", members.org_id" +
|
||||
", NULL::STRING AS iam_id" +
|
||||
", NULL::STRING AS project_id" +
|
||||
", NULL::STRING AS grant_id" +
|
||||
" FROM zitadel.projections.org_members as members" +
|
||||
" UNION ALL " +
|
||||
"SELECT members.user_id" +
|
||||
", members.roles" +
|
||||
", members.creation_date" +
|
||||
", members.change_date" +
|
||||
", members.sequence" +
|
||||
", members.resource_owner" +
|
||||
", NULL::STRING AS org_id" +
|
||||
", members.iam_id" +
|
||||
", NULL::STRING AS project_id" +
|
||||
", NULL::STRING AS grant_id" +
|
||||
" FROM zitadel.projections.iam_members as members" +
|
||||
" UNION ALL " +
|
||||
"SELECT members.user_id" +
|
||||
", members.roles" +
|
||||
", members.creation_date" +
|
||||
", members.change_date" +
|
||||
", members.sequence" +
|
||||
", members.resource_owner" +
|
||||
", NULL::STRING AS org_id" +
|
||||
", NULL::STRING AS iam_id" +
|
||||
", members.project_id" +
|
||||
", NULL::STRING AS grant_id" +
|
||||
" FROM zitadel.projections.project_members as members" +
|
||||
" UNION ALL " +
|
||||
"SELECT members.user_id" +
|
||||
", members.roles" +
|
||||
", members.creation_date" +
|
||||
", members.change_date" +
|
||||
", members.sequence" +
|
||||
", members.resource_owner" +
|
||||
", NULL::STRING AS org_id" +
|
||||
", NULL::STRING AS iam_id" +
|
||||
", members.project_id" +
|
||||
", members.grant_id" +
|
||||
" FROM zitadel.projections.project_grant_members as members" +
|
||||
") AS memberships" +
|
||||
" LEFT JOIN zitadel.projections.users_humans ON memberships.user_id = zitadel.projections.users_humans.user_id" +
|
||||
" LEFT JOIN zitadel.projections.users_machines ON memberships.user_id = zitadel.projections.users_machines.user_id")
|
||||
membershipCols = []string{
|
||||
"user_id",
|
||||
"roles",
|
||||
"creation_date",
|
||||
"change_date",
|
||||
"sequence",
|
||||
"resource_owner",
|
||||
"org_id",
|
||||
"iam_id",
|
||||
"project_id",
|
||||
"grant_id",
|
||||
"display_name",
|
||||
"name",
|
||||
"count",
|
||||
}
|
||||
)
|
||||
|
||||
func Test_MembershipPrepares(t *testing.T) {
|
||||
type want struct {
|
||||
sqlExpectations sqlExpectation
|
||||
err checkErr
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
prepare interface{}
|
||||
want want
|
||||
object interface{}
|
||||
}{
|
||||
{
|
||||
name: "prepareMembershipsQuery no result",
|
||||
prepare: prepareMembershipsQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
membershipsStmt,
|
||||
nil,
|
||||
nil,
|
||||
),
|
||||
},
|
||||
object: &Memberships{Memberships: []*Membership{}},
|
||||
},
|
||||
{
|
||||
name: "prepareMembershipsQuery one org member human",
|
||||
prepare: prepareMembershipsQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
membershipsStmt,
|
||||
membershipCols,
|
||||
[][]driver.Value{
|
||||
{
|
||||
"user-id",
|
||||
pq.StringArray{"role1", "role2"},
|
||||
testNow,
|
||||
testNow,
|
||||
uint64(20211202),
|
||||
"ro",
|
||||
"org-id",
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
"display name",
|
||||
nil,
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
object: &Memberships{
|
||||
SearchResponse: SearchResponse{
|
||||
Count: 1,
|
||||
},
|
||||
Memberships: []*Membership{
|
||||
{
|
||||
UserID: "user-id",
|
||||
Roles: []string{"role1", "role2"},
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211202,
|
||||
ResourceOwner: "ro",
|
||||
Org: &OrgMembership{OrgID: "org-id"},
|
||||
DisplayName: "display name",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareMembershipsQuery one org member machine",
|
||||
prepare: prepareMembershipsQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
membershipsStmt,
|
||||
membershipCols,
|
||||
[][]driver.Value{
|
||||
{
|
||||
"user-id",
|
||||
pq.StringArray{"role1", "role2"},
|
||||
testNow,
|
||||
testNow,
|
||||
uint64(20211202),
|
||||
"ro",
|
||||
"org-id",
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
"machine-name",
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
object: &Memberships{
|
||||
SearchResponse: SearchResponse{
|
||||
Count: 1,
|
||||
},
|
||||
Memberships: []*Membership{
|
||||
{
|
||||
UserID: "user-id",
|
||||
Roles: []string{"role1", "role2"},
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211202,
|
||||
ResourceOwner: "ro",
|
||||
Org: &OrgMembership{OrgID: "org-id"},
|
||||
DisplayName: "machine-name",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareMembershipsQuery one iam member human",
|
||||
prepare: prepareMembershipsQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
membershipsStmt,
|
||||
membershipCols,
|
||||
[][]driver.Value{
|
||||
{
|
||||
"user-id",
|
||||
pq.StringArray{"role1", "role2"},
|
||||
testNow,
|
||||
testNow,
|
||||
uint64(20211202),
|
||||
"ro",
|
||||
nil,
|
||||
"iam-id",
|
||||
nil,
|
||||
nil,
|
||||
"display name",
|
||||
nil,
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
object: &Memberships{
|
||||
SearchResponse: SearchResponse{
|
||||
Count: 1,
|
||||
},
|
||||
Memberships: []*Membership{
|
||||
{
|
||||
UserID: "user-id",
|
||||
Roles: []string{"role1", "role2"},
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211202,
|
||||
ResourceOwner: "ro",
|
||||
IAM: &IAMMembership{IAMID: "iam-id"},
|
||||
DisplayName: "display name",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareMembershipsQuery one iam member machine",
|
||||
prepare: prepareMembershipsQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
membershipsStmt,
|
||||
membershipCols,
|
||||
[][]driver.Value{
|
||||
{
|
||||
"user-id",
|
||||
pq.StringArray{"role1", "role2"},
|
||||
testNow,
|
||||
testNow,
|
||||
uint64(20211202),
|
||||
"ro",
|
||||
nil,
|
||||
"iam-id",
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
"machine-name",
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
object: &Memberships{
|
||||
SearchResponse: SearchResponse{
|
||||
Count: 1,
|
||||
},
|
||||
Memberships: []*Membership{
|
||||
{
|
||||
UserID: "user-id",
|
||||
Roles: []string{"role1", "role2"},
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211202,
|
||||
ResourceOwner: "ro",
|
||||
IAM: &IAMMembership{IAMID: "iam-id"},
|
||||
DisplayName: "machine-name",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareMembershipsQuery one project member human",
|
||||
prepare: prepareMembershipsQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
membershipsStmt,
|
||||
membershipCols,
|
||||
[][]driver.Value{
|
||||
{
|
||||
"user-id",
|
||||
pq.StringArray{"role1", "role2"},
|
||||
testNow,
|
||||
testNow,
|
||||
uint64(20211202),
|
||||
"ro",
|
||||
nil,
|
||||
nil,
|
||||
"project-id",
|
||||
nil,
|
||||
"display name",
|
||||
nil,
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
object: &Memberships{
|
||||
SearchResponse: SearchResponse{
|
||||
Count: 1,
|
||||
},
|
||||
Memberships: []*Membership{
|
||||
{
|
||||
UserID: "user-id",
|
||||
Roles: []string{"role1", "role2"},
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211202,
|
||||
ResourceOwner: "ro",
|
||||
Project: &ProjectMembership{ProjectID: "project-id"},
|
||||
DisplayName: "display name",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareMembershipsQuery one project member machine",
|
||||
prepare: prepareMembershipsQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
membershipsStmt,
|
||||
membershipCols,
|
||||
[][]driver.Value{
|
||||
{
|
||||
"user-id",
|
||||
pq.StringArray{"role1", "role2"},
|
||||
testNow,
|
||||
testNow,
|
||||
uint64(20211202),
|
||||
"ro",
|
||||
nil,
|
||||
nil,
|
||||
"project-id",
|
||||
nil,
|
||||
nil,
|
||||
"machine-name",
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
object: &Memberships{
|
||||
SearchResponse: SearchResponse{
|
||||
Count: 1,
|
||||
},
|
||||
Memberships: []*Membership{
|
||||
{
|
||||
UserID: "user-id",
|
||||
Roles: []string{"role1", "role2"},
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211202,
|
||||
ResourceOwner: "ro",
|
||||
Project: &ProjectMembership{ProjectID: "project-id"},
|
||||
DisplayName: "machine-name",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareMembershipsQuery one project grant member human",
|
||||
prepare: prepareMembershipsQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
membershipsStmt,
|
||||
membershipCols,
|
||||
[][]driver.Value{
|
||||
{
|
||||
"user-id",
|
||||
pq.StringArray{"role1", "role2"},
|
||||
testNow,
|
||||
testNow,
|
||||
uint64(20211202),
|
||||
"ro",
|
||||
nil,
|
||||
nil,
|
||||
"project-id",
|
||||
"grant-id",
|
||||
"display name",
|
||||
nil,
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
object: &Memberships{
|
||||
SearchResponse: SearchResponse{
|
||||
Count: 1,
|
||||
},
|
||||
Memberships: []*Membership{
|
||||
{
|
||||
UserID: "user-id",
|
||||
Roles: []string{"role1", "role2"},
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211202,
|
||||
ResourceOwner: "ro",
|
||||
ProjectGrant: &ProjectGrantMembership{
|
||||
GrantID: "grant-id",
|
||||
ProjectID: "project-id",
|
||||
},
|
||||
DisplayName: "display name",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareMembershipsQuery one project grant member machine",
|
||||
prepare: prepareMembershipsQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
membershipsStmt,
|
||||
membershipCols,
|
||||
[][]driver.Value{
|
||||
{
|
||||
"user-id",
|
||||
pq.StringArray{"role1", "role2"},
|
||||
testNow,
|
||||
testNow,
|
||||
uint64(20211202),
|
||||
"ro",
|
||||
nil,
|
||||
nil,
|
||||
"project-id",
|
||||
"grant-id",
|
||||
nil,
|
||||
"machine-name",
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
object: &Memberships{
|
||||
SearchResponse: SearchResponse{
|
||||
Count: 1,
|
||||
},
|
||||
Memberships: []*Membership{
|
||||
{
|
||||
UserID: "user-id",
|
||||
Roles: []string{"role1", "role2"},
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211202,
|
||||
ResourceOwner: "ro",
|
||||
ProjectGrant: &ProjectGrantMembership{
|
||||
GrantID: "grant-id",
|
||||
ProjectID: "project-id",
|
||||
},
|
||||
DisplayName: "machine-name",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareMembershipsQuery one for each member type",
|
||||
prepare: prepareMembershipsQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
membershipsStmt,
|
||||
membershipCols,
|
||||
[][]driver.Value{
|
||||
{
|
||||
"user-id",
|
||||
pq.StringArray{"role1", "role2"},
|
||||
testNow,
|
||||
testNow,
|
||||
uint64(20211202),
|
||||
"ro",
|
||||
"org-id",
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
"display name",
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"user-id",
|
||||
pq.StringArray{"role1", "role2"},
|
||||
testNow,
|
||||
testNow,
|
||||
uint64(20211202),
|
||||
"ro",
|
||||
nil,
|
||||
"iam-id",
|
||||
nil,
|
||||
nil,
|
||||
"display name",
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"user-id",
|
||||
pq.StringArray{"role1", "role2"},
|
||||
testNow,
|
||||
testNow,
|
||||
uint64(20211202),
|
||||
"ro",
|
||||
nil,
|
||||
nil,
|
||||
"project-id",
|
||||
nil,
|
||||
"display name",
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"user-id",
|
||||
pq.StringArray{"role1", "role2"},
|
||||
testNow,
|
||||
testNow,
|
||||
uint64(20211202),
|
||||
"ro",
|
||||
nil,
|
||||
nil,
|
||||
"project-id",
|
||||
"grant-id",
|
||||
"display name",
|
||||
nil,
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
object: &Memberships{
|
||||
SearchResponse: SearchResponse{
|
||||
Count: 4,
|
||||
},
|
||||
Memberships: []*Membership{
|
||||
{
|
||||
UserID: "user-id",
|
||||
Roles: []string{"role1", "role2"},
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211202,
|
||||
ResourceOwner: "ro",
|
||||
Org: &OrgMembership{OrgID: "org-id"},
|
||||
DisplayName: "display name",
|
||||
},
|
||||
{
|
||||
UserID: "user-id",
|
||||
Roles: []string{"role1", "role2"},
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211202,
|
||||
ResourceOwner: "ro",
|
||||
IAM: &IAMMembership{IAMID: "iam-id"},
|
||||
DisplayName: "display name",
|
||||
},
|
||||
{
|
||||
UserID: "user-id",
|
||||
Roles: []string{"role1", "role2"},
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211202,
|
||||
ResourceOwner: "ro",
|
||||
Project: &ProjectMembership{ProjectID: "project-id"},
|
||||
DisplayName: "display name",
|
||||
},
|
||||
{
|
||||
UserID: "user-id",
|
||||
Roles: []string{"role1", "role2"},
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211202,
|
||||
ResourceOwner: "ro",
|
||||
ProjectGrant: &ProjectGrantMembership{
|
||||
ProjectID: "project-id",
|
||||
GrantID: "grant-id",
|
||||
},
|
||||
DisplayName: "display name",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareMembershipsQuery sql err",
|
||||
prepare: prepareMembershipsQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueryErr(
|
||||
membershipsStmt,
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user