feat: user memberships (#537)

* feat: add search user memberships

* feat: add search user memberships

* feat: read user member ship

* feat: add usergrant search key

* feat: uesrmemberships based on permissions

* feat: merge master

* fix: correct permissions

* fix: update display name on change profile

* fix: merge request converations

* fix: err handling

* Update internal/user/model/user_membership_view.go

Co-authored-by: Silvan <silvan.reusser@gmail.com>

Co-authored-by: Silvan <silvan.reusser@gmail.com>
This commit is contained in:
Fabi 2020-07-30 14:37:55 +02:00 committed by GitHub
parent 4dabecd8d4
commit 75f1c4c576
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 3773 additions and 2174 deletions

View File

@ -21,6 +21,7 @@ InternalAuthZ:
- "user.grant.read"
- "user.grant.write"
- "user.grant.delete"
- "user.membership.read"
- "policy.read"
- "policy.write"
- "policy.delete"
@ -57,6 +58,7 @@ InternalAuthZ:
- "org.member.read"
- "user.read"
- "user.grant.read"
- "user.membership.read"
- "policy.read"
- "project.read"
- "project.member.read"
@ -79,6 +81,7 @@ InternalAuthZ:
- "user.grant.read"
- "user.grant.write"
- "user.grant.delete"
- "user.membership.read"
- "policy.read"
- "policy.write"
- "policy.delete"
@ -111,6 +114,7 @@ InternalAuthZ:
- "org.member.read"
- "user.read"
- "user.grant.read"
- "user.membership.read"
- "policy.read"
- "project.read"
- "project.member.read"
@ -180,6 +184,7 @@ InternalAuthZ:
- "user.grant.read"
- "user.grant.write"
- "user.grant.delete"
- "user.membership.read"
- Role: 'PROJECT_OWNER_VIEWER'
Permissions:
- "project.read"
@ -190,6 +195,7 @@ InternalAuthZ:
- "project.grant.member.read"
- "user.read"
- "user.grant.read"
- "user.membership.read"
- Role: 'PROJECT_GRANT_OWNER'
Permissions:
- "project.read"
@ -201,10 +207,12 @@ InternalAuthZ:
- "user.grant.read"
- "user.grant.write"
- "user.grant.delete"
- "user.membership.read"
- Role: 'PROJECT_GRANT_OWNER'
Permissions:
- "project.read"
- "project.grant.read"
- "project.grant.member.read"
- "user.read"
- "user.grant.read"
- "user.grant.read"
- "user.membership.read"

View File

@ -11,7 +11,7 @@ docker run -d \
--hostname=zitadel-db \
-p 26257:26257 -p 8080:8080 \
-v "${GOPATH}/src/github.com/caos/zitadel/cockroach-data/zitadel1:/cockroach/cockroach-data" \
cockroachdb/cockroach:v19.2.2 start --insecure
cockroachdb/cockroach:latest start --insecure
```
### local database migrations

10
go.mod
View File

@ -13,7 +13,7 @@ require (
github.com/VictoriaMetrics/fastcache v1.5.7
github.com/ajstarks/svgo v0.0.0-20200725142600-7a3c8b57fecb
github.com/allegro/bigcache v1.2.1
github.com/aws/aws-sdk-go v1.33.12 // indirect
github.com/aws/aws-sdk-go v1.33.13 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc
github.com/caos/logging v0.0.2
github.com/caos/oidc v0.6.4
@ -39,7 +39,7 @@ require (
github.com/kevinburke/go.uuid v1.2.0 // indirect
github.com/kevinburke/rest v0.0.0-20200429221318-0d2892b400f8 // indirect
github.com/kevinburke/twilio-go v0.0.0-20200713162607-ff84c3703a29
github.com/lib/pq v1.7.1
github.com/lib/pq v1.8.0
github.com/mattn/go-colorable v0.1.7 // indirect
github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
github.com/mitchellh/copystructure v1.0.0 // indirect
@ -54,10 +54,10 @@ require (
github.com/ttacon/libphonenumber v1.1.0
go.opencensus.io v0.22.4
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899
golang.org/x/sys v0.0.0-20200727154430-2d971f7391a4 // indirect
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1 // indirect
golang.org/x/text v0.3.3
golang.org/x/tools v0.0.0-20200725200936-102e7d357031
google.golang.org/genproto v0.0.0-20200726014623-da3ae01ef02d
golang.org/x/tools v0.0.0-20200727233628-55644ead90ce
google.golang.org/genproto v0.0.0-20200728010541-3dc8dca74b7b
google.golang.org/grpc v1.30.0
google.golang.org/protobuf v1.25.0
gopkg.in/square/go-jose.v2 v2.5.1

10
go.sum
View File

@ -66,6 +66,8 @@ github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpi
github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.33.12 h1:eydMoSwfrSTD9PWKUJOiDL7+/UwDW8AjInUGVE5Llh4=
github.com/aws/aws-sdk-go v1.33.12/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/aws/aws-sdk-go v1.33.13 h1:3+AsCrxxnhiUQEhWV+j3kEs7aBCIn2qkDjA+elpxYPU=
github.com/aws/aws-sdk-go v1.33.13/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/caos/logging v0.0.0-20191210002624-b3260f690a6a/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0=
@ -286,6 +288,8 @@ github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.4.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.7.1 h1:FvD5XTVTDt+KON6oIoOmHq6B6HzGuYEhuTMpEG0yuBQ=
github.com/lib/pq v1.7.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lyft/protoc-gen-star v0.4.10/go.mod h1:mE8fbna26u7aEA2QCVvvfBU/ZrPgocG1206xAFPcs94=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
@ -515,6 +519,8 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121 h1:rITEj+UZHYC927n8GT97eC3zr
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200727154430-2d971f7391a4 h1:gtF+PUC1CD1a9ocwQHbVNXuTp6RQsAYt6tpi6zjT81Y=
golang.org/x/sys v0.0.0-20200727154430-2d971f7391a4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1 h1:sIky/MyNRSHTrdxfsiUSS4WIAMvInbeXljJz+jDjeYE=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -573,6 +579,8 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20200713011307-fd294ab11aed/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200725200936-102e7d357031 h1:VtIxiVHWPhnny2ZTi4f9/2diZKqyLaq3FUTuud5+khA=
golang.org/x/tools v0.0.0-20200725200936-102e7d357031/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200727233628-55644ead90ce h1:HEwYEPqqa3/M0N2Q6IgtBaf2CaxvmRiVdAhX6LR7uE4=
golang.org/x/tools v0.0.0-20200727233628-55644ead90ce/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -641,6 +649,8 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
google.golang.org/genproto v0.0.0-20200711021454-869866162049/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200726014623-da3ae01ef02d h1:HJaAqDnKreMkv+AQyf1Mcw0jEmL9kKBNL07RDJu1N/k=
google.golang.org/genproto v0.0.0-20200726014623-da3ae01ef02d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200728010541-3dc8dca74b7b h1:FWkel6k8GbR7SbBY200Cz8tA58/KtMrfpVZwOOSjOvQ=
google.golang.org/genproto v0.0.0-20200728010541-3dc8dca74b7b/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=

View File

@ -16,7 +16,7 @@ func (v *View) IamMemberByIDs(orgID, userID string) (*model.IamMemberView, error
return view.IamMemberByIDs(v.Db, iamMemberTable, orgID, userID)
}
func (v *View) SearchIamMembers(request *iam_model.IamMemberSearchRequest) ([]*model.IamMemberView, int, error) {
func (v *View) SearchIamMembers(request *iam_model.IamMemberSearchRequest) ([]*model.IamMemberView, uint64, error) {
return view.SearchIamMembers(v.Db, iamMemberTable, request)
}

View File

@ -15,7 +15,7 @@ func (v *View) OrgByID(orgID string) (*model.OrgView, error) {
return org_view.OrgByID(v.Db, orgTable, orgID)
}
func (v *View) SearchOrgs(query *org_model.OrgSearchRequest) ([]*model.OrgView, int, error) {
func (v *View) SearchOrgs(query *org_model.OrgSearchRequest) ([]*model.OrgView, uint64, error) {
return org_view.SearchOrgs(v.Db, orgTable, query)
}

View File

@ -2,6 +2,7 @@ package management
import (
"context"
"github.com/caos/zitadel/internal/api/authz"
"github.com/golang/protobuf/ptypes/empty"
@ -194,3 +195,14 @@ func (s *Server) GetUserMfas(ctx context.Context, userID *management.UserID) (*m
}
return &management.MultiFactors{Mfas: mfasFromModel(mfas)}, nil
}
func (s *Server) SearchUserMemberships(ctx context.Context, in *management.UserMembershipSearchRequest) (*management.UserMembershipSearchResponse, error) {
request := userMembershipSearchRequestsToModel(in)
request.AppendResourceOwnerQuery(authz.GetCtxData(ctx).OrgID)
request.AppendUserIDQuery(in.UserId)
response, err := s.user.SearchUserMemberships(ctx, request)
if err != nil {
return nil, err
}
return userMembershipSearchResponseFromModel(response), nil
}

View File

@ -140,6 +140,41 @@ func userSearchKeyToModel(key management.UserSearchKey) usr_model.UserSearchKey
}
}
func userMembershipSearchRequestsToModel(request *management.UserMembershipSearchRequest) *usr_model.UserMembershipSearchRequest {
return &usr_model.UserMembershipSearchRequest{
Offset: request.Offset,
Limit: request.Limit,
Queries: userMembershipSearchQueriesToModel(request.Queries),
}
}
func userMembershipSearchQueriesToModel(queries []*management.UserMembershipSearchQuery) []*usr_model.UserMembershipSearchQuery {
converted := make([]*usr_model.UserMembershipSearchQuery, len(queries))
for i, q := range queries {
converted[i] = userMembershipSearchQueryToModel(q)
}
return converted
}
func userMembershipSearchQueryToModel(query *management.UserMembershipSearchQuery) *usr_model.UserMembershipSearchQuery {
return &usr_model.UserMembershipSearchQuery{
Key: userMembershipSearchKeyToModel(query.Key),
Method: searchMethodToModel(query.Method),
Value: query.Value,
}
}
func userMembershipSearchKeyToModel(key management.UserMembershipSearchKey) usr_model.UserMembershipSearchKey {
switch key {
case management.UserMembershipSearchKey_USERMEMBERSHIPSEARCHKEY_TYPE:
return usr_model.UserMembershipSearchKeyMemberType
case management.UserMembershipSearchKey_USERMEMBERSHIPSEARCHKEY_OBJECT_ID:
return usr_model.UserMembershipSearchKeyObjectID
default:
return usr_model.UserMembershipSearchKeyUnspecified
}
}
func profileFromModel(profile *usr_model.Profile) *management.UserProfile {
creationDate, err := ptypes.TimestampProto(profile.CreationDate)
logging.Log("GRPC-dkso3").OnError(err).Debug("unable to parse timestamp")
@ -398,6 +433,48 @@ func userViewFromModel(user *usr_model.UserView) *management.UserView {
}
}
func userMembershipSearchResponseFromModel(response *usr_model.UserMembershipSearchResponse) *management.UserMembershipSearchResponse {
timestamp, err := ptypes.TimestampProto(response.Timestamp)
logging.Log("GRPC-Hs8jd").OnError(err).Debug("unable to parse timestamp")
return &management.UserMembershipSearchResponse{
Offset: response.Offset,
Limit: response.Limit,
TotalResult: response.TotalResult,
Result: userMembershipViewsFromModel(response.Result),
ProcessedSequence: response.Sequence,
ViewTimestamp: timestamp,
}
}
func userMembershipViewsFromModel(memberships []*usr_model.UserMembershipView) []*management.UserMembershipView {
converted := make([]*management.UserMembershipView, len(memberships))
for i, membership := range memberships {
converted[i] = userMembershipViewFromModel(membership)
}
return converted
}
func userMembershipViewFromModel(membership *usr_model.UserMembershipView) *management.UserMembershipView {
creationDate, err := ptypes.TimestampProto(membership.CreationDate)
logging.Log("GRPC-Msnu8").OnError(err).Debug("unable to parse timestamp")
changeDate, err := ptypes.TimestampProto(membership.ChangeDate)
logging.Log("GRPC-Slco9").OnError(err).Debug("unable to parse timestamp")
return &management.UserMembershipView{
UserId: membership.UserID,
AggregateId: membership.AggregateID,
ObjectId: membership.ObjectID,
MemberType: memberTypeFromModel(membership.MemberType),
DisplayName: membership.DisplayName,
Roles: membership.Roles,
CreationDate: creationDate,
ChangeDate: changeDate,
Sequence: membership.Sequence,
ResourceOwner: membership.ResourceOwner,
}
}
func mfasFromModel(mfas []*usr_model.MultiFactor) []*management.MultiFactor {
converted := make([]*management.MultiFactor, len(mfas))
for i, mfa := range mfas {
@ -454,6 +531,18 @@ func genderFromModel(gender usr_model.Gender) management.Gender {
}
}
func memberTypeFromModel(memberType usr_model.MemberType) management.MemberType {
switch memberType {
case usr_model.MemberTypeOrganisation:
return management.MemberType_MEMBERTYPE_ORGANISATION
case usr_model.MemberTypeProject:
return management.MemberType_MEMBERTYPE_PROJECT
case usr_model.MemberTypeProjectGrant:
return management.MemberType_MEMBERTYPE_PROJECT_GRANT
default:
return management.MemberType_MEMBERTYPE_UNSPECIFIED
}
}
func genderToModel(gender management.Gender) usr_model.Gender {
switch gender {
case management.Gender_GENDER_FEMALE:

View File

@ -58,7 +58,6 @@ func userGrantUpdateToModel(u *management.UserGrantUpdate) *grant_model.UserGran
return &grant_model.UserGrant{
ObjectRoot: models.ObjectRoot{AggregateID: u.Id},
RoleKeys: u.RoleKeys,
GrantID: u.GrantId,
}
}
@ -127,6 +126,8 @@ func userGrantSearchKeyToModel(key management.UserGrantSearchKey) grant_model.Us
return grant_model.UserGrantSearchKeyUserID
case management.UserGrantSearchKey_USERGRANTSEARCHKEY_ROLE_KEY:
return grant_model.UserGrantSearchKeyRoleKey
case management.UserGrantSearchKey_USERGRANTSEARCHKEY_GRANT_ID:
return grant_model.UserGrantSearchKeyGrantID
default:
return grant_model.UserGrantSearchKeyUnspecified
}

View File

@ -170,7 +170,7 @@ func grantRespToOrgResp(grants *grant_model.UserGrantSearchResponse) *grant_mode
return resp
}
func orgRespToOrgResp(orgs []*org_view_model.OrgView, count int) *grant_model.ProjectOrgSearchResponse {
func orgRespToOrgResp(orgs []*org_view_model.OrgView, count uint64) *grant_model.ProjectOrgSearchResponse {
resp := &grant_model.ProjectOrgSearchResponse{
TotalResult: uint64(count),
}

View File

@ -18,7 +18,7 @@ func (v *View) ApplicationByID(appID string) (*model.ApplicationView, error) {
return view.ApplicationByID(v.Db, applicationTable, appID)
}
func (v *View) SearchApplications(request *proj_model.ApplicationSearchRequest) ([]*model.ApplicationView, int, error) {
func (v *View) SearchApplications(request *proj_model.ApplicationSearchRequest) ([]*model.ApplicationView, uint64, error) {
return view.SearchApplications(v.Db, applicationTable, request)
}

View File

@ -15,7 +15,7 @@ func (v *View) OrgByID(orgID string) (*org_model.OrgView, error) {
return org_view.OrgByID(v.Db, orgTable, orgID)
}
func (v *View) SearchOrgs(req *model.OrgSearchRequest) ([]*org_model.OrgView, int, error) {
func (v *View) SearchOrgs(req *model.OrgSearchRequest) ([]*org_model.OrgView, uint64, error) {
return org_view.SearchOrgs(v.Db, orgTable, req)
}

View File

@ -26,7 +26,7 @@ func (v *View) UserByLoginName(loginName string) (*model.UserView, error) {
func (v *View) UsersByOrgID(orgID string) ([]*model.UserView, error) {
return view.UsersByOrgID(v.Db, userTable, orgID)
}
func (v *View) SearchUsers(request *usr_model.UserSearchRequest) ([]*model.UserView, int, error) {
func (v *View) SearchUsers(request *usr_model.UserSearchRequest) ([]*model.UserView, uint64, error) {
return view.SearchUsers(v.Db, userTable, request)
}

View File

@ -31,7 +31,7 @@ func (v *View) UserGrantsByProjectAndUserID(projectID, userID string) ([]*model.
return view.UserGrantsByProjectAndUserID(v.Db, userGrantTable, projectID, userID)
}
func (v *View) SearchUserGrants(request *grant_model.UserGrantSearchRequest) ([]*model.UserGrantView, int, error) {
func (v *View) SearchUserGrants(request *grant_model.UserGrantSearchRequest) ([]*model.UserGrantView, uint64, error) {
return view.SearchUserGrants(v.Db, userGrantTable, request)
}

View File

@ -23,7 +23,7 @@ func (v *View) ApplicationByProjecIDAndAppName(projectID, appName string) (*mode
return view.ApplicationByProjectIDAndAppName(v.Db, applicationTable, projectID, appName)
}
func (v *View) SearchApplications(request *proj_model.ApplicationSearchRequest) ([]*model.ApplicationView, int, error) {
func (v *View) SearchApplications(request *proj_model.ApplicationSearchRequest) ([]*model.ApplicationView, uint64, error) {
return view.SearchApplications(v.Db, applicationTable, request)
}

View File

@ -15,7 +15,7 @@ func (v *View) OrgByID(orgID string) (*org_model.OrgView, error) {
return org_view.OrgByID(v.Db, orgTable, orgID)
}
func (v *View) SearchOrgs(req *model.OrgSearchRequest) ([]*org_model.OrgView, int, error) {
func (v *View) SearchOrgs(req *model.OrgSearchRequest) ([]*org_model.OrgView, uint64, error) {
return org_view.SearchOrgs(v.Db, orgTable, req)
}

View File

@ -27,7 +27,7 @@ func (v *View) UserGrantsByProjectID(projectID string) ([]*model.UserGrantView,
return view.UserGrantsByProjectID(v.Db, userGrantTable, projectID)
}
func (v *View) SearchUserGrants(request *grant_model.UserGrantSearchRequest) ([]*model.UserGrantView, int, error) {
func (v *View) SearchUserGrants(request *grant_model.UserGrantSearchRequest) ([]*model.UserGrantView, uint64, error) {
return view.SearchUserGrants(v.Db, userGrantTable, request)
}

View File

@ -22,7 +22,7 @@ func IamMemberByIDs(db *gorm.DB, table, orgID, userID string) (*model.IamMemberV
return member, err
}
func SearchIamMembers(db *gorm.DB, table string, req *iam_model.IamMemberSearchRequest) ([]*model.IamMemberView, int, error) {
func SearchIamMembers(db *gorm.DB, table string, req *iam_model.IamMemberSearchRequest) ([]*model.IamMemberView, uint64, error) {
members := make([]*model.IamMemberView, 0)
query := repository.PrepareSearchQuery(table, model.IamMemberSearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries})
count, err := query(db, &members)

View File

@ -0,0 +1,8 @@
package eventstore
const (
projectReadPerm = "project.read"
orgMemberReadPerm = "org.member.read"
projectMemberReadPerm = "project.member.read"
projectGrantMemberReadPerm = "project.member.read"
)

View File

@ -3,6 +3,8 @@ package eventstore
import (
"context"
caos_errs "github.com/caos/zitadel/internal/errors"
global_model "github.com/caos/zitadel/internal/model"
"github.com/caos/zitadel/internal/view/repository"
"github.com/caos/logging"
@ -96,7 +98,7 @@ func (repo *UserRepo) SearchUsers(ctx context.Context, request *usr_model.UserSe
request.EnsureLimit(repo.SearchLimit)
sequence, err := repo.View.GetLatestUserSequence()
logging.Log("EVENT-Lcn7d").OnError(err).Warn("could not read latest user sequence")
projects, count, err := repo.View.SearchUsers(request)
users, count, err := repo.View.SearchUsers(request)
if err != nil {
return nil, err
}
@ -104,7 +106,7 @@ func (repo *UserRepo) SearchUsers(ctx context.Context, request *usr_model.UserSe
Offset: request.Offset,
Limit: request.Limit,
TotalResult: uint64(count),
Result: model.UsersToModel(projects),
Result: model.UsersToModel(users),
}
if err == nil {
result.Sequence = sequence.CurrentSequence
@ -215,3 +217,71 @@ func (repo *UserRepo) AddressByID(ctx context.Context, userID string) (*usr_mode
func (repo *UserRepo) ChangeAddress(ctx context.Context, address *usr_model.Address) (*usr_model.Address, error) {
return repo.UserEvents.ChangeAddress(ctx, address)
}
func (repo *UserRepo) SearchUserMemberships(ctx context.Context, request *usr_model.UserMembershipSearchRequest) (*usr_model.UserMembershipSearchResponse, error) {
request.EnsureLimit(repo.SearchLimit)
sequence, err := repo.View.GetLatestUserMembershipSequence()
logging.Log("EVENT-Dn7sf").OnError(err).Warn("could not read latest user sequence")
result := handleSearchUserMembershipsPermissions(ctx, request, sequence)
if result != nil {
return result, nil
}
memberships, count, err := repo.View.SearchUserMemberships(request)
if err != nil {
return nil, err
}
result = &usr_model.UserMembershipSearchResponse{
Offset: request.Offset,
Limit: request.Limit,
TotalResult: uint64(count),
Result: model.UserMembershipsToModel(memberships),
}
if err == nil {
result.Sequence = sequence.CurrentSequence
result.Timestamp = sequence.CurrentTimestamp
}
return result, nil
}
func handleSearchUserMembershipsPermissions(ctx context.Context, request *usr_model.UserMembershipSearchRequest, sequence *repository.CurrentSequence) *usr_model.UserMembershipSearchResponse {
permissions := authz.GetAllPermissionsFromCtx(ctx)
orgPerm := authz.HasGlobalExplicitPermission(permissions, orgMemberReadPerm)
projectPerm := authz.HasGlobalExplicitPermission(permissions, projectMemberReadPerm)
projectGrantPerm := authz.HasGlobalExplicitPermission(permissions, projectGrantMemberReadPerm)
if orgPerm && projectPerm && projectGrantPerm {
return nil
}
if !orgPerm {
request.Queries = append(request.Queries, &usr_model.UserMembershipSearchQuery{Key: usr_model.UserMembershipSearchKeyMemberType, Method: global_model.SearchMethodNotEquals, Value: usr_model.MemberTypeOrganisation})
}
ids := authz.GetExplicitPermissionCtxIDs(permissions, projectMemberReadPerm)
ids = append(ids, authz.GetExplicitPermissionCtxIDs(permissions, projectGrantMemberReadPerm)...)
if _, q := request.GetSearchQuery(usr_model.UserMembershipSearchKeyObjectID); q != nil {
containsID := false
for _, id := range ids {
if id == q.Value {
containsID = true
break
}
}
if !containsID {
result := &usr_model.UserMembershipSearchResponse{
Offset: request.Offset,
Limit: request.Limit,
TotalResult: uint64(0),
Result: []*usr_model.UserMembershipView{},
}
if sequence != nil {
result.Sequence = sequence.CurrentSequence
result.Timestamp = sequence.CurrentTimestamp
}
return result
}
}
request.Queries = append(request.Queries, &usr_model.UserMembershipSearchQuery{Key: usr_model.UserMembershipSearchKeyObjectID, Method: global_model.SearchMethodIsOneOf, Value: ids})
return nil
}

View File

@ -13,10 +13,6 @@ import (
"github.com/caos/zitadel/internal/view/repository"
)
const (
projectReadPerm = "project.read"
)
type UserGrantRepo struct {
SearchLimit uint64
UserGrantEvents *grant_event.UserGrantEventStore

View File

@ -44,6 +44,7 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, ev
&Org{handler: handler{view, bulkLimit, configs.cycleDuration("Org"), errorCount}},
&OrgMember{handler: handler{view, bulkLimit, configs.cycleDuration("OrgMember"), errorCount}, userEvents: repos.UserEvents},
&OrgDomain{handler: handler{view, bulkLimit, configs.cycleDuration("OrgDomain"), errorCount}},
&UserMembership{handler: handler{view, bulkLimit, configs.cycleDuration("UserMembership"), errorCount}, orgEvents: repos.OrgEvents, projectEvents: repos.ProjectEvents},
}
}

View File

@ -0,0 +1,171 @@
package handler
import (
"context"
org_model "github.com/caos/zitadel/internal/org/model"
org_event "github.com/caos/zitadel/internal/org/repository/eventsourcing"
proj_event "github.com/caos/zitadel/internal/project/repository/eventsourcing"
proj_es_model "github.com/caos/zitadel/internal/project/repository/eventsourcing/model"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore/models"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/eventstore/spooler"
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
usr_model "github.com/caos/zitadel/internal/user/model"
usr_es_model "github.com/caos/zitadel/internal/user/repository/view/model"
)
type UserMembership struct {
handler
orgEvents *org_event.OrgEventstore
projectEvents *proj_event.ProjectEventstore
}
const (
userMembershipTable = "management.user_memberships"
)
func (m *UserMembership) ViewModel() string {
return userMembershipTable
}
func (m *UserMembership) EventQuery() (*models.SearchQuery, error) {
sequence, err := m.view.GetLatestUserMembershipSequence()
if err != nil {
return nil, err
}
return es_models.NewSearchQuery().
AggregateTypeFilter(org_es_model.OrgAggregate, proj_es_model.ProjectAggregate).
LatestSequenceFilter(sequence.CurrentSequence), nil
}
func (m *UserMembership) Reduce(event *models.Event) (err error) {
switch event.AggregateType {
case org_es_model.OrgAggregate:
err = m.processOrg(event)
case proj_es_model.ProjectAggregate:
err = m.processProject(event)
}
return err
}
func (m *UserMembership) processOrg(event *models.Event) (err error) {
member := new(usr_es_model.UserMembershipView)
err = member.AppendEvent(event)
if err != nil {
return err
}
switch event.Type {
case org_es_model.OrgMemberAdded:
err = m.fillOrgDisplayName(member)
case org_es_model.OrgMemberChanged:
member, err = m.view.UserMembershipByIDs(member.UserID, event.AggregateID, event.AggregateID, usr_model.MemberTypeOrganisation)
if err != nil {
return err
}
err = member.AppendEvent(event)
case org_es_model.OrgMemberRemoved:
return m.view.DeleteUserMembership(member.UserID, event.AggregateID, event.AggregateID, usr_model.MemberTypeOrganisation, event.Sequence)
case org_es_model.OrgChanged:
err = m.updateOrgDisplayName(event)
default:
return m.view.ProcessedUserMembershipSequence(event.Sequence)
}
if err != nil {
return err
}
return m.view.PutUserMembership(member, event.Sequence)
}
func (m *UserMembership) fillOrgDisplayName(member *usr_es_model.UserMembershipView) (err error) {
org, err := m.orgEvents.OrgByID(context.Background(), org_model.NewOrg(member.AggregateID))
if err != nil {
return err
}
member.DisplayName = org.Name
return nil
}
func (m *UserMembership) updateOrgDisplayName(event *models.Event) error {
org, err := m.orgEvents.OrgByID(context.Background(), org_model.NewOrg(event.AggregateID))
if err != nil {
return err
}
memberships, err := m.view.UserMembershipsByAggregateID(event.AggregateID)
if err != nil {
return err
}
for _, membership := range memberships {
membership.DisplayName = org.Name
}
return m.view.BulkPutUserMemberships(memberships, event.Sequence)
}
func (m *UserMembership) processProject(event *models.Event) (err error) {
member := new(usr_es_model.UserMembershipView)
err = member.AppendEvent(event)
if err != nil {
return err
}
switch event.Type {
case proj_es_model.ProjectMemberAdded, proj_es_model.ProjectGrantMemberAdded:
err = m.fillProjectDisplayName(member)
case proj_es_model.ProjectMemberChanged:
member, err = m.view.UserMembershipByIDs(member.UserID, event.AggregateID, event.AggregateID, usr_model.MemberTypeProject)
if err != nil {
return err
}
err = member.AppendEvent(event)
case proj_es_model.ProjectMemberRemoved:
return m.view.DeleteUserMembership(member.UserID, event.AggregateID, event.AggregateID, usr_model.MemberTypeProject, event.Sequence)
case proj_es_model.ProjectGrantMemberChanged:
member, err = m.view.UserMembershipByIDs(member.UserID, event.AggregateID, member.ObjectID, usr_model.MemberTypeProjectGrant)
if err != nil {
return err
}
err = member.AppendEvent(event)
case proj_es_model.ProjectGrantMemberRemoved:
return m.view.DeleteUserMembership(member.UserID, event.AggregateID, member.ObjectID, usr_model.MemberTypeProjectGrant, event.Sequence)
case proj_es_model.ProjectChanged:
err = m.updateProjectDisplayName(event)
default:
return m.view.ProcessedUserMembershipSequence(event.Sequence)
}
if err != nil {
return err
}
return m.view.PutUserMembership(member, event.Sequence)
}
func (m *UserMembership) fillProjectDisplayName(member *usr_es_model.UserMembershipView) (err error) {
project, err := m.projectEvents.ProjectByID(context.Background(), member.AggregateID)
if err != nil {
return err
}
member.DisplayName = project.Name
return nil
}
func (m *UserMembership) updateProjectDisplayName(event *models.Event) error {
project, err := m.projectEvents.ProjectByID(context.Background(), event.AggregateID)
if err != nil {
return err
}
memberships, err := m.view.UserMembershipsByAggregateID(event.AggregateID)
if err != nil {
return err
}
for _, membership := range memberships {
membership.DisplayName = project.Name
}
return m.view.BulkPutUserMemberships(memberships, event.Sequence)
}
func (m *UserMembership) OnError(event *models.Event, err error) error {
logging.LogWithFields("SPOOL-Ms3fj", "id", event.AggregateID).WithError(err).Warn("something went wrong in orgmember handler")
return spooler.HandleError(event, err, m.view.GetLatestUserMembershipFailedEvent, m.view.ProcessedUserMembershipFailedEvent, m.view.ProcessedUserMembershipSequence, m.errorCountUntilSkip)
}

View File

@ -15,7 +15,7 @@ func (v *View) ApplicationByID(appID string) (*model.ApplicationView, error) {
return view.ApplicationByID(v.Db, applicationTable, appID)
}
func (v *View) SearchApplications(request *proj_model.ApplicationSearchRequest) ([]*model.ApplicationView, int, error) {
func (v *View) SearchApplications(request *proj_model.ApplicationSearchRequest) ([]*model.ApplicationView, uint64, error) {
return view.SearchApplications(v.Db, applicationTable, request)
}

View File

@ -23,7 +23,7 @@ func (v *View) VerifiedOrgDomain(domain string) (*model.OrgDomainView, error) {
return view.VerifiedOrgDomain(v.Db, orgDomainTable, domain)
}
func (v *View) SearchOrgDomains(request *org_model.OrgDomainSearchRequest) ([]*model.OrgDomainView, int, error) {
func (v *View) SearchOrgDomains(request *org_model.OrgDomainSearchRequest) ([]*model.OrgDomainView, uint64, error) {
return view.SearchOrgDomains(v.Db, orgDomainTable, request)
}

View File

@ -15,7 +15,7 @@ func (v *View) OrgMemberByIDs(orgID, userID string) (*model.OrgMemberView, error
return view.OrgMemberByIDs(v.Db, orgMemberTable, orgID, userID)
}
func (v *View) SearchOrgMembers(request *org_model.OrgMemberSearchRequest) ([]*model.OrgMemberView, int, error) {
func (v *View) SearchOrgMembers(request *org_model.OrgMemberSearchRequest) ([]*model.OrgMemberView, uint64, error) {
return view.SearchOrgMembers(v.Db, orgMemberTable, request)
}

View File

@ -15,7 +15,7 @@ func (v *View) ProjectByID(projectID string) (*model.ProjectView, error) {
return view.ProjectByID(v.Db, projectTable, projectID)
}
func (v *View) SearchProjects(request *proj_model.ProjectViewSearchRequest) ([]*model.ProjectView, int, error) {
func (v *View) SearchProjects(request *proj_model.ProjectViewSearchRequest) ([]*model.ProjectView, uint64, error) {
return view.SearchProjects(v.Db, projectTable, request)
}

View File

@ -27,7 +27,7 @@ func (v *View) ProjectGrantsByProjectIDAndRoleKey(projectID, key string) ([]*mod
return view.ProjectGrantsByProjectIDAndRoleKey(v.Db, grantedProjectTable, projectID, key)
}
func (v *View) SearchProjectGrants(request *proj_model.ProjectGrantViewSearchRequest) ([]*model.ProjectGrantView, int, error) {
func (v *View) SearchProjectGrants(request *proj_model.ProjectGrantViewSearchRequest) ([]*model.ProjectGrantView, uint64, error) {
return view.SearchProjectGrants(v.Db, grantedProjectTable, request)
}

View File

@ -15,7 +15,7 @@ func (v *View) ProjectGrantMemberByIDs(projectID, userID string) (*model.Project
return view.ProjectGrantMemberByIDs(v.Db, projectGrantMemberTable, projectID, userID)
}
func (v *View) SearchProjectGrantMembers(request *proj_model.ProjectGrantMemberSearchRequest) ([]*model.ProjectGrantMemberView, int, error) {
func (v *View) SearchProjectGrantMembers(request *proj_model.ProjectGrantMemberSearchRequest) ([]*model.ProjectGrantMemberView, uint64, error) {
return view.SearchProjectGrantMembers(v.Db, projectGrantMemberTable, request)
}

View File

@ -15,7 +15,7 @@ func (v *View) ProjectMemberByIDs(projectID, userID string) (*model.ProjectMembe
return view.ProjectMemberByIDs(v.Db, projectMemberTable, projectID, userID)
}
func (v *View) SearchProjectMembers(request *proj_model.ProjectMemberSearchRequest) ([]*model.ProjectMemberView, int, error) {
func (v *View) SearchProjectMembers(request *proj_model.ProjectMemberSearchRequest) ([]*model.ProjectMemberView, uint64, error) {
return view.SearchProjectMembers(v.Db, projectMemberTable, request)
}

View File

@ -23,7 +23,7 @@ func (v *View) ResourceOwnerProjectRoles(projectID, resourceowner string) ([]*mo
return view.ResourceOwnerProjectRoles(v.Db, projectRoleTable, projectID, resourceowner)
}
func (v *View) SearchProjectRoles(request *proj_model.ProjectRoleSearchRequest) ([]*model.ProjectRoleView, int, error) {
func (v *View) SearchProjectRoles(request *proj_model.ProjectRoleSearchRequest) ([]*model.ProjectRoleView, uint64, error) {
return view.SearchProjectRoles(v.Db, projectRoleTable, request)
}

View File

@ -15,7 +15,7 @@ func (v *View) UserByID(userID string) (*model.UserView, error) {
return view.UserByID(v.Db, userTable, userID)
}
func (v *View) SearchUsers(request *usr_model.UserSearchRequest) ([]*model.UserView, int, error) {
func (v *View) SearchUsers(request *usr_model.UserSearchRequest) ([]*model.UserView, uint64, error) {
return view.SearchUsers(v.Db, userTable, request)
}

View File

@ -15,7 +15,7 @@ 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, int, error) {
func (v *View) SearchUserGrants(request *grant_model.UserGrantSearchRequest) ([]*model.UserGrantView, uint64, error) {
return view.SearchUserGrants(v.Db, userGrantTable, request)
}

View File

@ -0,0 +1,64 @@
package view
import (
usr_model "github.com/caos/zitadel/internal/user/model"
"github.com/caos/zitadel/internal/user/repository/view"
"github.com/caos/zitadel/internal/user/repository/view/model"
"github.com/caos/zitadel/internal/view/repository"
)
const (
userMembershipTable = "management.user_memberships"
)
func (v *View) UserMembershipByIDs(userID, aggregateID, objectID string, memberType usr_model.MemberType) (*model.UserMembershipView, error) {
return view.UserMembershipByIDs(v.Db, userMembershipTable, userID, aggregateID, objectID, memberType)
}
func (v *View) UserMembershipsByAggregateID(aggregateID string) ([]*model.UserMembershipView, error) {
return view.UserMembershipsByAggregateID(v.Db, userMembershipTable, aggregateID)
}
func (v *View) SearchUserMemberships(request *usr_model.UserMembershipSearchRequest) ([]*model.UserMembershipView, uint64, error) {
return view.SearchUserMemberships(v.Db, userMembershipTable, request)
}
func (v *View) PutUserMembership(membership *model.UserMembershipView, sequence uint64) error {
err := view.PutUserMembership(v.Db, userMembershipTable, membership)
if err != nil {
return err
}
return v.ProcessedUserMembershipSequence(sequence)
}
func (v *View) BulkPutUserMemberships(memberships []*model.UserMembershipView, sequence uint64) error {
err := view.PutUserMemberships(v.Db, userTable, memberships...)
if err != nil {
return err
}
return v.ProcessedUserMembershipSequence(sequence)
}
func (v *View) DeleteUserMembership(userID, aggregateID, objectID string, memberType usr_model.MemberType, eventSequence uint64) error {
err := view.DeleteUserMembership(v.Db, userMembershipTable, userID, aggregateID, objectID, memberType)
if err != nil {
return nil
}
return v.ProcessedUserMembershipSequence(eventSequence)
}
func (v *View) GetLatestUserMembershipSequence() (*repository.CurrentSequence, error) {
return v.latestSequence(userMembershipTable)
}
func (v *View) ProcessedUserMembershipSequence(eventSequence uint64) error {
return v.saveCurrentSequence(userMembershipTable, eventSequence)
}
func (v *View) GetLatestUserMembershipFailedEvent(sequence uint64) (*repository.FailedEvent, error) {
return v.latestFailedEvent(userMembershipTable, sequence)
}
func (v *View) ProcessedUserMembershipFailedEvent(failedEvent *repository.FailedEvent) error {
return v.saveFailedEvent(failedEvent)
}

View File

@ -37,4 +37,6 @@ type UserRepository interface {
AddressByID(ctx context.Context, userID string) (*model.Address, error)
ChangeAddress(ctx context.Context, address *model.Address) (*model.Address, error)
SearchUserMemberships(ctx context.Context, request *model.UserMembershipSearchRequest) (*model.UserMembershipSearchResponse, error)
}

View File

@ -33,7 +33,7 @@ func VerifiedOrgDomain(db *gorm.DB, table, domain string) (*model.OrgDomainView,
return domainView, err
}
func SearchOrgDomains(db *gorm.DB, table string, req *org_model.OrgDomainSearchRequest) ([]*model.OrgDomainView, int, error) {
func SearchOrgDomains(db *gorm.DB, table string, req *org_model.OrgDomainSearchRequest) ([]*model.OrgDomainView, uint64, error) {
members := make([]*model.OrgDomainView, 0)
query := repository.PrepareSearchQuery(table, model.OrgDomainSearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries})
count, err := query(db, &members)

View File

@ -22,7 +22,7 @@ func OrgMemberByIDs(db *gorm.DB, table, orgID, userID string) (*model.OrgMemberV
return member, err
}
func SearchOrgMembers(db *gorm.DB, table string, req *org_model.OrgMemberSearchRequest) ([]*model.OrgMemberView, int, error) {
func SearchOrgMembers(db *gorm.DB, table string, req *org_model.OrgMemberSearchRequest) ([]*model.OrgMemberView, uint64, error) {
members := make([]*model.OrgMemberView, 0)
query := repository.PrepareSearchQuery(table, model.OrgMemberSearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries})
count, err := query(db, &members)

View File

@ -18,7 +18,7 @@ func OrgByID(db *gorm.DB, table, orgID string) (*model.OrgView, error) {
return org, err
}
func SearchOrgs(db *gorm.DB, table string, req *org_model.OrgSearchRequest) ([]*model.OrgView, int, error) {
func SearchOrgs(db *gorm.DB, table string, req *org_model.OrgSearchRequest) ([]*model.OrgView, uint64, error) {
orgs := make([]*model.OrgView, 0)
query := repository.PrepareSearchQuery(table, model.OrgSearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries})
count, err := query(db, &orgs)

View File

@ -42,7 +42,7 @@ func ApplicationByProjectIDAndAppName(db *gorm.DB, table, projectID, appName str
return app, err
}
func SearchApplications(db *gorm.DB, table string, req *proj_model.ApplicationSearchRequest) ([]*model.ApplicationView, int, error) {
func SearchApplications(db *gorm.DB, table string, req *proj_model.ApplicationSearchRequest) ([]*model.ApplicationView, uint64, error) {
apps := make([]*model.ApplicationView, 0)
query := repository.PrepareSearchQuery(table, model.ApplicationSearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries})
count, err := query(db, &apps)

View File

@ -22,7 +22,7 @@ func ProjectGrantMemberByIDs(db *gorm.DB, table, grantID, userID string) (*model
return role, err
}
func SearchProjectGrantMembers(db *gorm.DB, table string, req *proj_model.ProjectGrantMemberSearchRequest) ([]*model.ProjectGrantMemberView, int, error) {
func SearchProjectGrantMembers(db *gorm.DB, table string, req *proj_model.ProjectGrantMemberSearchRequest) ([]*model.ProjectGrantMemberView, uint64, error) {
roles := make([]*model.ProjectGrantMemberView, 0)
query := repository.PrepareSearchQuery(table, model.ProjectGrantMemberSearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries})
count, err := query(db, &roles)

View File

@ -55,7 +55,7 @@ func ProjectGrantsByProjectIDAndRoleKey(db *gorm.DB, table, projectID, roleKey s
return projectGrants, err
}
func SearchProjectGrants(db *gorm.DB, table string, req *proj_model.ProjectGrantViewSearchRequest) ([]*model.ProjectGrantView, int, error) {
func SearchProjectGrants(db *gorm.DB, table string, req *proj_model.ProjectGrantViewSearchRequest) ([]*model.ProjectGrantView, uint64, error) {
projectGrants := make([]*model.ProjectGrantView, 0)
query := repository.PrepareSearchQuery(table, model.ProjectGrantSearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries})
count, err := query(db, &projectGrants)

View File

@ -22,7 +22,7 @@ func ProjectMemberByIDs(db *gorm.DB, table, projectID, userID string) (*model.Pr
return role, err
}
func SearchProjectMembers(db *gorm.DB, table string, req *proj_model.ProjectMemberSearchRequest) ([]*model.ProjectMemberView, int, error) {
func SearchProjectMembers(db *gorm.DB, table string, req *proj_model.ProjectMemberSearchRequest) ([]*model.ProjectMemberView, uint64, error) {
roles := make([]*model.ProjectMemberView, 0)
query := repository.PrepareSearchQuery(table, model.ProjectMemberSearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries})
count, err := query(db, &roles)

View File

@ -52,7 +52,7 @@ func ResourceOwnerProjectRoles(db *gorm.DB, table, projectID, resourceOwner stri
return roles, nil
}
func SearchProjectRoles(db *gorm.DB, table string, req *proj_model.ProjectRoleSearchRequest) ([]*model.ProjectRoleView, int, error) {
func SearchProjectRoles(db *gorm.DB, table string, req *proj_model.ProjectRoleSearchRequest) ([]*model.ProjectRoleView, uint64, error) {
roles := make([]*model.ProjectRoleView, 0)
query := repository.PrepareSearchQuery(table, model.ProjectRoleSearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries})
count, err := query(db, &roles)

View File

@ -34,7 +34,7 @@ func ProjectsByResourceOwner(db *gorm.DB, table, orgID string) ([]*model.Project
return projects, nil
}
func SearchProjects(db *gorm.DB, table string, req *proj_model.ProjectViewSearchRequest) ([]*model.ProjectView, int, error) {
func SearchProjects(db *gorm.DB, table string, req *proj_model.ProjectViewSearchRequest) ([]*model.ProjectView, uint64, error) {
projects := make([]*model.ProjectView, 0)
query := repository.PrepareSearchQuery(table, model.ProjectSearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries})
count, err := query(db, &projects)

View File

@ -22,3 +22,7 @@ type Profile struct {
func (p *Profile) IsValid() bool {
return p.FirstName != "" && p.LastName != ""
}
func (p *Profile) SetNamesAsDisplayname() {
p.DisplayName = p.FirstName + " " + p.LastName
}

View File

@ -0,0 +1,88 @@
package model
import (
"time"
"github.com/caos/zitadel/internal/model"
)
type UserMembershipView struct {
UserID string
MemberType MemberType
AggregateID string
//ObjectID differs from aggregate id if obejct is sub of an aggregate
ObjectID string
Roles []string
DisplayName string
CreationDate time.Time
ChangeDate time.Time
ResourceOwner string
Sequence uint64
}
type MemberType int32
const (
MemberTypeUnspecified MemberType = iota
MemberTypeOrganisation
MemberTypeProject
MemberTypeProjectGrant
)
type UserMembershipSearchRequest struct {
Offset uint64
Limit uint64
SortingColumn UserMembershipSearchKey
Asc bool
Queries []*UserMembershipSearchQuery
}
type UserMembershipSearchKey int32
const (
UserMembershipSearchKeyUnspecified UserMembershipSearchKey = iota
UserMembershipSearchKeyUserID
UserMembershipSearchKeyMemberType
UserMembershipSearchKeyAggregateID
UserMembershipSearchKeyObjectID
UserMembershipSearchKeyResourceOwner
)
type UserMembershipSearchQuery struct {
Key UserMembershipSearchKey
Method model.SearchMethod
Value interface{}
}
type UserMembershipSearchResponse struct {
Offset uint64
Limit uint64
TotalResult uint64
Result []*UserMembershipView
Sequence uint64
Timestamp time.Time
}
func (r *UserMembershipSearchRequest) EnsureLimit(limit uint64) {
if r.Limit == 0 || r.Limit > limit {
r.Limit = limit
}
}
func (r *UserMembershipSearchRequest) GetSearchQuery(key UserMembershipSearchKey) (int, *UserMembershipSearchQuery) {
for i, q := range r.Queries {
if q.Key == key {
return i, q
}
}
return -1, nil
}
func (r *UserMembershipSearchRequest) AppendResourceOwnerQuery(orgID string) {
r.Queries = append(r.Queries, &UserMembershipSearchQuery{Key: UserMembershipSearchKeyResourceOwner, Method: model.SearchMethodEquals, Value: orgID})
}
func (r *UserMembershipSearchRequest) AppendUserIDQuery(userID string) {
r.Queries = append(r.Queries, &UserMembershipSearchQuery{Key: UserMembershipSearchKeyUserID, Method: model.SearchMethodEquals, Value: userID})
}

View File

@ -611,6 +611,7 @@ func (es *UserEventstore) ProfileByID(ctx context.Context, userID string) (*usr_
}
func (es *UserEventstore) ChangeProfile(ctx context.Context, profile *usr_model.Profile) (*usr_model.Profile, error) {
profile.SetNamesAsDisplayname()
if !profile.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-d82i3", "Errors.User.ProfileInvalid")
}
@ -618,6 +619,7 @@ func (es *UserEventstore) ChangeProfile(ctx context.Context, profile *usr_model.
if err != nil {
return nil, err
}
repoExisting := model.UserFromModel(existing)
repoNew := model.ProfileFromModel(profile)

View File

@ -0,0 +1,139 @@
package model
import (
"encoding/json"
"github.com/caos/logging"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
proj_es_model "github.com/caos/zitadel/internal/project/repository/eventsourcing/model"
"github.com/caos/zitadel/internal/user/model"
"github.com/lib/pq"
"time"
)
const (
UserMembershipKeyUserID = "user_id"
UserMembershipKeyAggregateID = "aggregate_id"
UserMembershipKeyObjectID = "object_id"
UserMembershipKeyResourceOwner = "resource_owner"
UserMembershipKeyMemberType = "member_type"
)
type UserMembershipView struct {
UserID string `json:"-" gorm:"column:user_id;primary_key"`
MemberType int32 `json:"-" gorm:"column:member_type;primary_key"`
AggregateID string `json:"-" gorm:"column:aggregate_id;primary_key"`
ObjectID string `json:"-" gorm:"column:object_id;primary_key"`
Roles pq.StringArray `json:"-" gorm:"column:roles"`
DisplayName string `json:"-" gorm:"column:display_name"`
CreationDate time.Time `json:"-" gorm:"column:creation_date"`
ChangeDate time.Time `json:"-" gorm:"column:change_date"`
ResourceOwner string `json:"-" gorm:"column:resource_owner"`
Sequence uint64 `json:"-" gorm:"column:sequence"`
}
func UserMembershipFromModel(membership *model.UserMembershipView) *UserMembershipView {
return &UserMembershipView{
UserID: membership.UserID,
MemberType: int32(membership.MemberType),
AggregateID: membership.AggregateID,
ObjectID: membership.ObjectID,
Roles: membership.Roles,
DisplayName: membership.DisplayName,
ChangeDate: membership.ChangeDate,
CreationDate: membership.CreationDate,
ResourceOwner: membership.ResourceOwner,
Sequence: membership.Sequence,
}
}
func UserMembershipToModel(membership *UserMembershipView) *model.UserMembershipView {
return &model.UserMembershipView{
UserID: membership.UserID,
MemberType: model.MemberType(membership.MemberType),
AggregateID: membership.AggregateID,
ObjectID: membership.ObjectID,
Roles: membership.Roles,
DisplayName: membership.DisplayName,
ChangeDate: membership.ChangeDate,
CreationDate: membership.CreationDate,
ResourceOwner: membership.ResourceOwner,
Sequence: membership.Sequence,
}
}
func UserMembershipsToModel(memberships []*UserMembershipView) []*model.UserMembershipView {
result := make([]*model.UserMembershipView, len(memberships))
for i, m := range memberships {
result[i] = UserMembershipToModel(m)
}
return result
}
func (u *UserMembershipView) AppendEvent(event *models.Event) (err error) {
u.ChangeDate = event.CreationDate
u.Sequence = event.Sequence
switch event.Type {
case org_es_model.OrgMemberAdded:
u.setRootData(event, model.MemberTypeOrganisation)
err = u.setOrgMemberData(event)
case org_es_model.OrgMemberChanged:
err = u.setOrgMemberData(event)
case proj_es_model.ProjectMemberAdded:
u.setRootData(event, model.MemberTypeProject)
err = u.setProjectMemberData(event)
case proj_es_model.ProjectMemberChanged:
err = u.setProjectMemberData(event)
case proj_es_model.ProjectGrantMemberAdded:
u.setRootData(event, model.MemberTypeProjectGrant)
err = u.setProjectMemberData(event)
case proj_es_model.ProjectGrantMemberChanged:
err = u.setProjectMemberData(event)
}
return err
}
func (u *UserMembershipView) setRootData(event *models.Event, memberType model.MemberType) {
u.CreationDate = event.CreationDate
u.AggregateID = event.AggregateID
u.ObjectID = event.AggregateID
u.ResourceOwner = event.ResourceOwner
u.MemberType = int32(memberType)
}
func (u *UserMembershipView) setOrgMemberData(event *models.Event) error {
member := new(org_es_model.OrgMember)
if err := json.Unmarshal(event.Data, member); err != nil {
logging.Log("MODEL-Lps0e").WithError(err).Error("could not unmarshal event data")
return caos_errs.ThrowInternal(nil, "MODEL-6jhsw", "could not unmarshal data")
}
u.UserID = member.UserID
u.Roles = member.Roles
return nil
}
func (u *UserMembershipView) setProjectMemberData(event *models.Event) error {
member := new(proj_es_model.ProjectMember)
if err := json.Unmarshal(event.Data, member); err != nil {
logging.Log("MODEL-Esu8k").WithError(err).Error("could not unmarshal event data")
return caos_errs.ThrowInternal(nil, "MODEL-6jhsw", "could not unmarshal data")
}
u.UserID = member.UserID
u.Roles = member.Roles
return nil
}
func (u *UserMembershipView) setProjectGrantMemberData(event *models.Event) error {
member := new(proj_es_model.ProjectGrantMember)
if err := json.Unmarshal(event.Data, member); err != nil {
logging.Log("MODEL-MCn8s").WithError(err).Error("could not unmarshal event data")
return caos_errs.ThrowInternal(nil, "MODEL-6jhsw", "could not unmarshal data")
}
u.UserID = member.UserID
u.ObjectID = member.GrantID
u.Roles = member.Roles
return nil
}

View File

@ -0,0 +1,67 @@
package model
import (
global_model "github.com/caos/zitadel/internal/model"
usr_model "github.com/caos/zitadel/internal/user/model"
"github.com/caos/zitadel/internal/view/repository"
)
type UserMembershipSearchRequest usr_model.UserMembershipSearchRequest
type UserMembershipSearchQuery usr_model.UserMembershipSearchQuery
type UserMembershipSearchKey usr_model.UserMembershipSearchKey
func (req UserMembershipSearchRequest) GetLimit() uint64 {
return req.Limit
}
func (req UserMembershipSearchRequest) GetOffset() uint64 {
return req.Offset
}
func (req UserMembershipSearchRequest) GetSortingColumn() repository.ColumnKey {
if req.SortingColumn == usr_model.UserMembershipSearchKeyUnspecified {
return nil
}
return UserMembershipSearchKey(req.SortingColumn)
}
func (req UserMembershipSearchRequest) GetAsc() bool {
return req.Asc
}
func (req UserMembershipSearchRequest) GetQueries() []repository.SearchQuery {
result := make([]repository.SearchQuery, len(req.Queries))
for i, q := range req.Queries {
result[i] = UserMembershipSearchQuery{Key: q.Key, Value: q.Value, Method: q.Method}
}
return result
}
func (req UserMembershipSearchQuery) GetKey() repository.ColumnKey {
return UserMembershipSearchKey(req.Key)
}
func (req UserMembershipSearchQuery) GetMethod() global_model.SearchMethod {
return req.Method
}
func (req UserMembershipSearchQuery) GetValue() interface{} {
return req.Value
}
func (key UserMembershipSearchKey) ToColumnName() string {
switch usr_model.UserMembershipSearchKey(key) {
case usr_model.UserMembershipSearchKeyUserID:
return UserMembershipKeyUserID
case usr_model.UserMembershipSearchKeyResourceOwner:
return UserMembershipKeyResourceOwner
case usr_model.UserMembershipSearchKeyMemberType:
return UserMembershipKeyMemberType
case usr_model.UserMembershipSearchKeyAggregateID:
return UserMembershipKeyAggregateID
case usr_model.UserMembershipSearchKeyObjectID:
return UserMembershipKeyObjectID
default:
return ""
}
}

View File

@ -59,7 +59,7 @@ func UsersByOrgID(db *gorm.DB, table, orgID string) ([]*model.UserView, error) {
return users, err
}
func SearchUsers(db *gorm.DB, table string, req *usr_model.UserSearchRequest) ([]*model.UserView, int, error) {
func SearchUsers(db *gorm.DB, table string, req *usr_model.UserSearchRequest) ([]*model.UserView, uint64, error) {
users := make([]*model.UserView, 0)
query := repository.PrepareSearchQuery(table, model.UserSearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries})
count, err := query(db, &users)

View File

@ -0,0 +1,70 @@
package view
import (
global_model "github.com/caos/zitadel/internal/model"
"github.com/caos/zitadel/internal/view/repository"
"github.com/jinzhu/gorm"
caos_errs "github.com/caos/zitadel/internal/errors"
usr_model "github.com/caos/zitadel/internal/user/model"
"github.com/caos/zitadel/internal/user/repository/view/model"
)
func UserMembershipByIDs(db *gorm.DB, table, userID, aggregateID, objectID string, membertype usr_model.MemberType) (*model.UserMembershipView, error) {
memberships := new(model.UserMembershipView)
userIDQuery := &model.UserMembershipSearchQuery{Key: usr_model.UserMembershipSearchKeyUserID, Value: userID, Method: global_model.SearchMethodEquals}
aggregateIDQuery := &model.UserMembershipSearchQuery{Key: usr_model.UserMembershipSearchKeyAggregateID, Value: aggregateID, Method: global_model.SearchMethodEquals}
objectIDQuery := &model.UserMembershipSearchQuery{Key: usr_model.UserMembershipSearchKeyObjectID, Value: objectID, Method: global_model.SearchMethodEquals}
memberTypeQuery := &model.UserMembershipSearchQuery{Key: usr_model.UserMembershipSearchKeyMemberType, Value: int32(membertype), Method: global_model.SearchMethodEquals}
query := repository.PrepareGetByQuery(table, userIDQuery, aggregateIDQuery, objectIDQuery, memberTypeQuery)
err := query(db, memberships)
if caos_errs.IsNotFound(err) {
return nil, caos_errs.ThrowNotFound(nil, "VIEW-sj8Sw", "Errors.UserMembership.NotFound")
}
return memberships, err
}
func UserMembershipsByAggregateID(db *gorm.DB, table, aggregateID string) ([]*model.UserMembershipView, error) {
memberships := make([]*model.UserMembershipView, 0)
aggregateIDQuery := &usr_model.UserMembershipSearchQuery{Key: usr_model.UserMembershipSearchKeyAggregateID, Value: aggregateID, Method: global_model.SearchMethodEquals}
query := repository.PrepareSearchQuery(table, model.UserMembershipSearchRequest{
Queries: []*usr_model.UserMembershipSearchQuery{aggregateIDQuery},
})
_, err := query(db, memberships)
return memberships, err
}
func SearchUserMemberships(db *gorm.DB, table string, req *usr_model.UserMembershipSearchRequest) ([]*model.UserMembershipView, uint64, error) {
users := make([]*model.UserMembershipView, 0)
query := repository.PrepareSearchQuery(table, model.UserMembershipSearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries})
count, err := query(db, &users)
if err != nil {
return nil, 0, err
}
return users, count, nil
}
func PutUserMemberships(db *gorm.DB, table string, users ...*model.UserMembershipView) error {
save := repository.PrepareBulkSave(table)
u := make([]interface{}, len(users))
for i, user := range users {
u[i] = user
}
return save(db, u...)
}
func PutUserMembership(db *gorm.DB, table string, user *model.UserMembershipView) error {
save := repository.PrepareSave(table)
return save(db, user)
}
func DeleteUserMembership(db *gorm.DB, table, userID, aggregateID, objectID string, membertype usr_model.MemberType) error {
delete := repository.PrepareDeleteByKeys(table,
repository.Key{Key: model.UserMembershipSearchKey(usr_model.UserMembershipSearchKeyUserID), Value: userID},
repository.Key{Key: model.UserMembershipSearchKey(usr_model.UserMembershipSearchKeyAggregateID), Value: aggregateID},
repository.Key{Key: model.UserMembershipSearchKey(usr_model.UserMembershipSearchKeyObjectID), Value: objectID},
repository.Key{Key: model.UserMembershipSearchKey(usr_model.UserMembershipSearchKeyMemberType), Value: membertype},
)
return delete(db)
}

View File

@ -33,7 +33,7 @@ func UserGrantByIDs(db *gorm.DB, table, resourceOwnerID, projectID, userID strin
return user, err
}
func SearchUserGrants(db *gorm.DB, table string, req *grant_model.UserGrantSearchRequest) ([]*model.UserGrantView, int, error) {
func SearchUserGrants(db *gorm.DB, table string, req *grant_model.UserGrantSearchRequest) ([]*model.UserGrantView, uint64, error) {
users := make([]*model.UserGrantView, 0)
query := repository.PrepareSearchQuery(table, model.UserGrantSearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries})
count, err := query(db, &users)

View File

@ -26,9 +26,9 @@ type ColumnKey interface {
ToColumnName() string
}
func PrepareSearchQuery(table string, request SearchRequest) func(db *gorm.DB, res interface{}) (int, error) {
return func(db *gorm.DB, res interface{}) (int, error) {
count := 0
func PrepareSearchQuery(table string, request SearchRequest) func(db *gorm.DB, res interface{}) (uint64, error) {
return func(db *gorm.DB, res interface{}) (uint64, error) {
var count uint64 = 0
query := db.Table(table)
if column := request.GetSortingColumn(); column != nil {
order := "DESC"

View File

@ -13,7 +13,7 @@ func TestPrepareSearchQuery(t *testing.T) {
searchRequest SearchRequest
}
type res struct {
count int
count uint64
wantErr bool
errFunc func(err error) bool
}

View File

@ -0,0 +1,15 @@
CREATE TABLE management.user_memberships (
user_id TEXT,
member_type SMALLINT,
aggregate_id TEXT,
object_id TEXT,
roles TEXT ARRAY,
display_name TEXT,
resource_owner TEXT,
creation_date TIMESTAMPTZ,
change_date TIMESTAMPTZ,
sequence BIGINT,
PRIMARY KEY (user_id, member_type, aggregate_id, object_id)
);

View File

@ -159,6 +159,11 @@ var ManagementService_AuthMethods = authz.MethodMapping{
CheckParam: "",
},
"/caos.zitadel.management.api.v1.ManagementService/SearchUserMemberships": authz.Option{
Permission: "user.membership.read",
CheckParam: "",
},
"/caos.zitadel.management.api.v1.ManagementService/GetPasswordComplexityPolicy": authz.Option{
Permission: "policy.read",
CheckParam: "",

File diff suppressed because it is too large Load Diff

View File

@ -929,6 +929,41 @@ func request_ManagementService_SetInitialPassword_0(ctx context.Context, marshal
}
func request_ManagementService_SearchUserMemberships_0(ctx context.Context, marshaler runtime.Marshaler, client ManagementServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq UserMembershipSearchRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["user_id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "user_id")
}
protoReq.UserId, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "user_id", err)
}
msg, err := client.SearchUserMemberships(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func request_ManagementService_GetPasswordComplexityPolicy_0(ctx context.Context, marshaler runtime.Marshaler, client ManagementServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq empty.Empty
var metadata runtime.ServerMetadata
@ -4376,6 +4411,26 @@ func RegisterManagementServiceHandlerClient(ctx context.Context, mux *runtime.Se
})
mux.Handle("POST", pattern_ManagementService_SearchUserMemberships_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ManagementService_SearchUserMemberships_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_ManagementService_SearchUserMemberships_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_ManagementService_GetPasswordComplexityPolicy_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@ -6146,6 +6201,8 @@ var (
pattern_ManagementService_SetInitialPassword_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2, 2, 3}, []string{"users", "id", "password", "_initialize"}, ""))
pattern_ManagementService_SearchUserMemberships_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2, 2, 3}, []string{"users", "user_id", "memberships", "_search"}, ""))
pattern_ManagementService_GetPasswordComplexityPolicy_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"policies", "passwords", "complexity"}, ""))
pattern_ManagementService_GetDefaultPasswordComplexityPolicy_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"policies", "passwords", "complexity", "default"}, ""))
@ -6384,6 +6441,8 @@ var (
forward_ManagementService_SetInitialPassword_0 = runtime.ForwardResponseMessage
forward_ManagementService_SearchUserMemberships_0 = runtime.ForwardResponseMessage
forward_ManagementService_GetPasswordComplexityPolicy_0 = runtime.ForwardResponseMessage
forward_ManagementService_GetDefaultPasswordComplexityPolicy_0 = runtime.ForwardResponseMessage

View File

@ -2017,6 +2017,26 @@ func (mr *MockManagementServiceClientMockRecorder) SearchUserGrants(arg0, arg1 i
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchUserGrants", reflect.TypeOf((*MockManagementServiceClient)(nil).SearchUserGrants), varargs...)
}
// SearchUserMemberships mocks base method
func (m *MockManagementServiceClient) SearchUserMemberships(arg0 context.Context, arg1 *management.UserMembershipSearchRequest, arg2 ...grpc.CallOption) (*management.UserMembershipSearchResponse, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "SearchUserMemberships", varargs...)
ret0, _ := ret[0].(*management.UserMembershipSearchResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SearchUserMemberships indicates an expected call of SearchUserMemberships
func (mr *MockManagementServiceClientMockRecorder) SearchUserMemberships(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchUserMemberships", reflect.TypeOf((*MockManagementServiceClient)(nil).SearchUserMemberships), varargs...)
}
// SearchUsers mocks base method
func (m *MockManagementServiceClient) SearchUsers(arg0 context.Context, arg1 *management.UserSearchRequest, arg2 ...grpc.CallOption) (*management.UserSearchResponse, error) {
m.ctrl.T.Helper()

View File

@ -373,6 +373,17 @@ service ManagementService {
};
}
rpc SearchUserMemberships(UserMembershipSearchRequest) returns (UserMembershipSearchResponse) {
option (google.api.http) = {
post: "/users/{user_id}/memberships/_search"
body: "*"
};
option (caos.zitadel.utils.v1.auth_option) = {
permission: "user.membership.read"
};
}
// returns default policy if nothing other set on organisation
rpc GetPasswordComplexityPolicy(google.protobuf.Empty) returns (PasswordComplexityPolicy) {
option (google.api.http) = {
@ -2526,7 +2537,6 @@ message UserGrantUpdate {
string user_id = 1;
string id = 2;
repeated string role_keys = 3;
string grant_id = 4;
}
message UserGrantRemoveBulk {
@ -2626,6 +2636,7 @@ enum UserGrantSearchKey {
USERGRANTSEARCHKEY_USER_ID = 2;
USERGRANTSEARCHKEY_ORG_ID = 3;
USERGRANTSEARCHKEY_ROLE_KEY = 4;
USERGRANTSEARCHKEY_GRANT_ID = 5;
}
message ProjectUserGrantSearchRequest {
@ -2641,3 +2652,51 @@ message ProjectGrantUserGrantSearchRequest {
uint64 limit = 3;
repeated UserGrantSearchQuery queries = 4;
}
message UserMembershipSearchResponse {
uint64 offset = 1;
uint64 limit = 2;
uint64 total_result = 3;
repeated UserMembershipView result = 4;
uint64 processed_sequence = 5;
google.protobuf.Timestamp view_timestamp = 6;
}
message UserMembershipSearchRequest {
string user_id = 1;
uint64 offset = 2;
uint64 limit = 3;
repeated UserMembershipSearchQuery queries = 4;
}
message UserMembershipSearchQuery {
UserMembershipSearchKey key = 1 [(validate.rules).enum = {not_in: [0]}];
SearchMethod method = 2 [(validate.rules).enum = {in: [0]}];
string value = 3;
}
enum UserMembershipSearchKey {
USERMEMBERSHIPSEARCHKEY_UNSPECIFIED = 0;
USERMEMBERSHIPSEARCHKEY_TYPE = 1;
USERMEMBERSHIPSEARCHKEY_OBJECT_ID = 2;
}
message UserMembershipView {
string user_id = 1;
MemberType member_type = 2;
string aggregate_id = 3;
string object_id = 4;
repeated string roles = 5;
string display_name = 6;
google.protobuf.Timestamp creation_date = 7;
google.protobuf.Timestamp change_date = 8;
uint64 sequence = 9;
string resource_owner = 10;
}
enum MemberType {
MEMBERTYPE_UNSPECIFIED = 0;
MEMBERTYPE_ORGANISATION = 1;
MEMBERTYPE_PROJECT = 2;
MEMBERTYPE_PROJECT_GRANT = 3;
}