mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 20:37:23 +00:00
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:
parent
4dabecd8d4
commit
75f1c4c576
@ -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,6 +207,7 @@ InternalAuthZ:
|
||||
- "user.grant.read"
|
||||
- "user.grant.write"
|
||||
- "user.grant.delete"
|
||||
- "user.membership.read"
|
||||
- Role: 'PROJECT_GRANT_OWNER'
|
||||
Permissions:
|
||||
- "project.read"
|
||||
@ -208,3 +215,4 @@ InternalAuthZ:
|
||||
- "project.grant.member.read"
|
||||
- "user.read"
|
||||
- "user.grant.read"
|
||||
- "user.membership.read"
|
@ -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
10
go.mod
@ -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
10
go.sum
@ -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=
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -0,0 +1,8 @@
|
||||
package eventstore
|
||||
|
||||
const (
|
||||
projectReadPerm = "project.read"
|
||||
orgMemberReadPerm = "org.member.read"
|
||||
projectMemberReadPerm = "project.member.read"
|
||||
projectGrantMemberReadPerm = "project.member.read"
|
||||
)
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
88
internal/user/model/user_membership_view.go
Normal file
88
internal/user/model/user_membership_view.go
Normal 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})
|
||||
}
|
@ -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)
|
||||
|
||||
|
139
internal/user/repository/view/model/user_membership.go
Normal file
139
internal/user/repository/view/model/user_membership.go
Normal 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
|
||||
}
|
67
internal/user/repository/view/model/user_membership_query.go
Normal file
67
internal/user/repository/view/model/user_membership_query.go
Normal 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 ""
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
70
internal/user/repository/view/usermembership_view.go
Normal file
70
internal/user/repository/view/usermembership_view.go
Normal 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)
|
||||
}
|
@ -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)
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
}
|
||||
|
15
migrations/cockroach/V1.3__usermembership.sql
Normal file
15
migrations/cockroach/V1.3__usermembership.sql
Normal 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)
|
||||
);
|
@ -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
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user