fix: project grants (#4031)

* fix: filter granted memberships correctly

* fix: only show changes of granted project

* Apply suggestions from code review

Co-authored-by: Fabi <38692350+hifabienne@users.noreply.github.com>

* Update internal/query/user_membership.go

Co-authored-by: Fabi <38692350+hifabienne@users.noreply.github.com>

Co-authored-by: Fabi <38692350+hifabienne@users.noreply.github.com>
This commit is contained in:
Livio Spring 2022-07-27 09:55:44 +02:00 committed by GitHub
parent c15577c1f9
commit 5bd9badbcf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 224 additions and 44 deletions

View File

@ -19,6 +19,7 @@ export enum ChangeType {
USER = 'user', USER = 'user',
ORG = 'org', ORG = 'org',
PROJECT = 'project', PROJECT = 'project',
PROJECT_GRANT= 'project-grant',
APP = 'app', APP = 'app',
} }
@ -93,6 +94,9 @@ export class ChangesComponent implements OnInit, OnDestroy {
case ChangeType.PROJECT: case ChangeType.PROJECT:
first = this.mgmtUserService.listProjectChanges(this.id, 30, 0); first = this.mgmtUserService.listProjectChanges(this.id, 30, 0);
break; break;
case ChangeType.PROJECT_GRANT:
first = this.mgmtUserService.listProjectGrantChanges(this.id, this.secId, 30, 0);
break;
case ChangeType.ORG: case ChangeType.ORG:
first = this.mgmtUserService.listOrgChanges(30, 0); first = this.mgmtUserService.listOrgChanges(30, 0);
break; break;
@ -126,6 +130,9 @@ export class ChangesComponent implements OnInit, OnDestroy {
case ChangeType.PROJECT: case ChangeType.PROJECT:
more = this.mgmtUserService.listProjectChanges(this.id, 20, cursor); more = this.mgmtUserService.listProjectChanges(this.id, 20, cursor);
break; break;
case ChangeType.PROJECT_GRANT:
more = this.mgmtUserService.listProjectGrantChanges(this.id, this.secId, 20, cursor);
break;
case ChangeType.ORG: case ChangeType.ORG:
more = this.mgmtUserService.listOrgChanges(20, cursor); more = this.mgmtUserService.listOrgChanges(20, cursor);
break; break;

View File

@ -54,7 +54,7 @@
</ng-template> </ng-template>
<div metainfo> <div metainfo>
<cnsl-changes *ngIf="project" [changeType]="ChangeType.PROJECT" [id]="project.projectId"></cnsl-changes> <cnsl-changes *ngIf="project" [changeType]="ChangeType.PROJECT_GRANT" [id]="project.projectId" [secId]="project.grantId"></cnsl-changes>
</div> </div>
</cnsl-meta-layout> </cnsl-meta-layout>
</div> </div>

View File

@ -221,6 +221,8 @@ import {
ListPersonalAccessTokensResponse, ListPersonalAccessTokensResponse,
ListProjectChangesRequest, ListProjectChangesRequest,
ListProjectChangesResponse, ListProjectChangesResponse,
ListProjectGrantChangesRequest,
ListProjectGrantChangesResponse,
ListProjectGrantMemberRolesRequest, ListProjectGrantMemberRolesRequest,
ListProjectGrantMemberRolesResponse, ListProjectGrantMemberRolesResponse,
ListProjectGrantMembersRequest, ListProjectGrantMembersRequest,
@ -1776,6 +1778,28 @@ export class ManagementService {
return this.grpcService.mgmt.listProjectChanges(req, null).then((resp) => resp.toObject()); return this.grpcService.mgmt.listProjectChanges(req, null).then((resp) => resp.toObject());
} }
public listProjectGrantChanges(
projectId: string,
grantId: string,
limit: number,
sequence: number,
): Promise<ListProjectGrantChangesResponse.AsObject> {
const req = new ListProjectGrantChangesRequest();
req.setProjectId(projectId);
req.setGrantId(grantId);
const query = new ChangeQuery();
if (limit) {
query.setLimit(limit);
}
if (sequence) {
query.setSequence(sequence);
}
req.setQuery(query);
return this.grpcService.mgmt.listProjectGrantChanges(req, null).then((resp) => resp.toObject());
}
public listUserChanges(userId: string, limit: number, sequence: number): Promise<ListUserChangesResponse.AsObject> { public listUserChanges(userId: string, limit: number, sequence: number): Promise<ListUserChangesResponse.AsObject> {
const req = new ListUserChangesRequest(); const req = new ListUserChangesRequest();
req.setUserId(userId); req.setUserId(userId);

View File

@ -1417,6 +1417,19 @@ Removes an app key
DELETE: /projects/{project_id}/apps/{app_id}/keys/{key_id} DELETE: /projects/{project_id}/apps/{app_id}/keys/{key_id}
### ListProjectGrantChanges
> **rpc** ListProjectGrantChanges([ListProjectGrantChangesRequest](#listprojectgrantchangesrequest))
[ListProjectGrantChangesResponse](#listprojectgrantchangesresponse)
Returns the history of the project grant (each event)
Limit should always be set, there is a default limit set by the service
POST: /projects/{project_id}/grants/{grant_id}/changes/_search
### GetProjectGrantByID ### GetProjectGrantByID
> **rpc** GetProjectGrantByID([GetProjectGrantByIDRequest](#getprojectgrantbyidrequest)) > **rpc** GetProjectGrantByID([GetProjectGrantByIDRequest](#getprojectgrantbyidrequest))
@ -5743,6 +5756,30 @@ This is an empty request
### ListProjectGrantChangesRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| query | zitadel.change.v1.ChangeQuery | list limitations and ordering | |
| project_id | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
| grant_id | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
### ListProjectGrantChangesResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| result | repeated zitadel.change.v1.Change | zitadel.v1.ListDetails details = 1; was always returned empty (as we cannot get the necessary infos) | |
### ListProjectGrantMemberRolesRequest ### ListProjectGrantMemberRolesRequest

View File

@ -177,8 +177,12 @@ func (s *Server) ListMyProjectOrgs(ctx context.Context, req *auth_pb.ListMyProje
if !isIAMAdmin(memberships.Memberships) { if !isIAMAdmin(memberships.Memberships) {
ids := make([]string, 0, len(memberships.Memberships)) ids := make([]string, 0, len(memberships.Memberships))
for _, grant := range memberships.Memberships { for _, membership := range memberships.Memberships {
ids = appendIfNotExists(ids, grant.ResourceOwner) orgID := membership.ResourceOwner
if membership.ProjectGrant != nil && membership.ProjectGrant.GrantedOrgID != "" {
orgID = membership.ProjectGrant.GrantedOrgID
}
ids = appendIfNotExists(ids, orgID)
} }
idsQuery, err := query.NewOrgIDsSearchQuery(ids...) idsQuery, err := query.NewOrgIDsSearchQuery(ids...)

View File

@ -55,6 +55,17 @@ func (s *Server) ListProjects(ctx context.Context, req *mgmt_pb.ListProjectsRequ
}, nil }, nil
} }
func (s *Server) ListProjectGrantChanges(ctx context.Context, req *mgmt_pb.ListProjectGrantChangesRequest) (*mgmt_pb.ListProjectGrantChangesResponse, error) {
sequence, limit, asc := change_grpc.ChangeQueryToQuery(req.Query)
res, err := s.query.ProjectGrantChanges(ctx, req.ProjectId, req.GrantId, sequence, limit, asc, s.auditLogRetention)
if err != nil {
return nil, err
}
return &mgmt_pb.ListProjectGrantChangesResponse{
Result: change_grpc.ChangesToPb(res.Changes, s.assetAPIPrefix(ctx)),
}, nil
}
func (s *Server) ListGrantedProjects(ctx context.Context, req *mgmt_pb.ListGrantedProjectsRequest) (*mgmt_pb.ListGrantedProjectsResponse, error) { func (s *Server) ListGrantedProjects(ctx context.Context, req *mgmt_pb.ListGrantedProjectsRequest) (*mgmt_pb.ListGrantedProjectsResponse, error) {
queries, err := listGrantedProjectsRequestToModel(req) queries, err := listGrantedProjectsRequestToModel(req)
if err != nil { if err != nil {

View File

@ -34,8 +34,12 @@ func (repo *UserMembershipRepo) searchUserMemberships(ctx context.Context) (_ []
if err != nil { if err != nil {
return nil, err return nil, err
} }
grantedIDQuery, err := query.NewMembershipGrantedOrgIDSearchQuery(ctxData.OrgID)
if err != nil {
return nil, err
}
memberships, err := repo.Queries.Memberships(ctx, &query.MembershipSearchQuery{ memberships, err := repo.Queries.Memberships(ctx, &query.MembershipSearchQuery{
Queries: []query.SearchQuery{userIDQuery, orgIDsQuery}, Queries: []query.SearchQuery{userIDQuery, query.Or(orgIDsQuery, grantedIDQuery)},
}) })
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -50,6 +50,17 @@ func (q *Queries) ProjectChanges(ctx context.Context, projectID string, lastSequ
return q.changes(ctx, query, lastSequence, limit, sortAscending, auditLogRetention) return q.changes(ctx, query, lastSequence, limit, sortAscending, auditLogRetention)
} }
func (q *Queries) ProjectGrantChanges(ctx context.Context, projectID, grantID string, lastSequence uint64, limit uint64, sortAscending bool, auditLogRetention time.Duration) (*Changes, error) {
query := func(query *eventstore.SearchQuery) {
query.AggregateTypes(project.AggregateType).
AggregateIDs(projectID).
EventData(map[string]interface{}{
"grantId": grantID,
})
}
return q.changes(ctx, query, lastSequence, limit, sortAscending, auditLogRetention)
}
func (q *Queries) ApplicationChanges(ctx context.Context, projectID, appID string, lastSequence uint64, limit uint64, sortAscending bool, auditLogRetention time.Duration) (*Changes, error) { func (q *Queries) ApplicationChanges(ctx context.Context, projectID, appID string, lastSequence uint64, limit uint64, sortAscending bool, auditLogRetention time.Duration) (*Changes, error) {
query := func(query *eventstore.SearchQuery) { query := func(query *eventstore.SearchQuery) {
query.AggregateTypes(project.AggregateType). query.AggregateTypes(project.AggregateType).

View File

@ -312,6 +312,28 @@ func ListComparisonFromMethod(m domain.SearchMethod) ListComparison {
} }
} }
type or struct {
queries []SearchQuery
}
func Or(queries ...SearchQuery) *or {
return &or{
queries: queries,
}
}
func (q *or) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
return query.Where(q.comp())
}
func (q *or) comp() sq.Sqlizer {
queries := make([]sq.Sqlizer, 0)
for _, query := range q.queries {
queries = append(queries, query.comp())
}
return sq.Or(queries)
}
type BoolQuery struct { type BoolQuery struct {
Column Column Column Column
Value bool Value bool

View File

@ -48,9 +48,10 @@ type ProjectMembership struct {
} }
type ProjectGrantMembership struct { type ProjectGrantMembership struct {
ProjectID string ProjectID string
ProjectName string ProjectName string
GrantID string GrantID string
GrantedOrgID string
} }
type MembershipSearchQuery struct { type MembershipSearchQuery struct {
@ -78,6 +79,10 @@ func NewMembershipResourceOwnersSearchQuery(ids ...string) (SearchQuery, error)
return NewListQuery(membershipResourceOwner, list, ListIn) return NewListQuery(membershipResourceOwner, list, ListIn)
} }
func NewMembershipGrantedOrgIDSearchQuery(id string) (SearchQuery, error) {
return NewTextQuery(ProjectGrantColumnGrantedOrgID, id, TextEquals)
}
func NewMembershipProjectIDQuery(value string) (SearchQuery, error) { func NewMembershipProjectIDQuery(value string) (SearchQuery, error) {
return NewTextQuery(membershipProjectID, value, TextEquals) return NewTextQuery(membershipProjectID, value, TextEquals)
} }
@ -173,6 +178,10 @@ var (
name: projection.ProjectGrantMemberGrantIDCol, name: projection.ProjectGrantMemberGrantIDCol,
table: membershipAlias, table: membershipAlias,
} }
membershipGrantGrantedOrgID = Column{
name: projection.ProjectGrantColumnGrantedOrgID,
table: membershipAlias,
}
membershipFrom = "(" + membershipFrom = "(" +
prepareOrgMember() + prepareOrgMember() +
@ -197,12 +206,14 @@ func prepareMembershipsQuery() (sq.SelectBuilder, func(*sql.Rows) (*Memberships,
membershipIAMID.identifier(), membershipIAMID.identifier(),
membershipProjectID.identifier(), membershipProjectID.identifier(),
membershipGrantID.identifier(), membershipGrantID.identifier(),
ProjectGrantColumnGrantedOrgID.identifier(),
ProjectColumnName.identifier(), ProjectColumnName.identifier(),
OrgColumnName.identifier(), OrgColumnName.identifier(),
countColumn.identifier(), countColumn.identifier(),
).From(membershipFrom). ).From(membershipFrom).
LeftJoin(join(ProjectColumnID, membershipProjectID)). LeftJoin(join(ProjectColumnID, membershipProjectID)).
LeftJoin(join(OrgColumnID, membershipOrgID)). LeftJoin(join(OrgColumnID, membershipOrgID)).
LeftJoin(join(ProjectGrantColumnGrantID, membershipGrantID)).
PlaceholderFormat(sq.Dollar), PlaceholderFormat(sq.Dollar),
func(rows *sql.Rows) (*Memberships, error) { func(rows *sql.Rows) (*Memberships, error) {
memberships := make([]*Membership, 0) memberships := make([]*Membership, 0)
@ -210,14 +221,15 @@ func prepareMembershipsQuery() (sq.SelectBuilder, func(*sql.Rows) (*Memberships,
for rows.Next() { for rows.Next() {
var ( var (
membership = new(Membership) membership = new(Membership)
orgID = sql.NullString{} orgID = sql.NullString{}
iamID = sql.NullString{} iamID = sql.NullString{}
projectID = sql.NullString{} projectID = sql.NullString{}
grantID = sql.NullString{} grantID = sql.NullString{}
roles = pq.StringArray{} grantedOrgID = sql.NullString{}
projectName = sql.NullString{} roles = pq.StringArray{}
orgName = sql.NullString{} projectName = sql.NullString{}
orgName = sql.NullString{}
) )
err := rows.Scan( err := rows.Scan(
@ -231,6 +243,7 @@ func prepareMembershipsQuery() (sq.SelectBuilder, func(*sql.Rows) (*Memberships,
&iamID, &iamID,
&projectID, &projectID,
&grantID, &grantID,
&grantedOrgID,
&projectName, &projectName,
&orgName, &orgName,
&count, &count,
@ -252,11 +265,12 @@ func prepareMembershipsQuery() (sq.SelectBuilder, func(*sql.Rows) (*Memberships,
IAMID: iamID.String, IAMID: iamID.String,
Name: iamID.String, Name: iamID.String,
} }
} else if projectID.Valid && grantID.Valid { } else if projectID.Valid && grantID.Valid && grantedOrgID.Valid {
membership.ProjectGrant = &ProjectGrantMembership{ membership.ProjectGrant = &ProjectGrantMembership{
ProjectID: projectID.String, ProjectID: projectID.String,
ProjectName: projectName.String, ProjectName: projectName.String,
GrantID: grantID.String, GrantID: grantID.String,
GrantedOrgID: grantedOrgID.String,
} }
} else if projectID.Valid { } else if projectID.Valid {
membership.Project = &ProjectMembership{ membership.Project = &ProjectMembership{
@ -346,7 +360,8 @@ func prepareProjectGrantMember() string {
"NULL::STRING AS "+membershipIAMID.name, "NULL::STRING AS "+membershipIAMID.name,
ProjectGrantMemberProjectID.identifier(), ProjectGrantMemberProjectID.identifier(),
ProjectGrantMemberGrantID.identifier(), ProjectGrantMemberGrantID.identifier(),
).From(projectGrantMemberTable.identifier()).MustSql() ).From(projectGrantMemberTable.identifier()).
MustSql()
return stmt return stmt
} }

View File

@ -23,6 +23,7 @@ var (
", memberships.id" + ", memberships.id" +
", memberships.project_id" + ", memberships.project_id" +
", memberships.grant_id" + ", memberships.grant_id" +
", projections.project_grants.granted_org_id" +
", projections.projects.name" + ", projections.projects.name" +
", projections.orgs.name" + ", projections.orgs.name" +
", COUNT(*) OVER ()" + ", COUNT(*) OVER ()" +
@ -80,7 +81,8 @@ var (
" FROM projections.project_grant_members as members" + " FROM projections.project_grant_members as members" +
") AS memberships" + ") AS memberships" +
" LEFT JOIN projections.projects ON memberships.project_id = projections.projects.id" + " LEFT JOIN projections.projects ON memberships.project_id = projections.projects.id" +
" LEFT JOIN projections.orgs ON memberships.org_id = projections.orgs.id") " LEFT JOIN projections.orgs ON memberships.org_id = projections.orgs.id" +
" LEFT JOIN projections.project_grants ON memberships.grant_id = projections.project_grants.grant_id")
membershipCols = []string{ membershipCols = []string{
"user_id", "user_id",
"roles", "roles",
@ -92,6 +94,7 @@ var (
"instance_id", "instance_id",
"project_id", "project_id",
"grant_id", "grant_id",
"granted_org_id",
"name", //project name "name", //project name
"name", //org name "name", //org name
"count", "count",
@ -141,6 +144,7 @@ func Test_MembershipPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
"org-name", "org-name",
}, },
}, },
@ -184,6 +188,7 @@ func Test_MembershipPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
}, },
}, },
), ),
@ -224,6 +229,7 @@ func Test_MembershipPrepares(t *testing.T) {
nil, nil,
"project-id", "project-id",
nil, nil,
nil,
"project-name", "project-name",
nil, nil,
}, },
@ -266,6 +272,7 @@ func Test_MembershipPrepares(t *testing.T) {
nil, nil,
"project-id", "project-id",
"grant-id", "grant-id",
"granted-org-id",
"project-name", "project-name",
nil, nil,
}, },
@ -285,9 +292,10 @@ func Test_MembershipPrepares(t *testing.T) {
Sequence: 20211202, Sequence: 20211202,
ResourceOwner: "ro", ResourceOwner: "ro",
ProjectGrant: &ProjectGrantMembership{ ProjectGrant: &ProjectGrantMembership{
GrantID: "grant-id", GrantID: "grant-id",
ProjectID: "project-id", ProjectID: "project-id",
ProjectName: "project-name", ProjectName: "project-name",
GrantedOrgID: "granted-org-id",
}, },
}, },
}, },
@ -313,6 +321,7 @@ func Test_MembershipPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
"org-name", "org-name",
}, },
{ {
@ -328,6 +337,7 @@ func Test_MembershipPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
}, },
{ {
"user-id", "user-id",
@ -340,6 +350,7 @@ func Test_MembershipPrepares(t *testing.T) {
nil, nil,
"project-id", "project-id",
nil, nil,
nil,
"project-name", "project-name",
nil, nil,
}, },
@ -354,6 +365,7 @@ func Test_MembershipPrepares(t *testing.T) {
nil, nil,
"project-id", "project-id",
"grant-id", "grant-id",
"granted-org-id",
"project-name", "project-name",
nil, nil,
}, },
@ -400,9 +412,10 @@ func Test_MembershipPrepares(t *testing.T) {
Sequence: 20211202, Sequence: 20211202,
ResourceOwner: "ro", ResourceOwner: "ro",
ProjectGrant: &ProjectGrantMembership{ ProjectGrant: &ProjectGrantMembership{
ProjectID: "project-id", ProjectID: "project-id",
GrantID: "grant-id", GrantID: "grant-id",
ProjectName: "project-name", ProjectName: "project-name",
GrantedOrgID: "granted-org-id",
}, },
}, },
}, },

View File

@ -16,8 +16,12 @@ func (q *Queries) MyZitadelPermissions(ctx context.Context, orgID, userID string
if err != nil { if err != nil {
return nil, err return nil, err
} }
grantedOrgIDQuery, err := NewMembershipGrantedOrgIDSearchQuery(orgID)
if err != nil {
return nil, err
}
memberships, err := q.Memberships(ctx, &MembershipSearchQuery{ memberships, err := q.Memberships(ctx, &MembershipSearchQuery{
Queries: []SearchQuery{userIDQuery, orgIDsQuery}, Queries: []SearchQuery{userIDQuery, Or(orgIDsQuery, grantedOrgIDQuery)},
}) })
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -2,47 +2,47 @@ package management
import ( import (
"github.com/zitadel/zitadel/internal/api/grpc/server/middleware" "github.com/zitadel/zitadel/internal/api/grpc/server/middleware"
"github.com/zitadel/zitadel/pkg/grpc/change"
) )
func (c *ListUserChangesResponse) Localizers() []middleware.Localizer { func (c *ListUserChangesResponse) Localizers() []middleware.Localizer {
if c == nil { if c == nil {
return nil return nil
} }
localizers := make([]middleware.Localizer, len(c.Result)) return changesLocalizers(c.Result)
for i, change := range c.Result {
localizers[i] = change.EventType
}
return localizers
} }
func (c *ListOrgChangesResponse) Localizers() []middleware.Localizer { func (c *ListOrgChangesResponse) Localizers() []middleware.Localizer {
if c == nil { if c == nil {
return nil return nil
} }
localizers := make([]middleware.Localizer, len(c.Result)) return changesLocalizers(c.Result)
for i, change := range c.Result {
localizers[i] = change.EventType
}
return localizers
} }
func (c *ListProjectChangesResponse) Localizers() []middleware.Localizer { func (c *ListProjectChangesResponse) Localizers() []middleware.Localizer {
if c == nil { if c == nil {
return nil return nil
} }
localizers := make([]middleware.Localizer, len(c.Result)) return changesLocalizers(c.Result)
for i, change := range c.Result { }
localizers[i] = change.EventType
func (c *ListProjectGrantChangesResponse) Localizers() []middleware.Localizer {
if c == nil {
return nil
} }
return localizers return changesLocalizers(c.Result)
} }
func (c *ListAppChangesResponse) Localizers() []middleware.Localizer { func (c *ListAppChangesResponse) Localizers() []middleware.Localizer {
if c == nil { if c == nil {
return nil return nil
} }
localizers := make([]middleware.Localizer, len(c.Result)) return changesLocalizers(c.Result)
for i, change := range c.Result { }
func changesLocalizers(changes []*change.Change) []middleware.Localizer {
localizers := make([]middleware.Localizer, len(changes))
for i, change := range changes {
localizers[i] = change.EventType localizers[i] = change.EventType
} }
return localizers return localizers

View File

@ -1045,6 +1045,7 @@ service ManagementService {
option (zitadel.v1.auth_option) = { option (zitadel.v1.auth_option) = {
permission: "project.read" permission: "project.read"
check_field_name: "ProjectId"
}; };
} }
@ -1470,6 +1471,19 @@ service ManagementService {
}; };
} }
// Returns the history of the project grant (each event)
// Limit should always be set, there is a default limit set by the service
rpc ListProjectGrantChanges(ListProjectGrantChangesRequest) returns (ListProjectGrantChangesResponse) {
option (google.api.http) = {
post: "/projects/{project_id}/grants/{grant_id}/changes/_search"
};
option (zitadel.v1.auth_option) = {
permission: "project.grant.read"
check_field_name: "GrantId"
};
}
// Returns a project grant (ProjectGrant = Grant another organisation for my project) // Returns a project grant (ProjectGrant = Grant another organisation for my project)
rpc GetProjectGrantByID(GetProjectGrantByIDRequest) returns (GetProjectGrantByIDResponse) { rpc GetProjectGrantByID(GetProjectGrantByIDRequest) returns (GetProjectGrantByIDResponse) {
option (google.api.http) = { option (google.api.http) = {
@ -4100,6 +4114,20 @@ message RemoveAppKeyResponse {
zitadel.v1.ObjectDetails details = 1; zitadel.v1.ObjectDetails details = 1;
} }
message ListProjectGrantChangesRequest {
//list limitations and ordering
zitadel.change.v1.ChangeQuery query = 1;
string project_id = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
string grant_id = 3 [(validate.rules).string = {min_len: 1, max_len: 200}];
}
message ListProjectGrantChangesResponse {
reserved 1;
reserved "details";
// zitadel.v1.ListDetails details = 1; was always returned empty (as we cannot get the necessary infos)
repeated zitadel.change.v1.Change result = 2;
}
message GetProjectGrantByIDRequest { message GetProjectGrantByIDRequest {
string project_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}]; string project_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
string grant_id = 2 [(validate.rules).string = {min_len: 1, max_len: 200}]; string grant_id = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];