feat: project v2beta resource API (#9742)

# Which Problems Are Solved

Resource management of projects and sub-resources was before limited by
the context provided by the management API, which would mean you could
only manage resources belonging to a specific organization.

# How the Problems Are Solved

With the addition of a resource-based API, it is now possible to manage
projects and sub-resources on the basis of the resources themselves,
which means that as long as you have the permission for the resource,
you can create, read, update and delete it.

- CreateProject to create a project under an organization
- UpdateProject to update an existing project
- DeleteProject to delete an existing project
- DeactivateProject and ActivateProject to change the status of a
project
- GetProject to query for a specific project with an identifier
- ListProject to query for projects and granted projects
- CreateProjectGrant to create a project grant with project and granted
organization
- UpdateProjectGrant to update the roles of a project grant
- DeactivateProjectGrant and ActivateProjectGrant to change the status
of a project grant
- DeleteProjectGrant to delete an existing project grant
- ListProjectGrants to query for project grants
- AddProjectRole to add a role to an existing project
- UpdateProjectRole to change texts of an existing role
- RemoveProjectRole to remove an existing role
- ListProjectRoles to query for project roles

# Additional Changes

- Changes to ListProjects, which now contains granted projects as well
- Changes to messages as defined in the
[API_DESIGN](https://github.com/zitadel/zitadel/blob/main/API_DESIGN.md)
- Permission checks for project functionality on query and command side
- Added testing to unit tests on command side
- Change update endpoints to no error returns if nothing changes in the
resource
- Changed all integration test utility to the new service
- ListProjects now also correctly lists `granted projects`
- Permission checks for project grant and project role functionality on
query and command side
- Change existing pre checks so that they also work resource specific
without resourceowner
- Added the resourceowner to the grant and role if no resourceowner is
provided
- Corrected import tests with project grants and roles
- Added testing to unit tests on command side
- Change update endpoints to no error returns if nothing changes in the
resource
- Changed all integration test utility to the new service
- Corrected some naming in the proto files to adhere to the API_DESIGN

# Additional Context

Closes #9177

---------

Co-authored-by: Livio Spring <[email protected]>
This commit is contained in:
Stefan Benz
2025-05-21 14:40:47 +02:00
committed by GitHub
co-authored by Livio Spring
parent 6889d6a1da
commit 7eb45c6cfd
64 changed files with 9821 additions and 1037 deletions
+4
View File
@@ -46,6 +46,7 @@ import (
oidc_v2beta "github.com/zitadel/zitadel/internal/api/grpc/oidc/v2beta"
org_v2 "github.com/zitadel/zitadel/internal/api/grpc/org/v2"
org_v2beta "github.com/zitadel/zitadel/internal/api/grpc/org/v2beta"
project_v2beta "github.com/zitadel/zitadel/internal/api/grpc/project/v2beta"
"github.com/zitadel/zitadel/internal/api/grpc/resources/debug_events/debug_events"
user_v3_alpha "github.com/zitadel/zitadel/internal/api/grpc/resources/user/v3alpha"
userschema_v3_alpha "github.com/zitadel/zitadel/internal/api/grpc/resources/userschema/v3alpha"
@@ -491,6 +492,9 @@ func startAPIs(
if err := apis.RegisterService(ctx, action_v2_beta.CreateServer(config.SystemDefaults, commands, queries, domain.AllActionFunctions, apis.ListGrpcMethods, apis.ListGrpcServices)); err != nil {
return nil, err
}
if err := apis.RegisterService(ctx, project_v2beta.CreateServer(config.SystemDefaults, commands, queries, permissionCheck)); err != nil {
return nil, err
}
if err := apis.RegisterService(ctx, userschema_v3_alpha.CreateServer(config.SystemDefaults, commands, queries)); err != nil {
return nil, err
}
+8
View File
@@ -364,6 +364,14 @@ module.exports = {
categoryLinkSource: "auto",
},
},
project_v2beta: {
specPath: ".artifacts/openapi/zitadel/project/v2beta/project_service.swagger.json",
outputDir: "docs/apis/resources/project_service_v2",
sidebarOptions: {
groupPathsBy: "tag",
categoryLinkSource: "auto",
},
},
instance_v2: {
specPath: ".artifacts/openapi/zitadel/instance/v2beta/instance_service.swagger.json",
outputDir: "docs/apis/resources/instance_service_v2",
+12
View File
@@ -12,6 +12,7 @@ const sidebar_api_feature_service_v2 = require("./docs/apis/resources/feature_se
const sidebar_api_org_service_v2 = require("./docs/apis/resources/org_service_v2/sidebar.ts").default
const sidebar_api_idp_service_v2 = require("./docs/apis/resources/idp_service_v2/sidebar.ts").default
const sidebar_api_actions_v2 = require("./docs/apis/resources/action_service_v2/sidebar.ts").default
const sidebar_api_project_service_v2 = require("./docs/apis/resources/project_service_v2/sidebar.ts").default
const sidebar_api_webkey_service_v2 = require("./docs/apis/resources/webkey_service_v2/sidebar.ts").default
const sidebar_api_instance_service_v2 = require("./docs/apis/resources/instance_service_v2/sidebar.ts").default
@@ -843,6 +844,17 @@ module.exports = {
},
{
type: "category",
label: "Project (Beta)",
link: {
type: "generated-index",
title: "Project Service API (Beta)",
slug: "/apis/resources/project_service_v2",
description:
"This API is intended to manage projects and subresources for ZITADEL. \n"+
"\n" +
"This service is in beta state. It can AND will continue breaking until a stable version is released.",
},
items: sidebar_api_project_service_v2,
label: "Instance (Beta)",
link: {
type: "generated-index",
@@ -580,7 +580,7 @@ func TestServer_ExecutionTargetPreUserinfo(t *testing.T) {
isolatedIAMCtx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
ctxLoginClient := instance.WithAuthorization(CTX, integration.UserTypeLogin)
client, err := instance.CreateOIDCImplicitFlowClient(isolatedIAMCtx, redirectURIImplicit, loginV2)
client, err := instance.CreateOIDCImplicitFlowClient(isolatedIAMCtx, t, redirectURIImplicit, loginV2)
require.NoError(t, err)
type want struct {
@@ -893,7 +893,7 @@ func TestServer_ExecutionTargetPreAccessToken(t *testing.T) {
isolatedIAMCtx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
ctxLoginClient := instance.WithAuthorization(CTX, integration.UserTypeLogin)
client, err := instance.CreateOIDCImplicitFlowClient(isolatedIAMCtx, redirectURIImplicit, loginV2)
client, err := instance.CreateOIDCImplicitFlowClient(isolatedIAMCtx, t, redirectURIImplicit, loginV2)
require.NoError(t, err)
type want struct {
@@ -1255,10 +1255,9 @@ func createSAMLSP(t *testing.T, idpMetadata *saml.EntityDescriptor, binding stri
}
func createSAMLApplication(ctx context.Context, t *testing.T, instance *integration.Instance, idpMetadata *saml.EntityDescriptor, binding string, projectRoleCheck, hasProjectCheck bool) (string, string, *samlsp.Middleware) {
project, err := instance.CreateProjectWithPermissionCheck(ctx, projectRoleCheck, hasProjectCheck)
require.NoError(t, err)
project := instance.CreateProject(ctx, t, instance.DefaultOrg.GetId(), gofakeit.AppName(), projectRoleCheck, hasProjectCheck)
rootURL, sp := createSAMLSP(t, idpMetadata, binding)
_, err = instance.CreateSAMLClient(ctx, project.GetId(), sp)
_, err := instance.CreateSAMLClient(ctx, project.GetId(), sp)
require.NoError(t, err)
return project.GetId(), rootURL, sp
}
+3 -3
View File
@@ -736,7 +736,7 @@ func (s *Server) getProjectsAndApps(ctx context.Context, org string) ([]*v1_pb.D
if err != nil {
return nil, nil, nil, nil, nil, err
}
queriedProjects, err := s.query.SearchProjects(ctx, &query.ProjectSearchQueries{Queries: []query.SearchQuery{projectSearch}})
queriedProjects, err := s.query.SearchProjects(ctx, &query.ProjectSearchQueries{Queries: []query.SearchQuery{projectSearch}}, nil)
if err != nil {
return nil, nil, nil, nil, nil, err
}
@@ -763,7 +763,7 @@ func (s *Server) getProjectsAndApps(ctx context.Context, org string) ([]*v1_pb.D
return nil, nil, nil, nil, nil, err
}
queriedProjectRoles, err := s.query.SearchProjectRoles(ctx, false, &query.ProjectRoleSearchQueries{Queries: []query.SearchQuery{projectRoleSearch}})
queriedProjectRoles, err := s.query.SearchProjectRoles(ctx, false, &query.ProjectRoleSearchQueries{Queries: []query.SearchQuery{projectRoleSearch}}, nil)
if err != nil {
return nil, nil, nil, nil, nil, err
}
@@ -945,7 +945,7 @@ func (s *Server) getNecessaryProjectGrantsForOrg(ctx context.Context, org string
if err != nil {
return nil, err
}
queriedProjectGrants, err := s.query.SearchProjectGrants(ctx, &query.ProjectGrantSearchQueries{Queries: []query.SearchQuery{projectGrantSearchOrg}})
queriedProjectGrants, err := s.query.SearchProjectGrants(ctx, &query.ProjectGrantSearchQueries{Queries: []query.SearchQuery{projectGrantSearchOrg}}, nil)
if err != nil {
return nil, err
}
+3 -3
View File
@@ -621,7 +621,7 @@ func importProjects(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDa
}
for _, project := range org.GetProjects() {
logging.Debugf("import project: %s", project.GetProjectId())
_, err := s.command.AddProjectWithID(ctx, management.ProjectCreateToDomain(project.GetProject()), org.GetOrgId(), project.GetProjectId())
_, err := s.command.AddProject(ctx, management.ProjectCreateToCommand(project.GetProject(), project.GetProjectId(), org.GetOrgId()))
if err != nil {
*errors = append(*errors, &admin_pb.ImportDataError{Type: "project", Id: project.GetProjectId(), Message: err.Error()})
if isCtxTimeout(ctx) {
@@ -761,7 +761,7 @@ func importProjectRoles(ctx context.Context, s *Server, errors *[]*admin_pb.Impo
logging.Debugf("import projectroles: %s", role.ProjectId+"_"+role.RoleKey)
// TBD: why not command.BulkAddProjectRole?
_, err := s.command.AddProjectRole(ctx, management.AddProjectRoleRequestToDomain(role), org.GetOrgId())
_, err := s.command.AddProjectRole(ctx, management.AddProjectRoleRequestToCommand(role, org.GetOrgId()))
if err != nil {
*errors = append(*errors, &admin_pb.ImportDataError{Type: "project_role", Id: role.ProjectId + "_" + role.RoleKey, Message: err.Error()})
if isCtxTimeout(ctx) {
@@ -1023,7 +1023,7 @@ func importOrg2(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataEr
if org.ProjectGrants != nil {
for _, grant := range org.GetProjectGrants() {
logging.Debugf("import projectgrant: %s", grant.GetGrantId()+"_"+grant.GetProjectGrant().GetProjectId()+"_"+grant.GetProjectGrant().GetGrantedOrgId())
_, err := s.command.AddProjectGrantWithID(ctx, management.AddProjectGrantRequestToDomain(grant.GetProjectGrant()), grant.GetGrantId(), org.GetOrgId())
_, err := s.command.AddProjectGrant(ctx, management.AddProjectGrantRequestToCommand(grant.GetProjectGrant(), grant.GetGrantId(), org.GetOrgId()))
if err != nil {
*errors = append(*errors, &admin_pb.ImportDataError{Type: "project_grant", Id: org.GetOrgId() + "_" + grant.GetProjectGrant().GetProjectId() + "_" + grant.GetProjectGrant().GetGrantedOrgId(), Message: err.Error()})
if isCtxTimeout(ctx) {
@@ -259,6 +259,12 @@ func TestServer_ImportData(t *testing.T) {
Data: &admin.ImportDataRequest_DataOrgs{
DataOrgs: &admin.ImportDataOrg{
Orgs: []*admin.DataOrg{
{
OrgId: orgIDs[4],
Org: &management.AddOrgRequest{
Name: gofakeit.ProductName(),
},
},
{
OrgId: orgIDs[3],
Org: &management.AddOrgRequest{
@@ -336,6 +342,9 @@ func TestServer_ImportData(t *testing.T) {
},
Success: &admin.ImportDataSuccess{
Orgs: []*admin.ImportDataSuccessOrg{
{
OrgId: orgIDs[4],
},
{
OrgId: orgIDs[3],
ProjectIds: projectIDs[2:4],
@@ -363,6 +372,12 @@ func TestServer_ImportData(t *testing.T) {
Data: &admin.ImportDataRequest_DataOrgs{
DataOrgs: &admin.ImportDataOrg{
Orgs: []*admin.DataOrg{
{
OrgId: orgIDs[6],
Org: &management.AddOrgRequest{
Name: gofakeit.ProductName(),
},
},
{
OrgId: orgIDs[5],
Org: &management.AddOrgRequest{
@@ -383,6 +398,11 @@ func TestServer_ImportData(t *testing.T) {
RoleKey: "role1",
DisplayName: "role1",
},
{
ProjectId: projectIDs[4],
RoleKey: "role2",
DisplayName: "role2",
},
},
HumanUsers: []*v1.DataHumanUser{
{
@@ -442,11 +462,15 @@ func TestServer_ImportData(t *testing.T) {
},
Success: &admin.ImportDataSuccess{
Orgs: []*admin.ImportDataSuccessOrg{
{
OrgId: orgIDs[6],
},
{
OrgId: orgIDs[5],
ProjectIds: projectIDs[4:5],
ProjectRoles: []string{
projectIDs[4] + "_role1",
projectIDs[4] + "_role2",
},
HumanUserIds: userIDs[2:3],
ProjectGrants: []*admin.ImportDataSuccessProjectGrant{
+14 -13
View File
@@ -47,7 +47,7 @@ func (s *Server) ListProjects(ctx context.Context, req *mgmt_pb.ListProjectsRequ
if err != nil {
return nil, err
}
projects, err := s.query.SearchProjects(ctx, queries)
projects, err := s.query.SearchProjects(ctx, queries, nil)
if err != nil {
return nil, err
}
@@ -109,7 +109,7 @@ func (s *Server) ListGrantedProjects(ctx context.Context, req *mgmt_pb.ListGrant
if err != nil {
return nil, err
}
projects, err := s.query.SearchProjectGrants(ctx, queries)
projects, err := s.query.SearchProjectGrants(ctx, queries, nil)
if err != nil {
return nil, err
}
@@ -175,25 +175,26 @@ func (s *Server) ListProjectChanges(ctx context.Context, req *mgmt_pb.ListProjec
}
func (s *Server) AddProject(ctx context.Context, req *mgmt_pb.AddProjectRequest) (*mgmt_pb.AddProjectResponse, error) {
project, err := s.command.AddProject(ctx, ProjectCreateToDomain(req), authz.GetCtxData(ctx).OrgID)
add := ProjectCreateToCommand(req, "", authz.GetCtxData(ctx).OrgID)
project, err := s.command.AddProject(ctx, add)
if err != nil {
return nil, err
}
return &mgmt_pb.AddProjectResponse{
Id: project.AggregateID,
Details: object_grpc.AddToDetailsPb(project.Sequence, project.ChangeDate, project.ResourceOwner),
Id: add.AggregateID,
Details: object_grpc.AddToDetailsPb(project.Sequence, project.EventDate, project.ResourceOwner),
}, nil
}
func (s *Server) UpdateProject(ctx context.Context, req *mgmt_pb.UpdateProjectRequest) (*mgmt_pb.UpdateProjectResponse, error) {
project, err := s.command.ChangeProject(ctx, ProjectUpdateToDomain(req), authz.GetCtxData(ctx).OrgID)
project, err := s.command.ChangeProject(ctx, ProjectUpdateToCommand(req, authz.GetCtxData(ctx).OrgID))
if err != nil {
return nil, err
}
return &mgmt_pb.UpdateProjectResponse{
Details: object_grpc.ChangeToDetailsPb(
project.Sequence,
project.ChangeDate,
project.EventDate,
project.ResourceOwner,
),
}, nil
@@ -252,7 +253,7 @@ func (s *Server) ListProjectRoles(ctx context.Context, req *mgmt_pb.ListProjectR
if err != nil {
return nil, err
}
roles, err := s.query.SearchProjectRoles(ctx, true, queries)
roles, err := s.query.SearchProjectRoles(ctx, true, queries, nil)
if err != nil {
return nil, err
}
@@ -263,21 +264,21 @@ func (s *Server) ListProjectRoles(ctx context.Context, req *mgmt_pb.ListProjectR
}
func (s *Server) AddProjectRole(ctx context.Context, req *mgmt_pb.AddProjectRoleRequest) (*mgmt_pb.AddProjectRoleResponse, error) {
role, err := s.command.AddProjectRole(ctx, AddProjectRoleRequestToDomain(req), authz.GetCtxData(ctx).OrgID)
role, err := s.command.AddProjectRole(ctx, AddProjectRoleRequestToCommand(req, authz.GetCtxData(ctx).OrgID))
if err != nil {
return nil, err
}
return &mgmt_pb.AddProjectRoleResponse{
Details: object_grpc.AddToDetailsPb(
role.Sequence,
role.ChangeDate,
role.EventDate,
role.ResourceOwner,
),
}, nil
}
func (s *Server) BulkAddProjectRoles(ctx context.Context, req *mgmt_pb.BulkAddProjectRolesRequest) (*mgmt_pb.BulkAddProjectRolesResponse, error) {
details, err := s.command.BulkAddProjectRole(ctx, req.ProjectId, authz.GetCtxData(ctx).OrgID, BulkAddProjectRolesRequestToDomain(req))
details, err := s.command.BulkAddProjectRole(ctx, req.ProjectId, authz.GetCtxData(ctx).OrgID, BulkAddProjectRolesRequestToCommand(req, authz.GetCtxData(ctx).OrgID))
if err != nil {
return nil, err
}
@@ -287,14 +288,14 @@ func (s *Server) BulkAddProjectRoles(ctx context.Context, req *mgmt_pb.BulkAddPr
}
func (s *Server) UpdateProjectRole(ctx context.Context, req *mgmt_pb.UpdateProjectRoleRequest) (*mgmt_pb.UpdateProjectRoleResponse, error) {
role, err := s.command.ChangeProjectRole(ctx, UpdateProjectRoleRequestToDomain(req), authz.GetCtxData(ctx).OrgID)
role, err := s.command.ChangeProjectRole(ctx, UpdateProjectRoleRequestToCommand(req, authz.GetCtxData(ctx).OrgID))
if err != nil {
return nil, err
}
return &mgmt_pb.UpdateProjectRoleResponse{
Details: object_grpc.ChangeToDetailsPb(
role.Sequence,
role.ChangeDate,
role.EventDate,
role.ResourceOwner,
),
}, nil
@@ -3,10 +3,13 @@ package management
import (
"context"
"github.com/muhlemmer/gu"
"github.com/zitadel/zitadel/internal/api/authz"
member_grpc "github.com/zitadel/zitadel/internal/api/grpc/member"
"github.com/zitadel/zitadel/internal/api/grpc/object"
proj_grpc "github.com/zitadel/zitadel/internal/api/grpc/project"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/query"
@@ -14,8 +17,12 @@ import (
proj_pb "github.com/zitadel/zitadel/pkg/grpc/project"
)
func ProjectCreateToDomain(req *mgmt_pb.AddProjectRequest) *domain.Project {
return &domain.Project{
func ProjectCreateToCommand(req *mgmt_pb.AddProjectRequest, projectID string, resourceOwner string) *command.AddProject {
return &command.AddProject{
ObjectRoot: models.ObjectRoot{
AggregateID: projectID,
ResourceOwner: resourceOwner,
},
Name: req.Name,
ProjectRoleAssertion: req.ProjectRoleAssertion,
ProjectRoleCheck: req.ProjectRoleCheck,
@@ -24,16 +31,17 @@ func ProjectCreateToDomain(req *mgmt_pb.AddProjectRequest) *domain.Project {
}
}
func ProjectUpdateToDomain(req *mgmt_pb.UpdateProjectRequest) *domain.Project {
return &domain.Project{
func ProjectUpdateToCommand(req *mgmt_pb.UpdateProjectRequest, resourceOwner string) *command.ChangeProject {
return &command.ChangeProject{
ObjectRoot: models.ObjectRoot{
AggregateID: req.Id,
AggregateID: req.Id,
ResourceOwner: resourceOwner,
},
Name: req.Name,
ProjectRoleAssertion: req.ProjectRoleAssertion,
ProjectRoleCheck: req.ProjectRoleCheck,
HasProjectCheck: req.HasProjectCheck,
PrivateLabelingSetting: privateLabelingSettingToDomain(req.PrivateLabelingSetting),
Name: gu.Ptr(req.Name),
ProjectRoleAssertion: gu.Ptr(req.ProjectRoleAssertion),
ProjectRoleCheck: gu.Ptr(req.ProjectRoleCheck),
HasProjectCheck: gu.Ptr(req.HasProjectCheck),
PrivateLabelingSetting: gu.Ptr(privateLabelingSettingToDomain(req.PrivateLabelingSetting)),
}
}
@@ -48,10 +56,11 @@ func privateLabelingSettingToDomain(setting proj_pb.PrivateLabelingSetting) doma
}
}
func AddProjectRoleRequestToDomain(req *mgmt_pb.AddProjectRoleRequest) *domain.ProjectRole {
return &domain.ProjectRole{
func AddProjectRoleRequestToCommand(req *mgmt_pb.AddProjectRoleRequest, resourceOwner string) *command.AddProjectRole {
return &command.AddProjectRole{
ObjectRoot: models.ObjectRoot{
AggregateID: req.ProjectId,
AggregateID: req.ProjectId,
ResourceOwner: resourceOwner,
},
Key: req.RoleKey,
DisplayName: req.DisplayName,
@@ -59,12 +68,13 @@ func AddProjectRoleRequestToDomain(req *mgmt_pb.AddProjectRoleRequest) *domain.P
}
}
func BulkAddProjectRolesRequestToDomain(req *mgmt_pb.BulkAddProjectRolesRequest) []*domain.ProjectRole {
roles := make([]*domain.ProjectRole, len(req.Roles))
func BulkAddProjectRolesRequestToCommand(req *mgmt_pb.BulkAddProjectRolesRequest, resourceOwner string) []*command.AddProjectRole {
roles := make([]*command.AddProjectRole, len(req.Roles))
for i, role := range req.Roles {
roles[i] = &domain.ProjectRole{
roles[i] = &command.AddProjectRole{
ObjectRoot: models.ObjectRoot{
AggregateID: req.ProjectId,
AggregateID: req.ProjectId,
ResourceOwner: resourceOwner,
},
Key: role.Key,
DisplayName: role.DisplayName,
@@ -74,10 +84,11 @@ func BulkAddProjectRolesRequestToDomain(req *mgmt_pb.BulkAddProjectRolesRequest)
return roles
}
func UpdateProjectRoleRequestToDomain(req *mgmt_pb.UpdateProjectRoleRequest) *domain.ProjectRole {
return &domain.ProjectRole{
func UpdateProjectRoleRequestToCommand(req *mgmt_pb.UpdateProjectRoleRequest, resourceOwner string) *command.ChangeProjectRole {
return &command.ChangeProjectRole{
ObjectRoot: models.ObjectRoot{
AggregateID: req.ProjectId,
AggregateID: req.ProjectId,
ResourceOwner: resourceOwner,
},
Key: req.RoleKey,
DisplayName: req.DisplayName,
@@ -31,7 +31,7 @@ func (s *Server) ListProjectGrants(ctx context.Context, req *mgmt_pb.ListProject
if err != nil {
return nil, err
}
grants, err := s.query.SearchProjectGrants(ctx, queries)
grants, err := s.query.SearchProjectGrants(ctx, queries, nil)
if err != nil {
return nil, err
}
@@ -54,7 +54,7 @@ func (s *Server) ListAllProjectGrants(ctx context.Context, req *mgmt_pb.ListAllP
if err != nil {
return nil, err
}
grants, err := s.query.SearchProjectGrants(ctx, queries)
grants, err := s.query.SearchProjectGrants(ctx, queries, nil)
if err != nil {
return nil, err
}
@@ -65,16 +65,17 @@ func (s *Server) ListAllProjectGrants(ctx context.Context, req *mgmt_pb.ListAllP
}
func (s *Server) AddProjectGrant(ctx context.Context, req *mgmt_pb.AddProjectGrantRequest) (*mgmt_pb.AddProjectGrantResponse, error) {
grant, err := s.command.AddProjectGrant(ctx, AddProjectGrantRequestToDomain(req), authz.GetCtxData(ctx).OrgID)
grant := AddProjectGrantRequestToCommand(req, "", authz.GetCtxData(ctx).OrgID)
details, err := s.command.AddProjectGrant(ctx, grant)
if err != nil {
return nil, err
}
return &mgmt_pb.AddProjectGrantResponse{
GrantId: grant.GrantID,
Details: object_grpc.AddToDetailsPb(
grant.Sequence,
grant.ChangeDate,
grant.ResourceOwner,
details.Sequence,
details.EventDate,
details.ResourceOwner,
),
}, nil
}
@@ -94,14 +95,14 @@ func (s *Server) UpdateProjectGrant(ctx context.Context, req *mgmt_pb.UpdateProj
if err != nil {
return nil, err
}
grant, err := s.command.ChangeProjectGrant(ctx, UpdateProjectGrantRequestToDomain(req), authz.GetCtxData(ctx).OrgID, userGrantsToIDs(grants.UserGrants)...)
grant, err := s.command.ChangeProjectGrant(ctx, UpdateProjectGrantRequestToCommand(req, authz.GetCtxData(ctx).OrgID), userGrantsToIDs(grants.UserGrants)...)
if err != nil {
return nil, err
}
return &mgmt_pb.UpdateProjectGrantResponse{
Details: object_grpc.ChangeToDetailsPb(
grant.Sequence,
grant.ChangeDate,
grant.EventDate,
grant.ResourceOwner,
),
}, nil
@@ -6,6 +6,7 @@ import (
"github.com/zitadel/zitadel/internal/api/authz"
member_grpc "github.com/zitadel/zitadel/internal/api/grpc/member"
"github.com/zitadel/zitadel/internal/api/grpc/object"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/query"
@@ -100,20 +101,23 @@ func AllProjectGrantQueryToModel(apiQuery *proj_pb.AllProjectGrantQuery) (query.
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-M099f", "List.Query.Invalid")
}
}
func AddProjectGrantRequestToDomain(req *mgmt_pb.AddProjectGrantRequest) *domain.ProjectGrant {
return &domain.ProjectGrant{
func AddProjectGrantRequestToCommand(req *mgmt_pb.AddProjectGrantRequest, grantID string, resourceOwner string) *command.AddProjectGrant {
return &command.AddProjectGrant{
ObjectRoot: models.ObjectRoot{
AggregateID: req.ProjectId,
AggregateID: req.ProjectId,
ResourceOwner: resourceOwner,
},
GrantID: grantID,
GrantedOrgID: req.GrantedOrgId,
RoleKeys: req.RoleKeys,
}
}
func UpdateProjectGrantRequestToDomain(req *mgmt_pb.UpdateProjectGrantRequest) *domain.ProjectGrant {
return &domain.ProjectGrant{
func UpdateProjectGrantRequestToCommand(req *mgmt_pb.UpdateProjectGrantRequest, resourceOwner string) *command.ChangeProjectGrant {
return &command.ChangeProjectGrant{
ObjectRoot: models.ObjectRoot{
AggregateID: req.ProjectId,
AggregateID: req.ProjectId,
ResourceOwner: resourceOwner,
},
GrantID: req.GrantId,
RoleKeys: req.RoleKeys,
@@ -24,8 +24,7 @@ import (
)
func TestServer_GetAuthRequest(t *testing.T) {
project, err := Instance.CreateProject(CTX)
require.NoError(t, err)
project := Instance.CreateProject(CTX, t, "", gofakeit.AppName(), false, false)
client, err := Instance.CreateOIDCNativeClient(CTX, redirectURI, logoutRedirectURI, project.GetId(), false)
require.NoError(t, err)
@@ -98,8 +97,7 @@ func TestServer_GetAuthRequest(t *testing.T) {
}
func TestServer_CreateCallback(t *testing.T) {
project, err := Instance.CreateProject(CTX)
require.NoError(t, err)
project := Instance.CreateProject(CTX, t, "", gofakeit.AppName(), false, false)
client, err := Instance.CreateOIDCNativeClient(CTX, redirectURI, logoutRedirectURI, project.GetId(), false)
require.NoError(t, err)
clientV2, err := Instance.CreateOIDCClientLoginVersion(CTX, redirectURI, logoutRedirectURI, project.GetId(), app.OIDCAppType_OIDC_APP_TYPE_NATIVE, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE, false, loginV2)
@@ -288,7 +286,7 @@ func TestServer_CreateCallback(t *testing.T) {
ctx: CTX,
req: &oidc_pb.CreateCallbackRequest{
AuthRequestId: func() string {
client, err := Instance.CreateOIDCImplicitFlowClient(CTX, redirectURIImplicit, nil)
client, err := Instance.CreateOIDCImplicitFlowClient(CTX, t, redirectURIImplicit, nil)
require.NoError(t, err)
authRequestID, err := Instance.CreateOIDCAuthRequestImplicit(CTX, client.GetClientId(), Instance.Users.Get(integration.UserTypeOrgOwner).ID, redirectURIImplicit)
require.NoError(t, err)
@@ -315,7 +313,7 @@ func TestServer_CreateCallback(t *testing.T) {
ctx: CTXLoginClient,
req: &oidc_pb.CreateCallbackRequest{
AuthRequestId: func() string {
clientV2, err := Instance.CreateOIDCImplicitFlowClient(CTX, redirectURIImplicit, loginV2)
clientV2, err := Instance.CreateOIDCImplicitFlowClient(CTX, t, redirectURIImplicit, loginV2)
require.NoError(t, err)
authRequestID, err := Instance.CreateOIDCAuthRequestImplicitWithoutLoginClientHeader(CTX, clientV2.GetClientId(), redirectURIImplicit)
require.NoError(t, err)
@@ -371,7 +369,7 @@ func TestServer_CreateCallback_Permission(t *testing.T) {
projectID2, _ := createOIDCApplication(ctx, t, true, true)
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
Instance.CreateProjectGrant(ctx, projectID2, orgResp.GetOrganizationId())
Instance.CreateProjectGrant(ctx, t, projectID2, orgResp.GetOrganizationId())
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
Instance.CreateProjectUserGrant(t, ctx, projectID, user.GetUserId())
@@ -386,7 +384,7 @@ func TestServer_CreateCallback_Permission(t *testing.T) {
projectID, clientID := createOIDCApplication(ctx, t, true, true)
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
Instance.CreateProjectGrant(ctx, t, projectID, orgResp.GetOrganizationId())
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
Instance.CreateProjectUserGrant(t, ctx, projectID, user.GetUserId())
@@ -407,9 +405,9 @@ func TestServer_CreateCallback_Permission(t *testing.T) {
projectID, clientID := createOIDCApplication(ctx, t, true, true)
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
projectGrantResp := Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
Instance.CreateProjectGrant(ctx, t, projectID, orgResp.GetOrganizationId())
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
Instance.CreateProjectGrantUserGrant(ctx, orgResp.GetOrganizationId(), projectID, projectGrantResp.GetGrantId(), user.GetUserId())
Instance.CreateProjectGrantUserGrant(ctx, orgResp.GetOrganizationId(), projectID, orgResp.GetOrganizationId(), user.GetUserId())
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
},
@@ -544,9 +542,9 @@ func TestServer_CreateCallback_Permission(t *testing.T) {
projectID, clientID := createOIDCApplication(ctx, t, true, false)
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
projectGrantResp := Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
Instance.CreateProjectGrant(ctx, t, projectID, orgResp.GetOrganizationId())
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
Instance.CreateProjectGrantUserGrant(ctx, orgResp.GetOrganizationId(), projectID, projectGrantResp.GetGrantId(), user.GetUserId())
Instance.CreateProjectGrantUserGrant(ctx, orgResp.GetOrganizationId(), projectID, orgResp.GetOrganizationId(), user.GetUserId())
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
},
want: &oidc_pb.CreateCallbackResponse{
@@ -564,7 +562,7 @@ func TestServer_CreateCallback_Permission(t *testing.T) {
projectID, clientID := createOIDCApplication(ctx, t, true, false)
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
Instance.CreateProjectGrant(ctx, t, projectID, orgResp.GetOrganizationId())
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
},
@@ -606,7 +604,7 @@ func TestServer_CreateCallback_Permission(t *testing.T) {
projectID, clientID := createOIDCApplication(ctx, t, false, true)
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
Instance.CreateProjectGrant(ctx, t, projectID, orgResp.GetOrganizationId())
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
@@ -639,8 +637,7 @@ func TestServer_CreateCallback_Permission(t *testing.T) {
}
func TestServer_GetDeviceAuthorizationRequest(t *testing.T) {
project, err := Instance.CreateProject(CTX)
require.NoError(t, err)
project := Instance.CreateProject(CTX, t, "", gofakeit.AppName(), false, false)
client, err := Instance.CreateOIDCClient(CTX, redirectURI, logoutRedirectURI, project.GetId(), app.OIDCAppType_OIDC_APP_TYPE_NATIVE, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE, false, app.OIDCGrantType_OIDC_GRANT_TYPE_DEVICE_CODE)
require.NoError(t, err)
@@ -697,8 +694,7 @@ func TestServer_GetDeviceAuthorizationRequest(t *testing.T) {
}
func TestServer_AuthorizeOrDenyDeviceAuthorization(t *testing.T) {
project, err := Instance.CreateProject(CTX)
require.NoError(t, err)
project := Instance.CreateProject(CTX, t, "", gofakeit.AppName(), false, false)
client, err := Instance.CreateOIDCClient(CTX, redirectURI, logoutRedirectURI, project.GetId(), app.OIDCAppType_OIDC_APP_TYPE_NATIVE, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE, false, app.OIDCGrantType_OIDC_GRANT_TYPE_DEVICE_CODE)
require.NoError(t, err)
sessionResp := createSession(t, CTX, Instance.Users[integration.UserTypeOrgOwner].ID)
@@ -895,8 +891,7 @@ func createSessionAndAuthRequestForCallback(ctx context.Context, t *testing.T, c
}
func createOIDCApplication(ctx context.Context, t *testing.T, projectRoleCheck, hasProjectCheck bool) (string, string) {
project, err := Instance.CreateProjectWithPermissionCheck(ctx, projectRoleCheck, hasProjectCheck)
require.NoError(t, err)
project := Instance.CreateProject(ctx, t, "", gofakeit.AppName(), projectRoleCheck, hasProjectCheck)
clientV2, err := Instance.CreateOIDCClientLoginVersion(ctx, redirectURI, logoutRedirectURI, project.GetId(), app.OIDCAppType_OIDC_APP_TYPE_NATIVE, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE, false, loginV2)
require.NoError(t, err)
return project.GetId(), clientV2.GetClientId()
@@ -23,8 +23,7 @@ import (
)
func TestServer_GetAuthRequest(t *testing.T) {
project, err := Instance.CreateProject(CTX)
require.NoError(t, err)
project := Instance.CreateProject(CTX, t, "", gofakeit.AppName(), false, false)
client, err := Instance.CreateOIDCNativeClient(CTX, redirectURI, logoutRedirectURI, project.GetId(), false)
require.NoError(t, err)
@@ -97,8 +96,7 @@ func TestServer_GetAuthRequest(t *testing.T) {
}
func TestServer_CreateCallback(t *testing.T) {
project, err := Instance.CreateProject(CTX)
require.NoError(t, err)
project := Instance.CreateProject(CTX, t, "", gofakeit.AppName(), false, false)
client, err := Instance.CreateOIDCNativeClient(CTX, redirectURI, logoutRedirectURI, project.GetId(), false)
require.NoError(t, err)
clientV2, err := Instance.CreateOIDCClientLoginVersion(CTX, redirectURI, logoutRedirectURI, project.GetId(), app.OIDCAppType_OIDC_APP_TYPE_NATIVE, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE, false, loginV2)
@@ -289,7 +287,7 @@ func TestServer_CreateCallback(t *testing.T) {
ctx: CTX,
req: &oidc_pb.CreateCallbackRequest{
AuthRequestId: func() string {
client, err := Instance.CreateOIDCImplicitFlowClient(CTX, redirectURIImplicit, nil)
client, err := Instance.CreateOIDCImplicitFlowClient(CTX, t, redirectURIImplicit, nil)
require.NoError(t, err)
authRequestID, err := Instance.CreateOIDCAuthRequestImplicit(CTX, client.GetClientId(), Instance.Users.Get(integration.UserTypeOrgOwner).ID, redirectURIImplicit)
require.NoError(t, err)
@@ -316,7 +314,7 @@ func TestServer_CreateCallback(t *testing.T) {
ctx: CTXLoginClient,
req: &oidc_pb.CreateCallbackRequest{
AuthRequestId: func() string {
clientV2, err := Instance.CreateOIDCImplicitFlowClient(CTX, redirectURIImplicit, loginV2)
clientV2, err := Instance.CreateOIDCImplicitFlowClient(CTX, t, redirectURIImplicit, loginV2)
require.NoError(t, err)
authRequestID, err := Instance.CreateOIDCAuthRequestImplicitWithoutLoginClientHeader(CTX, clientV2.GetClientId(), redirectURIImplicit)
require.NoError(t, err)
@@ -372,7 +370,7 @@ func TestServer_CreateCallback_Permission(t *testing.T) {
projectID2, _ := createOIDCApplication(ctx, t, true, true)
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
Instance.CreateProjectGrant(ctx, projectID2, orgResp.GetOrganizationId())
Instance.CreateProjectGrant(ctx, t, projectID2, orgResp.GetOrganizationId())
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
Instance.CreateProjectUserGrant(t, ctx, projectID, user.GetUserId())
@@ -387,7 +385,7 @@ func TestServer_CreateCallback_Permission(t *testing.T) {
projectID, clientID := createOIDCApplication(ctx, t, true, true)
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
Instance.CreateProjectGrant(ctx, t, projectID, orgResp.GetOrganizationId())
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
Instance.CreateProjectUserGrant(t, ctx, projectID, user.GetUserId())
@@ -408,9 +406,9 @@ func TestServer_CreateCallback_Permission(t *testing.T) {
projectID, clientID := createOIDCApplication(ctx, t, true, true)
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
projectGrantResp := Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
Instance.CreateProjectGrant(ctx, t, projectID, orgResp.GetOrganizationId())
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
Instance.CreateProjectGrantUserGrant(ctx, orgResp.GetOrganizationId(), projectID, projectGrantResp.GetGrantId(), user.GetUserId())
Instance.CreateProjectGrantUserGrant(ctx, orgResp.GetOrganizationId(), projectID, orgResp.GetOrganizationId(), user.GetUserId())
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
},
@@ -545,9 +543,9 @@ func TestServer_CreateCallback_Permission(t *testing.T) {
projectID, clientID := createOIDCApplication(ctx, t, true, false)
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
projectGrantResp := Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
Instance.CreateProjectGrant(ctx, t, projectID, orgResp.GetOrganizationId())
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
Instance.CreateProjectGrantUserGrant(ctx, orgResp.GetOrganizationId(), projectID, projectGrantResp.GetGrantId(), user.GetUserId())
Instance.CreateProjectGrantUserGrant(ctx, orgResp.GetOrganizationId(), projectID, orgResp.GetOrganizationId(), user.GetUserId())
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
},
want: &oidc_pb.CreateCallbackResponse{
@@ -565,7 +563,7 @@ func TestServer_CreateCallback_Permission(t *testing.T) {
projectID, clientID := createOIDCApplication(ctx, t, true, false)
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
Instance.CreateProjectGrant(ctx, t, projectID, orgResp.GetOrganizationId())
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
},
@@ -607,7 +605,7 @@ func TestServer_CreateCallback_Permission(t *testing.T) {
projectID, clientID := createOIDCApplication(ctx, t, false, true)
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
Instance.CreateProjectGrant(ctx, t, projectID, orgResp.GetOrganizationId())
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
@@ -669,8 +667,7 @@ func createSessionAndAuthRequestForCallback(ctx context.Context, t *testing.T, c
}
func createOIDCApplication(ctx context.Context, t *testing.T, projectRoleCheck, hasProjectCheck bool) (string, string) {
project, err := Instance.CreateProjectWithPermissionCheck(ctx, projectRoleCheck, hasProjectCheck)
require.NoError(t, err)
project := Instance.CreateProject(ctx, t, "", gofakeit.AppName(), projectRoleCheck, hasProjectCheck)
clientV2, err := Instance.CreateOIDCClientLoginVersion(ctx, redirectURI, logoutRedirectURI, project.GetId(), app.OIDCAppType_OIDC_APP_TYPE_NATIVE, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE, false, loginV2)
require.NoError(t, err)
return project.GetId(), clientV2.GetClientId()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,698 @@
//go:build integration
package project_test
import (
"context"
"testing"
"time"
"github.com/brianvoe/gofakeit/v6"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/zitadel/zitadel/internal/integration"
project "github.com/zitadel/zitadel/pkg/grpc/project/v2beta"
)
func TestServer_AddProjectRole(t *testing.T) {
iamOwnerCtx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
orgResp := instance.CreateOrganization(iamOwnerCtx, gofakeit.AppName(), gofakeit.Email())
alreadyExistingProject := instance.CreateProject(iamOwnerCtx, t, orgResp.GetOrganizationId(), gofakeit.AppName(), false, false)
alreadyExistingProjectRoleName := gofakeit.AppName()
instance.AddProjectRole(iamOwnerCtx, t, alreadyExistingProject.GetId(), alreadyExistingProjectRoleName, alreadyExistingProjectRoleName, "")
type want struct {
creationDate bool
}
tests := []struct {
name string
ctx context.Context
prepare func(request *project.AddProjectRoleRequest)
req *project.AddProjectRoleRequest
want
wantErr bool
}{
{
name: "empty key",
ctx: iamOwnerCtx,
prepare: func(request *project.AddProjectRoleRequest) {
projectResp := instance.CreateProject(iamOwnerCtx, t, orgResp.GetOrganizationId(), gofakeit.AppName(), false, false)
request.ProjectId = projectResp.GetId()
},
req: &project.AddProjectRoleRequest{
RoleKey: "",
DisplayName: gofakeit.AppName(),
},
wantErr: true,
},
{
name: "empty displayname",
ctx: iamOwnerCtx,
prepare: func(request *project.AddProjectRoleRequest) {
projectResp := instance.CreateProject(iamOwnerCtx, t, orgResp.GetOrganizationId(), gofakeit.AppName(), false, false)
request.ProjectId = projectResp.GetId()
},
req: &project.AddProjectRoleRequest{
RoleKey: gofakeit.AppName(),
DisplayName: "",
},
wantErr: true,
},
{
name: "already existing, error",
ctx: iamOwnerCtx,
prepare: func(request *project.AddProjectRoleRequest) {
request.ProjectId = alreadyExistingProject.GetId()
},
req: &project.AddProjectRoleRequest{
RoleKey: alreadyExistingProjectRoleName,
DisplayName: alreadyExistingProjectRoleName,
},
wantErr: true,
},
{
name: "empty, ok",
ctx: iamOwnerCtx,
prepare: func(request *project.AddProjectRoleRequest) {
projectResp := instance.CreateProject(iamOwnerCtx, t, orgResp.GetOrganizationId(), gofakeit.AppName(), false, false)
request.ProjectId = projectResp.GetId()
},
req: &project.AddProjectRoleRequest{
RoleKey: gofakeit.Name(),
DisplayName: gofakeit.Name(),
},
want: want{
creationDate: true,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.prepare != nil {
tt.prepare(tt.req)
}
creationDate := time.Now().UTC()
got, err := instance.Client.Projectv2Beta.AddProjectRole(tt.ctx, tt.req)
changeDate := time.Now().UTC()
if tt.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
assertAddProjectRoleResponse(t, creationDate, changeDate, tt.want.creationDate, got)
})
}
}
func TestServer_AddProjectRole_Permission(t *testing.T) {
iamOwnerCtx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
orgResp := instance.CreateOrganization(iamOwnerCtx, gofakeit.AppName(), gofakeit.Email())
alreadyExistingProject := instance.CreateProject(iamOwnerCtx, t, orgResp.GetOrganizationId(), gofakeit.AppName(), false, false)
alreadyExistingProjectRoleName := gofakeit.AppName()
instance.AddProjectRole(iamOwnerCtx, t, alreadyExistingProject.GetId(), alreadyExistingProjectRoleName, alreadyExistingProjectRoleName, "")
type want struct {
creationDate bool
}
tests := []struct {
name string
ctx context.Context
prepare func(request *project.AddProjectRoleRequest)
req *project.AddProjectRoleRequest
want
wantErr bool
}{
{
name: "unauthenticated",
ctx: CTX,
prepare: func(request *project.AddProjectRoleRequest) {
projectResp := instance.CreateProject(iamOwnerCtx, t, orgResp.GetOrganizationId(), gofakeit.AppName(), false, false)
request.ProjectId = projectResp.GetId()
},
req: &project.AddProjectRoleRequest{
RoleKey: gofakeit.AppName(),
DisplayName: gofakeit.AppName(),
},
wantErr: true,
},
{
name: "no permission",
ctx: instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
prepare: func(request *project.AddProjectRoleRequest) {
projectResp := instance.CreateProject(iamOwnerCtx, t, orgResp.GetOrganizationId(), gofakeit.AppName(), false, false)
request.ProjectId = projectResp.GetId()
},
req: &project.AddProjectRoleRequest{
RoleKey: gofakeit.AppName(),
DisplayName: gofakeit.AppName(),
},
wantErr: true,
},
{
name: "organization owner, other org",
ctx: instance.WithAuthorization(CTX, integration.UserTypeOrgOwner),
prepare: func(request *project.AddProjectRoleRequest) {
projectResp := instance.CreateProject(iamOwnerCtx, t, orgResp.GetOrganizationId(), gofakeit.AppName(), false, false)
request.ProjectId = projectResp.GetId()
},
req: &project.AddProjectRoleRequest{
RoleKey: gofakeit.AppName(),
DisplayName: gofakeit.AppName(),
},
wantErr: true,
},
{
name: "organization owner, ok",
ctx: instance.WithAuthorization(CTX, integration.UserTypeOrgOwner),
prepare: func(request *project.AddProjectRoleRequest) {
projectResp := instance.CreateProject(iamOwnerCtx, t, instance.DefaultOrg.GetId(), gofakeit.AppName(), false, false)
request.ProjectId = projectResp.GetId()
},
req: &project.AddProjectRoleRequest{
RoleKey: gofakeit.AppName(),
DisplayName: gofakeit.AppName(),
},
want: want{
creationDate: true,
},
},
{
name: "instance owner, ok",
ctx: iamOwnerCtx,
prepare: func(request *project.AddProjectRoleRequest) {
projectResp := instance.CreateProject(iamOwnerCtx, t, orgResp.GetOrganizationId(), gofakeit.AppName(), false, false)
request.ProjectId = projectResp.GetId()
},
req: &project.AddProjectRoleRequest{
RoleKey: gofakeit.AppName(),
DisplayName: gofakeit.AppName(),
},
want: want{
creationDate: true,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.prepare != nil {
tt.prepare(tt.req)
}
creationDate := time.Now().UTC()
got, err := instance.Client.Projectv2Beta.AddProjectRole(tt.ctx, tt.req)
changeDate := time.Now().UTC()
if tt.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
assertAddProjectRoleResponse(t, creationDate, changeDate, tt.want.creationDate, got)
})
}
}
func assertAddProjectRoleResponse(t *testing.T, creationDate, changeDate time.Time, expectedCreationDate bool, actualResp *project.AddProjectRoleResponse) {
if expectedCreationDate {
if !changeDate.IsZero() {
assert.WithinRange(t, actualResp.GetCreationDate().AsTime(), creationDate, changeDate)
} else {
assert.WithinRange(t, actualResp.GetCreationDate().AsTime(), creationDate, time.Now().UTC())
}
} else {
assert.Nil(t, actualResp.CreationDate)
}
}
func TestServer_UpdateProjectRole(t *testing.T) {
iamOwnerCtx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
orgResp := instance.CreateOrganization(iamOwnerCtx, gofakeit.AppName(), gofakeit.Email())
type args struct {
ctx context.Context
req *project.UpdateProjectRoleRequest
}
type want struct {
change bool
changeDate bool
}
tests := []struct {
name string
prepare func(request *project.UpdateProjectRoleRequest)
args args
want want
wantErr bool
}{
{
name: "missing permission",
prepare: func(request *project.UpdateProjectRoleRequest) {
projectResp := instance.CreateProject(iamOwnerCtx, t, orgResp.GetOrganizationId(), gofakeit.AppName(), false, false)
roleName := gofakeit.AppName()
instance.AddProjectRole(iamOwnerCtx, t, projectResp.GetId(), roleName, roleName, "")
request.ProjectId = projectResp.GetId()
request.RoleKey = roleName
},
args: args{
ctx: instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
req: &project.UpdateProjectRoleRequest{
DisplayName: gu.Ptr("changed"),
},
},
wantErr: true,
},
{
name: "not existing",
prepare: func(request *project.UpdateProjectRoleRequest) {
request.RoleKey = "notexisting"
return
},
args: args{
ctx: iamOwnerCtx,
req: &project.UpdateProjectRoleRequest{
DisplayName: gu.Ptr("changed"),
},
},
wantErr: true,
},
{
name: "no change, ok",
prepare: func(request *project.UpdateProjectRoleRequest) {
projectResp := instance.CreateProject(iamOwnerCtx, t, orgResp.GetOrganizationId(), gofakeit.AppName(), false, false)
roleName := gofakeit.AppName()
instance.AddProjectRole(iamOwnerCtx, t, projectResp.GetId(), roleName, roleName, "")
request.ProjectId = projectResp.GetId()
request.RoleKey = roleName
request.DisplayName = gu.Ptr(roleName)
},
args: args{
ctx: iamOwnerCtx,
req: &project.UpdateProjectRoleRequest{},
},
want: want{
change: false,
changeDate: true,
},
},
{
name: "change display name, ok",
prepare: func(request *project.UpdateProjectRoleRequest) {
projectResp := instance.CreateProject(iamOwnerCtx, t, orgResp.GetOrganizationId(), gofakeit.AppName(), false, false)
roleName := gofakeit.AppName()
instance.AddProjectRole(iamOwnerCtx, t, projectResp.GetId(), roleName, roleName, "")
request.ProjectId = projectResp.GetId()
request.RoleKey = roleName
},
args: args{
ctx: iamOwnerCtx,
req: &project.UpdateProjectRoleRequest{
DisplayName: gu.Ptr(gofakeit.AppName()),
},
},
want: want{
change: true,
changeDate: true,
},
},
{
name: "change full, ok",
prepare: func(request *project.UpdateProjectRoleRequest) {
projectResp := instance.CreateProject(iamOwnerCtx, t, orgResp.GetOrganizationId(), gofakeit.AppName(), false, false)
roleName := gofakeit.AppName()
instance.AddProjectRole(iamOwnerCtx, t, projectResp.GetId(), roleName, roleName, "")
request.ProjectId = projectResp.GetId()
request.RoleKey = roleName
},
args: args{
ctx: iamOwnerCtx,
req: &project.UpdateProjectRoleRequest{
DisplayName: gu.Ptr(gofakeit.AppName()),
Group: gu.Ptr(gofakeit.AppName()),
},
},
want: want{
change: true,
changeDate: true,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
creationDate := time.Now().UTC()
tt.prepare(tt.args.req)
got, err := instance.Client.Projectv2Beta.UpdateProjectRole(tt.args.ctx, tt.args.req)
if tt.wantErr {
assert.Error(t, err)
return
}
changeDate := time.Time{}
if tt.want.change {
changeDate = time.Now().UTC()
}
assert.NoError(t, err)
assertUpdateProjectRoleResponse(t, creationDate, changeDate, tt.want.changeDate, got)
})
}
}
func TestServer_UpdateProjectRole_Permission(t *testing.T) {
iamOwnerCtx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
orgResp := instance.CreateOrganization(iamOwnerCtx, gofakeit.AppName(), gofakeit.Email())
type args struct {
ctx context.Context
req *project.UpdateProjectRoleRequest
}
type want struct {
change bool
changeDate bool
}
tests := []struct {
name string
prepare func(request *project.UpdateProjectRoleRequest)
args args
want want
wantErr bool
}{
{
name: "unauthenicated",
prepare: func(request *project.UpdateProjectRoleRequest) {
projectResp := instance.CreateProject(iamOwnerCtx, t, orgResp.GetOrganizationId(), gofakeit.AppName(), false, false)
roleName := gofakeit.AppName()
instance.AddProjectRole(iamOwnerCtx, t, projectResp.GetId(), roleName, roleName, "")
request.ProjectId = projectResp.GetId()
request.RoleKey = roleName
},
args: args{
ctx: CTX,
req: &project.UpdateProjectRoleRequest{
DisplayName: gu.Ptr("changed"),
},
},
wantErr: true,
},
{
name: "no permission",
prepare: func(request *project.UpdateProjectRoleRequest) {
projectResp := instance.CreateProject(iamOwnerCtx, t, orgResp.GetOrganizationId(), gofakeit.AppName(), false, false)
roleName := gofakeit.AppName()
instance.AddProjectRole(iamOwnerCtx, t, projectResp.GetId(), roleName, roleName, "")
request.ProjectId = projectResp.GetId()
request.RoleKey = roleName
},
args: args{
ctx: instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
req: &project.UpdateProjectRoleRequest{
DisplayName: gu.Ptr("changed"),
},
},
wantErr: true,
},
{
name: "organization owner, other org",
prepare: func(request *project.UpdateProjectRoleRequest) {
projectResp := instance.CreateProject(iamOwnerCtx, t, orgResp.GetOrganizationId(), gofakeit.AppName(), false, false)
roleName := gofakeit.AppName()
instance.AddProjectRole(iamOwnerCtx, t, projectResp.GetId(), roleName, roleName, "")
request.ProjectId = projectResp.GetId()
request.RoleKey = roleName
},
args: args{
ctx: instance.WithAuthorization(CTX, integration.UserTypeOrgOwner),
req: &project.UpdateProjectRoleRequest{
DisplayName: gu.Ptr("changed"),
},
},
wantErr: true,
},
{
name: "organization owner, ok",
prepare: func(request *project.UpdateProjectRoleRequest) {
projectResp := instance.CreateProject(iamOwnerCtx, t, instance.DefaultOrg.GetId(), gofakeit.AppName(), false, false)
roleName := gofakeit.AppName()
instance.AddProjectRole(iamOwnerCtx, t, projectResp.GetId(), roleName, roleName, "")
request.ProjectId = projectResp.GetId()
request.RoleKey = roleName
},
args: args{
ctx: instance.WithAuthorization(CTX, integration.UserTypeOrgOwner),
req: &project.UpdateProjectRoleRequest{
DisplayName: gu.Ptr(gofakeit.AppName()),
},
},
want: want{
change: true,
changeDate: true,
},
},
{
name: "instance owner, ok",
prepare: func(request *project.UpdateProjectRoleRequest) {
projectResp := instance.CreateProject(iamOwnerCtx, t, orgResp.GetOrganizationId(), gofakeit.AppName(), false, false)
roleName := gofakeit.AppName()
instance.AddProjectRole(iamOwnerCtx, t, projectResp.GetId(), roleName, roleName, "")
request.ProjectId = projectResp.GetId()
request.RoleKey = roleName
},
args: args{
ctx: iamOwnerCtx,
req: &project.UpdateProjectRoleRequest{
DisplayName: gu.Ptr(gofakeit.AppName()),
},
},
want: want{
change: true,
changeDate: true,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
creationDate := time.Now().UTC()
tt.prepare(tt.args.req)
got, err := instance.Client.Projectv2Beta.UpdateProjectRole(tt.args.ctx, tt.args.req)
if tt.wantErr {
assert.Error(t, err)
return
}
changeDate := time.Time{}
if tt.want.change {
changeDate = time.Now().UTC()
}
assert.NoError(t, err)
assertUpdateProjectRoleResponse(t, creationDate, changeDate, tt.want.changeDate, got)
})
}
}
func assertUpdateProjectRoleResponse(t *testing.T, creationDate, changeDate time.Time, expectedChangeDate bool, actualResp *project.UpdateProjectRoleResponse) {
if expectedChangeDate {
if !changeDate.IsZero() {
assert.WithinRange(t, actualResp.GetChangeDate().AsTime(), creationDate, changeDate)
} else {
assert.WithinRange(t, actualResp.GetChangeDate().AsTime(), creationDate, time.Now().UTC())
}
} else {
assert.Nil(t, actualResp.ChangeDate)
}
}
func TestServer_DeleteProjectRole(t *testing.T) {
iamOwnerCtx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
orgResp := instance.CreateOrganization(iamOwnerCtx, gofakeit.AppName(), gofakeit.Email())
tests := []struct {
name string
ctx context.Context
prepare func(request *project.RemoveProjectRoleRequest) (time.Time, time.Time)
req *project.RemoveProjectRoleRequest
wantDeletionDate bool
wantErr bool
}{
{
name: "empty id",
ctx: iamOwnerCtx,
req: &project.RemoveProjectRoleRequest{
ProjectId: "",
RoleKey: "notexisting",
},
wantErr: true,
},
{
name: "delete, not existing",
ctx: iamOwnerCtx,
req: &project.RemoveProjectRoleRequest{
ProjectId: "notexisting",
RoleKey: "notexisting",
},
wantDeletionDate: false,
},
{
name: "delete",
ctx: iamOwnerCtx,
prepare: func(request *project.RemoveProjectRoleRequest) (time.Time, time.Time) {
creationDate := time.Now().UTC()
projectResp := instance.CreateProject(iamOwnerCtx, t, orgResp.GetOrganizationId(), gofakeit.AppName(), false, false)
roleName := gofakeit.AppName()
instance.AddProjectRole(iamOwnerCtx, t, projectResp.GetId(), roleName, roleName, "")
request.ProjectId = projectResp.GetId()
request.RoleKey = roleName
return creationDate, time.Time{}
},
req: &project.RemoveProjectRoleRequest{},
wantDeletionDate: true,
},
{
name: "delete, already removed",
ctx: iamOwnerCtx,
prepare: func(request *project.RemoveProjectRoleRequest) (time.Time, time.Time) {
creationDate := time.Now().UTC()
projectResp := instance.CreateProject(iamOwnerCtx, t, orgResp.GetOrganizationId(), gofakeit.AppName(), false, false)
roleName := gofakeit.AppName()
instance.AddProjectRole(iamOwnerCtx, t, projectResp.GetId(), roleName, roleName, "")
request.ProjectId = projectResp.GetId()
request.RoleKey = roleName
instance.RemoveProjectRole(iamOwnerCtx, t, projectResp.GetId(), roleName)
return creationDate, time.Now().UTC()
},
req: &project.RemoveProjectRoleRequest{},
wantDeletionDate: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var creationDate, deletionDate time.Time
if tt.prepare != nil {
creationDate, deletionDate = tt.prepare(tt.req)
}
got, err := instance.Client.Projectv2Beta.RemoveProjectRole(tt.ctx, tt.req)
if tt.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
assertRemoveProjectRoleResponse(t, creationDate, deletionDate, tt.wantDeletionDate, got)
})
}
}
func TestServer_DeleteProjectRole_Permission(t *testing.T) {
iamOwnerCtx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
orgResp := instance.CreateOrganization(iamOwnerCtx, gofakeit.AppName(), gofakeit.Email())
tests := []struct {
name string
ctx context.Context
prepare func(request *project.RemoveProjectRoleRequest) (time.Time, time.Time)
req *project.RemoveProjectRoleRequest
wantDeletionDate bool
wantErr bool
}{
{
name: "unauthenticated",
ctx: CTX,
prepare: func(request *project.RemoveProjectRoleRequest) (time.Time, time.Time) {
creationDate := time.Now().UTC()
projectResp := instance.CreateProject(iamOwnerCtx, t, orgResp.GetOrganizationId(), gofakeit.AppName(), false, false)
roleName := gofakeit.AppName()
instance.AddProjectRole(iamOwnerCtx, t, projectResp.GetId(), roleName, roleName, "")
request.ProjectId = projectResp.GetId()
request.RoleKey = roleName
return creationDate, time.Time{}
},
req: &project.RemoveProjectRoleRequest{},
wantErr: true,
},
{
name: "no permission",
ctx: instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
prepare: func(request *project.RemoveProjectRoleRequest) (time.Time, time.Time) {
creationDate := time.Now().UTC()
projectResp := instance.CreateProject(iamOwnerCtx, t, orgResp.GetOrganizationId(), gofakeit.AppName(), false, false)
roleName := gofakeit.AppName()
instance.AddProjectRole(iamOwnerCtx, t, projectResp.GetId(), roleName, roleName, "")
request.ProjectId = projectResp.GetId()
request.RoleKey = roleName
return creationDate, time.Time{}
},
req: &project.RemoveProjectRoleRequest{},
wantErr: true,
},
{
name: "organization owner, other org",
ctx: instance.WithAuthorization(CTX, integration.UserTypeOrgOwner),
prepare: func(request *project.RemoveProjectRoleRequest) (time.Time, time.Time) {
creationDate := time.Now().UTC()
projectResp := instance.CreateProject(iamOwnerCtx, t, orgResp.GetOrganizationId(), gofakeit.AppName(), false, false)
roleName := gofakeit.AppName()
instance.AddProjectRole(iamOwnerCtx, t, projectResp.GetId(), roleName, roleName, "")
request.ProjectId = projectResp.GetId()
request.RoleKey = roleName
return creationDate, time.Time{}
},
req: &project.RemoveProjectRoleRequest{},
wantErr: true,
},
{
name: "organization owner, ok",
ctx: instance.WithAuthorization(CTX, integration.UserTypeOrgOwner),
prepare: func(request *project.RemoveProjectRoleRequest) (time.Time, time.Time) {
creationDate := time.Now().UTC()
projectResp := instance.CreateProject(iamOwnerCtx, t, instance.DefaultOrg.GetId(), gofakeit.AppName(), false, false)
roleName := gofakeit.AppName()
instance.AddProjectRole(iamOwnerCtx, t, projectResp.GetId(), roleName, roleName, "")
request.ProjectId = projectResp.GetId()
request.RoleKey = roleName
return creationDate, time.Time{}
},
req: &project.RemoveProjectRoleRequest{},
wantDeletionDate: true,
},
{
name: "instance owner, ok",
ctx: iamOwnerCtx,
prepare: func(request *project.RemoveProjectRoleRequest) (time.Time, time.Time) {
creationDate := time.Now().UTC()
projectResp := instance.CreateProject(iamOwnerCtx, t, orgResp.GetOrganizationId(), gofakeit.AppName(), false, false)
roleName := gofakeit.AppName()
instance.AddProjectRole(iamOwnerCtx, t, projectResp.GetId(), roleName, roleName, "")
request.ProjectId = projectResp.GetId()
request.RoleKey = roleName
return creationDate, time.Time{}
},
req: &project.RemoveProjectRoleRequest{},
wantDeletionDate: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var creationDate, deletionDate time.Time
if tt.prepare != nil {
creationDate, deletionDate = tt.prepare(tt.req)
}
got, err := instance.Client.Projectv2Beta.RemoveProjectRole(tt.ctx, tt.req)
if tt.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
assertRemoveProjectRoleResponse(t, creationDate, deletionDate, tt.wantDeletionDate, got)
})
}
}
func assertRemoveProjectRoleResponse(t *testing.T, creationDate, deletionDate time.Time, expectedDeletionDate bool, actualResp *project.RemoveProjectRoleResponse) {
if expectedDeletionDate {
if !deletionDate.IsZero() {
assert.WithinRange(t, actualResp.GetRemovalDate().AsTime(), creationDate, deletionDate)
} else {
assert.WithinRange(t, actualResp.GetRemovalDate().AsTime(), creationDate, time.Now().UTC())
}
} else {
assert.Nil(t, actualResp.RemovalDate)
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,63 @@
//go:build integration
package project_test
import (
"context"
"os"
"testing"
"time"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zitadel/zitadel/internal/integration"
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
)
var (
CTX context.Context
instance *integration.Instance
instancePermissionV2 *integration.Instance
)
func TestMain(m *testing.M) {
os.Exit(func() int {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
defer cancel()
CTX = ctx
instance = integration.NewInstance(ctx)
instancePermissionV2 = integration.NewInstance(CTX)
return m.Run()
}())
}
func ensureFeaturePermissionV2Enabled(t *testing.T, instance *integration.Instance) {
ctx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
f, err := instance.Client.FeatureV2.GetInstanceFeatures(ctx, &feature.GetInstanceFeaturesRequest{
Inheritance: true,
})
require.NoError(t, err)
if f.PermissionCheckV2.GetEnabled() {
return
}
_, err = instance.Client.FeatureV2.SetInstanceFeatures(ctx, &feature.SetInstanceFeaturesRequest{
PermissionCheckV2: gu.Ptr(true),
})
require.NoError(t, err)
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(ctx, 5*time.Minute)
require.EventuallyWithT(t,
func(ttt *assert.CollectT) {
f, err := instance.Client.FeatureV2.GetInstanceFeatures(ctx, &feature.GetInstanceFeaturesRequest{
Inheritance: true,
})
assert.NoError(ttt, err)
if f.PermissionCheckV2.GetEnabled() {
return
}
},
retryDuration,
tick,
"timed out waiting for ensuring instance feature")
}
+161
View File
@@ -0,0 +1,161 @@
package project
import (
"context"
"github.com/muhlemmer/gu"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/query"
project_pb "github.com/zitadel/zitadel/pkg/grpc/project/v2beta"
)
func (s *Server) CreateProject(ctx context.Context, req *project_pb.CreateProjectRequest) (*project_pb.CreateProjectResponse, error) {
add := projectCreateToCommand(req)
project, err := s.command.AddProject(ctx, add)
if err != nil {
return nil, err
}
var creationDate *timestamppb.Timestamp
if !project.EventDate.IsZero() {
creationDate = timestamppb.New(project.EventDate)
}
return &project_pb.CreateProjectResponse{
Id: add.AggregateID,
CreationDate: creationDate,
}, nil
}
func projectCreateToCommand(req *project_pb.CreateProjectRequest) *command.AddProject {
var aggregateID string
if req.Id != nil {
aggregateID = *req.Id
}
return &command.AddProject{
ObjectRoot: models.ObjectRoot{
ResourceOwner: req.OrganizationId,
AggregateID: aggregateID,
},
Name: req.Name,
ProjectRoleAssertion: req.ProjectRoleAssertion,
ProjectRoleCheck: req.AuthorizationRequired,
HasProjectCheck: req.ProjectAccessRequired,
PrivateLabelingSetting: privateLabelingSettingToDomain(req.PrivateLabelingSetting),
}
}
func privateLabelingSettingToDomain(setting project_pb.PrivateLabelingSetting) domain.PrivateLabelingSetting {
switch setting {
case project_pb.PrivateLabelingSetting_PRIVATE_LABELING_SETTING_ALLOW_LOGIN_USER_RESOURCE_OWNER_POLICY:
return domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy
case project_pb.PrivateLabelingSetting_PRIVATE_LABELING_SETTING_ENFORCE_PROJECT_RESOURCE_OWNER_POLICY:
return domain.PrivateLabelingSettingEnforceProjectResourceOwnerPolicy
case project_pb.PrivateLabelingSetting_PRIVATE_LABELING_SETTING_UNSPECIFIED:
return domain.PrivateLabelingSettingUnspecified
default:
return domain.PrivateLabelingSettingUnspecified
}
}
func (s *Server) UpdateProject(ctx context.Context, req *project_pb.UpdateProjectRequest) (*project_pb.UpdateProjectResponse, error) {
project, err := s.command.ChangeProject(ctx, projectUpdateToCommand(req))
if err != nil {
return nil, err
}
var changeDate *timestamppb.Timestamp
if !project.EventDate.IsZero() {
changeDate = timestamppb.New(project.EventDate)
}
return &project_pb.UpdateProjectResponse{
ChangeDate: changeDate,
}, nil
}
func projectUpdateToCommand(req *project_pb.UpdateProjectRequest) *command.ChangeProject {
var labeling *domain.PrivateLabelingSetting
if req.PrivateLabelingSetting != nil {
labeling = gu.Ptr(privateLabelingSettingToDomain(*req.PrivateLabelingSetting))
}
return &command.ChangeProject{
ObjectRoot: models.ObjectRoot{
AggregateID: req.Id,
},
Name: req.Name,
ProjectRoleAssertion: req.ProjectRoleAssertion,
ProjectRoleCheck: req.ProjectRoleCheck,
HasProjectCheck: req.HasProjectCheck,
PrivateLabelingSetting: labeling,
}
}
func (s *Server) DeleteProject(ctx context.Context, req *project_pb.DeleteProjectRequest) (*project_pb.DeleteProjectResponse, error) {
userGrantIDs, err := s.userGrantsFromProject(ctx, req.Id)
if err != nil {
return nil, err
}
deletedAt, err := s.command.DeleteProject(ctx, req.Id, "", userGrantIDs...)
if err != nil {
return nil, err
}
var deletionDate *timestamppb.Timestamp
if !deletedAt.IsZero() {
deletionDate = timestamppb.New(deletedAt)
}
return &project_pb.DeleteProjectResponse{
DeletionDate: deletionDate,
}, nil
}
func (s *Server) userGrantsFromProject(ctx context.Context, projectID string) ([]string, error) {
projectQuery, err := query.NewUserGrantProjectIDSearchQuery(projectID)
if err != nil {
return nil, err
}
userGrants, err := s.query.UserGrants(ctx, &query.UserGrantsQueries{
Queries: []query.SearchQuery{projectQuery},
}, false)
if err != nil {
return nil, err
}
return userGrantsToIDs(userGrants.UserGrants), nil
}
func (s *Server) DeactivateProject(ctx context.Context, req *project_pb.DeactivateProjectRequest) (*project_pb.DeactivateProjectResponse, error) {
details, err := s.command.DeactivateProject(ctx, req.Id, "")
if err != nil {
return nil, err
}
var changeDate *timestamppb.Timestamp
if !details.EventDate.IsZero() {
changeDate = timestamppb.New(details.EventDate)
}
return &project_pb.DeactivateProjectResponse{
ChangeDate: changeDate,
}, nil
}
func (s *Server) ActivateProject(ctx context.Context, req *project_pb.ActivateProjectRequest) (*project_pb.ActivateProjectResponse, error) {
details, err := s.command.ReactivateProject(ctx, req.Id, "")
if err != nil {
return nil, err
}
var changeDate *timestamppb.Timestamp
if !details.EventDate.IsZero() {
changeDate = timestamppb.New(details.EventDate)
}
return &project_pb.ActivateProjectResponse{
ChangeDate: changeDate,
}, nil
}
func userGrantsToIDs(userGrants []*query.UserGrant) []string {
converted := make([]string, len(userGrants))
for i, grant := range userGrants {
converted[i] = grant.ID
}
return converted
}
@@ -0,0 +1,126 @@
package project
import (
"context"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/query"
project_pb "github.com/zitadel/zitadel/pkg/grpc/project/v2beta"
)
func (s *Server) CreateProjectGrant(ctx context.Context, req *project_pb.CreateProjectGrantRequest) (*project_pb.CreateProjectGrantResponse, error) {
add := projectGrantCreateToCommand(req)
project, err := s.command.AddProjectGrant(ctx, add)
if err != nil {
return nil, err
}
var creationDate *timestamppb.Timestamp
if !project.EventDate.IsZero() {
creationDate = timestamppb.New(project.EventDate)
}
return &project_pb.CreateProjectGrantResponse{
CreationDate: creationDate,
}, nil
}
func projectGrantCreateToCommand(req *project_pb.CreateProjectGrantRequest) *command.AddProjectGrant {
return &command.AddProjectGrant{
ObjectRoot: models.ObjectRoot{
AggregateID: req.ProjectId,
},
GrantID: req.GrantedOrganizationId,
GrantedOrgID: req.GrantedOrganizationId,
RoleKeys: req.RoleKeys,
}
}
func (s *Server) UpdateProjectGrant(ctx context.Context, req *project_pb.UpdateProjectGrantRequest) (*project_pb.UpdateProjectGrantResponse, error) {
project, err := s.command.ChangeProjectGrant(ctx, projectGrantUpdateToCommand(req))
if err != nil {
return nil, err
}
var changeDate *timestamppb.Timestamp
if !project.EventDate.IsZero() {
changeDate = timestamppb.New(project.EventDate)
}
return &project_pb.UpdateProjectGrantResponse{
ChangeDate: changeDate,
}, nil
}
func projectGrantUpdateToCommand(req *project_pb.UpdateProjectGrantRequest) *command.ChangeProjectGrant {
return &command.ChangeProjectGrant{
ObjectRoot: models.ObjectRoot{
AggregateID: req.ProjectId,
},
GrantID: req.GrantedOrganizationId,
RoleKeys: req.RoleKeys,
}
}
func (s *Server) DeactivateProjectGrant(ctx context.Context, req *project_pb.DeactivateProjectGrantRequest) (*project_pb.DeactivateProjectGrantResponse, error) {
details, err := s.command.DeactivateProjectGrant(ctx, req.ProjectId, req.GrantedOrganizationId, "")
if err != nil {
return nil, err
}
var changeDate *timestamppb.Timestamp
if !details.EventDate.IsZero() {
changeDate = timestamppb.New(details.EventDate)
}
return &project_pb.DeactivateProjectGrantResponse{
ChangeDate: changeDate,
}, nil
}
func (s *Server) ActivateProjectGrant(ctx context.Context, req *project_pb.ActivateProjectGrantRequest) (*project_pb.ActivateProjectGrantResponse, error) {
details, err := s.command.ReactivateProjectGrant(ctx, req.ProjectId, req.GrantedOrganizationId, "")
if err != nil {
return nil, err
}
var changeDate *timestamppb.Timestamp
if !details.EventDate.IsZero() {
changeDate = timestamppb.New(details.EventDate)
}
return &project_pb.ActivateProjectGrantResponse{
ChangeDate: changeDate,
}, nil
}
func (s *Server) DeleteProjectGrant(ctx context.Context, req *project_pb.DeleteProjectGrantRequest) (*project_pb.DeleteProjectGrantResponse, error) {
userGrantIDs, err := s.userGrantsFromProjectGrant(ctx, req.ProjectId, req.GrantedOrganizationId)
if err != nil {
return nil, err
}
details, err := s.command.RemoveProjectGrant(ctx, req.ProjectId, req.GrantedOrganizationId, "", userGrantIDs...)
if err != nil {
return nil, err
}
var deletionDate *timestamppb.Timestamp
if !details.EventDate.IsZero() {
deletionDate = timestamppb.New(details.EventDate)
}
return &project_pb.DeleteProjectGrantResponse{
DeletionDate: deletionDate,
}, nil
}
func (s *Server) userGrantsFromProjectGrant(ctx context.Context, projectID, grantedOrganizationID string) ([]string, error) {
projectQuery, err := query.NewUserGrantProjectIDSearchQuery(projectID)
if err != nil {
return nil, err
}
grantQuery, err := query.NewUserGrantGrantIDSearchQuery(grantedOrganizationID)
if err != nil {
return nil, err
}
userGrants, err := s.query.UserGrants(ctx, &query.UserGrantsQueries{
Queries: []query.SearchQuery{projectQuery, grantQuery},
}, false)
if err != nil {
return nil, err
}
return userGrantsToIDs(userGrants.UserGrants), nil
}
@@ -0,0 +1,132 @@
package project
import (
"context"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/query"
project_pb "github.com/zitadel/zitadel/pkg/grpc/project/v2beta"
)
func (s *Server) AddProjectRole(ctx context.Context, req *project_pb.AddProjectRoleRequest) (*project_pb.AddProjectRoleResponse, error) {
role, err := s.command.AddProjectRole(ctx, addProjectRoleRequestToCommand(req))
if err != nil {
return nil, err
}
var creationDate *timestamppb.Timestamp
if !role.EventDate.IsZero() {
creationDate = timestamppb.New(role.EventDate)
}
return &project_pb.AddProjectRoleResponse{
CreationDate: creationDate,
}, nil
}
func addProjectRoleRequestToCommand(req *project_pb.AddProjectRoleRequest) *command.AddProjectRole {
group := ""
if req.Group != nil {
group = *req.Group
}
return &command.AddProjectRole{
ObjectRoot: models.ObjectRoot{
AggregateID: req.ProjectId,
},
Key: req.RoleKey,
DisplayName: req.DisplayName,
Group: group,
}
}
func (s *Server) UpdateProjectRole(ctx context.Context, req *project_pb.UpdateProjectRoleRequest) (*project_pb.UpdateProjectRoleResponse, error) {
role, err := s.command.ChangeProjectRole(ctx, updateProjectRoleRequestToCommand(req))
if err != nil {
return nil, err
}
var changeDate *timestamppb.Timestamp
if !role.EventDate.IsZero() {
changeDate = timestamppb.New(role.EventDate)
}
return &project_pb.UpdateProjectRoleResponse{
ChangeDate: changeDate,
}, nil
}
func updateProjectRoleRequestToCommand(req *project_pb.UpdateProjectRoleRequest) *command.ChangeProjectRole {
displayName := ""
if req.DisplayName != nil {
displayName = *req.DisplayName
}
group := ""
if req.Group != nil {
group = *req.Group
}
return &command.ChangeProjectRole{
ObjectRoot: models.ObjectRoot{
AggregateID: req.ProjectId,
},
Key: req.RoleKey,
DisplayName: displayName,
Group: group,
}
}
func (s *Server) RemoveProjectRole(ctx context.Context, req *project_pb.RemoveProjectRoleRequest) (*project_pb.RemoveProjectRoleResponse, error) {
userGrantIDs, err := s.userGrantsFromProjectAndRole(ctx, req.ProjectId, req.RoleKey)
if err != nil {
return nil, err
}
projectGrantIDs, err := s.projectGrantsFromProjectAndRole(ctx, req.ProjectId, req.RoleKey)
if err != nil {
return nil, err
}
details, err := s.command.RemoveProjectRole(ctx, req.ProjectId, req.RoleKey, "", projectGrantIDs, userGrantIDs...)
if err != nil {
return nil, err
}
var deletionDate *timestamppb.Timestamp
if !details.EventDate.IsZero() {
deletionDate = timestamppb.New(details.EventDate)
}
return &project_pb.RemoveProjectRoleResponse{
RemovalDate: deletionDate,
}, nil
}
func (s *Server) userGrantsFromProjectAndRole(ctx context.Context, projectID, roleKey string) ([]string, error) {
projectQuery, err := query.NewUserGrantProjectIDSearchQuery(projectID)
if err != nil {
return nil, err
}
rolesQuery, err := query.NewUserGrantRoleQuery(roleKey)
if err != nil {
return nil, err
}
userGrants, err := s.query.UserGrants(ctx, &query.UserGrantsQueries{
Queries: []query.SearchQuery{projectQuery, rolesQuery},
}, false)
if err != nil {
return nil, err
}
return userGrantsToIDs(userGrants.UserGrants), nil
}
func (s *Server) projectGrantsFromProjectAndRole(ctx context.Context, projectID, roleKey string) ([]string, error) {
projectGrants, err := s.query.SearchProjectGrantsByProjectIDAndRoleKey(ctx, projectID, roleKey)
if err != nil {
return nil, err
}
return projectGrantsToIDs(projectGrants), nil
}
func projectGrantsToIDs(projectGrants *query.ProjectGrants) []string {
converted := make([]string, len(projectGrants.ProjectGrants))
for i, grant := range projectGrants.ProjectGrants {
converted[i] = grant.GrantID
}
return converted
}
+427
View File
@@ -0,0 +1,427 @@
package project
import (
"context"
"google.golang.org/protobuf/types/known/timestamppb"
filter "github.com/zitadel/zitadel/internal/api/grpc/filter/v2beta"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/zerrors"
project_pb "github.com/zitadel/zitadel/pkg/grpc/project/v2beta"
)
func (s *Server) GetProject(ctx context.Context, req *project_pb.GetProjectRequest) (*project_pb.GetProjectResponse, error) {
project, err := s.query.GetProjectByIDWithPermission(ctx, true, req.Id, s.checkPermission)
if err != nil {
return nil, err
}
return &project_pb.GetProjectResponse{
Project: projectToPb(project),
}, nil
}
func (s *Server) ListProjects(ctx context.Context, req *project_pb.ListProjectsRequest) (*project_pb.ListProjectsResponse, error) {
queries, err := s.listProjectRequestToModel(req)
if err != nil {
return nil, err
}
resp, err := s.query.SearchGrantedProjects(ctx, queries, s.checkPermission)
if err != nil {
return nil, err
}
return &project_pb.ListProjectsResponse{
Projects: grantedProjectsToPb(resp.GrantedProjects),
Pagination: filter.QueryToPaginationPb(queries.SearchRequest, resp.SearchResponse),
}, nil
}
func (s *Server) listProjectRequestToModel(req *project_pb.ListProjectsRequest) (*query.ProjectAndGrantedProjectSearchQueries, error) {
offset, limit, asc, err := filter.PaginationPbToQuery(s.systemDefaults, req.Pagination)
if err != nil {
return nil, err
}
queries, err := projectFiltersToQuery(req.Filters)
if err != nil {
return nil, err
}
return &query.ProjectAndGrantedProjectSearchQueries{
SearchRequest: query.SearchRequest{
Offset: offset,
Limit: limit,
Asc: asc,
SortingColumn: grantedProjectFieldNameToSortingColumn(req.SortingColumn),
},
Queries: queries,
}, nil
}
func grantedProjectFieldNameToSortingColumn(field *project_pb.ProjectFieldName) query.Column {
if field == nil {
return query.GrantedProjectColumnCreationDate
}
switch *field {
case project_pb.ProjectFieldName_PROJECT_FIELD_NAME_CREATION_DATE:
return query.GrantedProjectColumnCreationDate
case project_pb.ProjectFieldName_PROJECT_FIELD_NAME_ID:
return query.GrantedProjectColumnID
case project_pb.ProjectFieldName_PROJECT_FIELD_NAME_NAME:
return query.GrantedProjectColumnName
case project_pb.ProjectFieldName_PROJECT_FIELD_NAME_CHANGE_DATE:
return query.GrantedProjectColumnChangeDate
case project_pb.ProjectFieldName_PROJECT_FIELD_NAME_UNSPECIFIED:
return query.GrantedProjectColumnCreationDate
default:
return query.GrantedProjectColumnCreationDate
}
}
func projectFiltersToQuery(queries []*project_pb.ProjectSearchFilter) (_ []query.SearchQuery, err error) {
q := make([]query.SearchQuery, len(queries))
for i, qry := range queries {
q[i], err = projectFilterToModel(qry)
if err != nil {
return nil, err
}
}
return q, nil
}
func projectFilterToModel(filter *project_pb.ProjectSearchFilter) (query.SearchQuery, error) {
switch q := filter.Filter.(type) {
case *project_pb.ProjectSearchFilter_ProjectNameFilter:
return projectNameFilterToQuery(q.ProjectNameFilter)
case *project_pb.ProjectSearchFilter_InProjectIdsFilter:
return projectInIDsFilterToQuery(q.InProjectIdsFilter)
case *project_pb.ProjectSearchFilter_ProjectResourceOwnerFilter:
return projectResourceOwnerFilterToQuery(q.ProjectResourceOwnerFilter)
case *project_pb.ProjectSearchFilter_ProjectOrganizationIdFilter:
return projectOrganizationIDFilterToQuery(q.ProjectOrganizationIdFilter)
case *project_pb.ProjectSearchFilter_ProjectGrantResourceOwnerFilter:
return projectGrantResourceOwnerFilterToQuery(q.ProjectGrantResourceOwnerFilter)
default:
return nil, zerrors.ThrowInvalidArgument(nil, "ORG-vR9nC", "List.Query.Invalid")
}
}
func projectNameFilterToQuery(q *project_pb.ProjectNameFilter) (query.SearchQuery, error) {
return query.NewGrantedProjectNameSearchQuery(filter.TextMethodPbToQuery(q.Method), q.GetProjectName())
}
func projectInIDsFilterToQuery(q *project_pb.InProjectIDsFilter) (query.SearchQuery, error) {
return query.NewGrantedProjectIDSearchQuery(q.ProjectIds)
}
func projectResourceOwnerFilterToQuery(q *project_pb.ProjectResourceOwnerFilter) (query.SearchQuery, error) {
return query.NewGrantedProjectResourceOwnerSearchQuery(q.ProjectResourceOwner)
}
func projectOrganizationIDFilterToQuery(q *project_pb.ProjectOrganizationIDFilter) (query.SearchQuery, error) {
return query.NewGrantedProjectOrganizationIDSearchQuery(q.ProjectOrganizationId)
}
func projectGrantResourceOwnerFilterToQuery(q *project_pb.ProjectGrantResourceOwnerFilter) (query.SearchQuery, error) {
return query.NewGrantedProjectGrantResourceOwnerSearchQuery(q.ProjectGrantResourceOwner)
}
func grantedProjectsToPb(projects []*query.GrantedProject) []*project_pb.Project {
o := make([]*project_pb.Project, len(projects))
for i, org := range projects {
o[i] = grantedProjectToPb(org)
}
return o
}
func projectToPb(project *query.Project) *project_pb.Project {
return &project_pb.Project{
Id: project.ID,
OrganizationId: project.ResourceOwner,
CreationDate: timestamppb.New(project.CreationDate),
ChangeDate: timestamppb.New(project.ChangeDate),
State: projectStateToPb(project.State),
Name: project.Name,
PrivateLabelingSetting: privateLabelingSettingToPb(project.PrivateLabelingSetting),
ProjectAccessRequired: project.HasProjectCheck,
ProjectRoleAssertion: project.ProjectRoleAssertion,
AuthorizationRequired: project.ProjectRoleCheck,
}
}
func grantedProjectToPb(project *query.GrantedProject) *project_pb.Project {
var grantedOrganizationID, grantedOrganizationName *string
if project.GrantedOrgID != "" {
grantedOrganizationID = &project.GrantedOrgID
}
if project.OrgName != "" {
grantedOrganizationName = &project.OrgName
}
return &project_pb.Project{
Id: project.ProjectID,
OrganizationId: project.ResourceOwner,
CreationDate: timestamppb.New(project.CreationDate),
ChangeDate: timestamppb.New(project.ChangeDate),
State: projectStateToPb(project.ProjectState),
Name: project.ProjectName,
PrivateLabelingSetting: privateLabelingSettingToPb(project.PrivateLabelingSetting),
ProjectAccessRequired: project.HasProjectCheck,
ProjectRoleAssertion: project.ProjectRoleAssertion,
AuthorizationRequired: project.ProjectRoleCheck,
GrantedOrganizationId: grantedOrganizationID,
GrantedOrganizationName: grantedOrganizationName,
GrantedState: grantedProjectStateToPb(project.ProjectGrantState),
}
}
func projectStateToPb(state domain.ProjectState) project_pb.ProjectState {
switch state {
case domain.ProjectStateActive:
return project_pb.ProjectState_PROJECT_STATE_ACTIVE
case domain.ProjectStateInactive:
return project_pb.ProjectState_PROJECT_STATE_INACTIVE
case domain.ProjectStateUnspecified, domain.ProjectStateRemoved:
return project_pb.ProjectState_PROJECT_STATE_UNSPECIFIED
default:
return project_pb.ProjectState_PROJECT_STATE_UNSPECIFIED
}
}
func grantedProjectStateToPb(state domain.ProjectGrantState) project_pb.GrantedProjectState {
switch state {
case domain.ProjectGrantStateActive:
return project_pb.GrantedProjectState_GRANTED_PROJECT_STATE_ACTIVE
case domain.ProjectGrantStateInactive:
return project_pb.GrantedProjectState_GRANTED_PROJECT_STATE_INACTIVE
case domain.ProjectGrantStateUnspecified, domain.ProjectGrantStateRemoved:
return project_pb.GrantedProjectState_GRANTED_PROJECT_STATE_UNSPECIFIED
default:
return project_pb.GrantedProjectState_GRANTED_PROJECT_STATE_UNSPECIFIED
}
}
func privateLabelingSettingToPb(setting domain.PrivateLabelingSetting) project_pb.PrivateLabelingSetting {
switch setting {
case domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy:
return project_pb.PrivateLabelingSetting_PRIVATE_LABELING_SETTING_ALLOW_LOGIN_USER_RESOURCE_OWNER_POLICY
case domain.PrivateLabelingSettingEnforceProjectResourceOwnerPolicy:
return project_pb.PrivateLabelingSetting_PRIVATE_LABELING_SETTING_ENFORCE_PROJECT_RESOURCE_OWNER_POLICY
case domain.PrivateLabelingSettingUnspecified:
return project_pb.PrivateLabelingSetting_PRIVATE_LABELING_SETTING_UNSPECIFIED
default:
return project_pb.PrivateLabelingSetting_PRIVATE_LABELING_SETTING_UNSPECIFIED
}
}
func (s *Server) ListProjectGrants(ctx context.Context, req *project_pb.ListProjectGrantsRequest) (*project_pb.ListProjectGrantsResponse, error) {
queries, err := s.listProjectGrantsRequestToModel(req)
if err != nil {
return nil, err
}
resp, err := s.query.SearchProjectGrants(ctx, queries, s.checkPermission)
if err != nil {
return nil, err
}
return &project_pb.ListProjectGrantsResponse{
ProjectGrants: projectGrantsToPb(resp.ProjectGrants),
Pagination: filter.QueryToPaginationPb(queries.SearchRequest, resp.SearchResponse),
}, nil
}
func (s *Server) listProjectGrantsRequestToModel(req *project_pb.ListProjectGrantsRequest) (*query.ProjectGrantSearchQueries, error) {
offset, limit, asc, err := filter.PaginationPbToQuery(s.systemDefaults, req.Pagination)
if err != nil {
return nil, err
}
queries, err := projectGrantFiltersToModel(req.Filters)
if err != nil {
return nil, err
}
return &query.ProjectGrantSearchQueries{
SearchRequest: query.SearchRequest{
Offset: offset,
Limit: limit,
Asc: asc,
SortingColumn: projectGrantFieldNameToSortingColumn(req.SortingColumn),
},
Queries: queries,
}, nil
}
func projectGrantFieldNameToSortingColumn(field *project_pb.ProjectGrantFieldName) query.Column {
if field == nil {
return query.ProjectGrantColumnCreationDate
}
switch *field {
case project_pb.ProjectGrantFieldName_PROJECT_GRANT_FIELD_NAME_PROJECT_ID:
return query.ProjectGrantColumnProjectID
case project_pb.ProjectGrantFieldName_PROJECT_GRANT_FIELD_NAME_CREATION_DATE:
return query.ProjectGrantColumnCreationDate
case project_pb.ProjectGrantFieldName_PROJECT_GRANT_FIELD_NAME_CHANGE_DATE:
return query.ProjectGrantColumnChangeDate
case project_pb.ProjectGrantFieldName_PROJECT_GRANT_FIELD_NAME_UNSPECIFIED:
return query.ProjectGrantColumnCreationDate
default:
return query.ProjectGrantColumnCreationDate
}
}
func projectGrantFiltersToModel(queries []*project_pb.ProjectGrantSearchFilter) (_ []query.SearchQuery, err error) {
q := make([]query.SearchQuery, len(queries))
for i, qry := range queries {
q[i], err = projectGrantFilterToModel(qry)
if err != nil {
return nil, err
}
}
return q, nil
}
func projectGrantFilterToModel(filter *project_pb.ProjectGrantSearchFilter) (query.SearchQuery, error) {
switch q := filter.Filter.(type) {
case *project_pb.ProjectGrantSearchFilter_ProjectNameFilter:
return projectNameFilterToQuery(q.ProjectNameFilter)
case *project_pb.ProjectGrantSearchFilter_RoleKeyFilter:
return query.NewProjectGrantRoleKeySearchQuery(q.RoleKeyFilter.Key)
case *project_pb.ProjectGrantSearchFilter_InProjectIdsFilter:
return query.NewProjectGrantProjectIDsSearchQuery(q.InProjectIdsFilter.ProjectIds)
case *project_pb.ProjectGrantSearchFilter_ProjectResourceOwnerFilter:
return query.NewProjectGrantResourceOwnerSearchQuery(q.ProjectResourceOwnerFilter.ProjectResourceOwner)
case *project_pb.ProjectGrantSearchFilter_ProjectGrantResourceOwnerFilter:
return query.NewProjectGrantGrantedOrgIDSearchQuery(q.ProjectGrantResourceOwnerFilter.ProjectGrantResourceOwner)
default:
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-M099f", "List.Query.Invalid")
}
}
func projectGrantsToPb(projects []*query.ProjectGrant) []*project_pb.ProjectGrant {
p := make([]*project_pb.ProjectGrant, len(projects))
for i, project := range projects {
p[i] = projectGrantToPb(project)
}
return p
}
func projectGrantToPb(project *query.ProjectGrant) *project_pb.ProjectGrant {
return &project_pb.ProjectGrant{
OrganizationId: project.ResourceOwner,
CreationDate: timestamppb.New(project.CreationDate),
ChangeDate: timestamppb.New(project.ChangeDate),
GrantedOrganizationId: project.GrantedOrgID,
GrantedOrganizationName: project.OrgName,
GrantedRoleKeys: project.GrantedRoleKeys,
ProjectId: project.ProjectID,
ProjectName: project.ProjectName,
State: projectGrantStateToPb(project.State),
}
}
func projectGrantStateToPb(state domain.ProjectGrantState) project_pb.ProjectGrantState {
switch state {
case domain.ProjectGrantStateActive:
return project_pb.ProjectGrantState_PROJECT_GRANT_STATE_ACTIVE
case domain.ProjectGrantStateInactive:
return project_pb.ProjectGrantState_PROJECT_GRANT_STATE_INACTIVE
case domain.ProjectGrantStateUnspecified, domain.ProjectGrantStateRemoved:
return project_pb.ProjectGrantState_PROJECT_GRANT_STATE_UNSPECIFIED
default:
return project_pb.ProjectGrantState_PROJECT_GRANT_STATE_UNSPECIFIED
}
}
func (s *Server) ListProjectRoles(ctx context.Context, req *project_pb.ListProjectRolesRequest) (*project_pb.ListProjectRolesResponse, error) {
queries, err := s.listProjectRolesRequestToModel(req)
if err != nil {
return nil, err
}
err = queries.AppendProjectIDQuery(req.ProjectId)
if err != nil {
return nil, err
}
roles, err := s.query.SearchProjectRoles(ctx, true, queries, s.checkPermission)
if err != nil {
return nil, err
}
return &project_pb.ListProjectRolesResponse{
ProjectRoles: roleViewsToPb(roles.ProjectRoles),
Pagination: filter.QueryToPaginationPb(queries.SearchRequest, roles.SearchResponse),
}, nil
}
func (s *Server) listProjectRolesRequestToModel(req *project_pb.ListProjectRolesRequest) (*query.ProjectRoleSearchQueries, error) {
offset, limit, asc, err := filter.PaginationPbToQuery(s.systemDefaults, req.Pagination)
if err != nil {
return nil, err
}
queries, err := roleQueriesToModel(req.Filters)
if err != nil {
return nil, err
}
return &query.ProjectRoleSearchQueries{
SearchRequest: query.SearchRequest{
Offset: offset,
Limit: limit,
Asc: asc,
SortingColumn: projectRoleFieldNameToSortingColumn(req.SortingColumn),
},
Queries: queries,
}, nil
}
func projectRoleFieldNameToSortingColumn(field *project_pb.ProjectRoleFieldName) query.Column {
if field == nil {
return query.ProjectRoleColumnCreationDate
}
switch *field {
case project_pb.ProjectRoleFieldName_PROJECT_ROLE_FIELD_NAME_KEY:
return query.ProjectRoleColumnKey
case project_pb.ProjectRoleFieldName_PROJECT_ROLE_FIELD_NAME_CREATION_DATE:
return query.ProjectRoleColumnCreationDate
case project_pb.ProjectRoleFieldName_PROJECT_ROLE_FIELD_NAME_CHANGE_DATE:
return query.ProjectRoleColumnChangeDate
case project_pb.ProjectRoleFieldName_PROJECT_ROLE_FIELD_NAME_UNSPECIFIED:
return query.ProjectRoleColumnCreationDate
default:
return query.ProjectRoleColumnCreationDate
}
}
func roleQueriesToModel(queries []*project_pb.ProjectRoleSearchFilter) (_ []query.SearchQuery, err error) {
q := make([]query.SearchQuery, len(queries))
for i, query := range queries {
q[i], err = roleQueryToModel(query)
if err != nil {
return nil, err
}
}
return q, nil
}
func roleQueryToModel(apiQuery *project_pb.ProjectRoleSearchFilter) (query.SearchQuery, error) {
switch q := apiQuery.Filter.(type) {
case *project_pb.ProjectRoleSearchFilter_RoleKeyFilter:
return query.NewProjectRoleKeySearchQuery(filter.TextMethodPbToQuery(q.RoleKeyFilter.Method), q.RoleKeyFilter.Key)
case *project_pb.ProjectRoleSearchFilter_DisplayNameFilter:
return query.NewProjectRoleDisplayNameSearchQuery(filter.TextMethodPbToQuery(q.DisplayNameFilter.Method), q.DisplayNameFilter.DisplayName)
default:
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-fms0e", "List.Query.Invalid")
}
}
func roleViewsToPb(roles []*query.ProjectRole) []*project_pb.ProjectRole {
o := make([]*project_pb.ProjectRole, len(roles))
for i, org := range roles {
o[i] = roleViewToPb(org)
}
return o
}
func roleViewToPb(role *query.ProjectRole) *project_pb.ProjectRole {
return &project_pb.ProjectRole{
ProjectId: role.ProjectID,
Key: role.Key,
CreationDate: timestamppb.New(role.CreationDate),
ChangeDate: timestamppb.New(role.ChangeDate),
DisplayName: role.DisplayName,
Group: role.Group,
}
}
@@ -0,0 +1,60 @@
package project
import (
"google.golang.org/grpc"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/api/grpc/server"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/config/systemdefaults"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
project "github.com/zitadel/zitadel/pkg/grpc/project/v2beta"
)
var _ project.ProjectServiceServer = (*Server)(nil)
type Server struct {
project.UnimplementedProjectServiceServer
systemDefaults systemdefaults.SystemDefaults
command *command.Commands
query *query.Queries
checkPermission domain.PermissionCheck
}
type Config struct{}
func CreateServer(
systemDefaults systemdefaults.SystemDefaults,
command *command.Commands,
query *query.Queries,
checkPermission domain.PermissionCheck,
) *Server {
return &Server{
systemDefaults: systemDefaults,
command: command,
query: query,
checkPermission: checkPermission,
}
}
func (s *Server) RegisterServer(grpcServer *grpc.Server) {
project.RegisterProjectServiceServer(grpcServer, s)
}
func (s *Server) AppName() string {
return project.ProjectService_ServiceDesc.ServiceName
}
func (s *Server) MethodPrefix() string {
return project.ProjectService_ServiceDesc.ServiceName
}
func (s *Server) AuthMethods() authz.MethodMapping {
return project.ProjectService_AuthMethods
}
func (s *Server) RegisterGateway() server.RegisterGatewayFunc {
return project.RegisterProjectServiceHandler
}
@@ -332,7 +332,7 @@ func TestServer_CreateResponse_Permission(t *testing.T) {
projectID2, _, _ := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, true)
orgResp := Instance.CreateOrganization(ctx, "saml-permission-"+gofakeit.AppName(), gofakeit.Email())
Instance.CreateProjectGrant(ctx, projectID2, orgResp.GetOrganizationId())
Instance.CreateProjectGrant(ctx, t, projectID2, orgResp.GetOrganizationId())
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
Instance.CreateProjectUserGrant(t, ctx, projectID, user.GetUserId())
@@ -346,7 +346,7 @@ func TestServer_CreateResponse_Permission(t *testing.T) {
projectID, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, true)
orgResp := Instance.CreateOrganization(ctx, "saml-permission-"+gofakeit.AppName(), gofakeit.Email())
Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
Instance.CreateProjectGrant(ctx, t, projectID, orgResp.GetOrganizationId())
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
Instance.CreateProjectUserGrant(t, ctx, projectID, user.GetUserId())
@@ -368,9 +368,9 @@ func TestServer_CreateResponse_Permission(t *testing.T) {
projectID, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, true)
orgResp := Instance.CreateOrganization(ctx, "saml-permission-"+gofakeit.AppName(), gofakeit.Email())
projectGrantResp := Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
Instance.CreateProjectGrant(ctx, t, projectID, orgResp.GetOrganizationId())
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
Instance.CreateProjectGrantUserGrant(ctx, orgResp.GetOrganizationId(), projectID, projectGrantResp.GetGrantId(), user.GetUserId())
Instance.CreateProjectGrantUserGrant(ctx, orgResp.GetOrganizationId(), projectID, orgResp.GetOrganizationId(), user.GetUserId())
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
},
@@ -502,9 +502,9 @@ func TestServer_CreateResponse_Permission(t *testing.T) {
projectID, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, false)
orgResp := Instance.CreateOrganization(ctx, "saml-permissison-"+gofakeit.AppName(), gofakeit.Email())
projectGrantResp := Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
Instance.CreateProjectGrant(ctx, t, projectID, orgResp.GetOrganizationId())
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
Instance.CreateProjectGrantUserGrant(ctx, orgResp.GetOrganizationId(), projectID, projectGrantResp.GetGrantId(), user.GetUserId())
Instance.CreateProjectGrantUserGrant(ctx, orgResp.GetOrganizationId(), projectID, orgResp.GetOrganizationId(), user.GetUserId())
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
},
@@ -523,7 +523,7 @@ func TestServer_CreateResponse_Permission(t *testing.T) {
projectID, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, false)
orgResp := Instance.CreateOrganization(ctx, "saml-permissison-"+gofakeit.AppName(), gofakeit.Email())
Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
Instance.CreateProjectGrant(ctx, t, projectID, orgResp.GetOrganizationId())
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
@@ -563,7 +563,7 @@ func TestServer_CreateResponse_Permission(t *testing.T) {
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
projectID, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, false, true)
orgResp := Instance.CreateOrganization(ctx, "saml-permissison-"+gofakeit.AppName(), gofakeit.Email())
Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
Instance.CreateProjectGrant(ctx, t, projectID, orgResp.GetOrganizationId())
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
@@ -640,10 +640,9 @@ func createSAMLSP(t *testing.T, idpMetadata *saml.EntityDescriptor, binding stri
}
func createSAMLApplication(ctx context.Context, t *testing.T, idpMetadata *saml.EntityDescriptor, binding string, projectRoleCheck, hasProjectCheck bool) (string, string, *samlsp.Middleware) {
project, err := Instance.CreateProjectWithPermissionCheck(ctx, projectRoleCheck, hasProjectCheck)
require.NoError(t, err)
project := Instance.CreateProject(ctx, t, "", gofakeit.AppName(), projectRoleCheck, hasProjectCheck)
rootURL, sp := createSAMLSP(t, idpMetadata, binding)
_, err = Instance.CreateSAMLClient(ctx, project.GetId(), sp)
_, err := Instance.CreateSAMLClient(ctx, project.GetId(), sp)
require.NoError(t, err)
return project.GetId(), rootURL, sp
}
@@ -1753,8 +1753,8 @@ func TestServer_ReactivateUser(t *testing.T) {
}
func TestServer_DeleteUser(t *testing.T) {
projectResp, err := Instance.CreateProject(CTX)
require.NoError(t, err)
projectResp := Instance.CreateProject(CTX, t, "", gofakeit.AppName(), false, false)
type args struct {
req *user.DeleteUserRequest
prepare func(*testing.T, *user.DeleteUserRequest) context.Context
@@ -1706,12 +1706,11 @@ func TestServer_ReactivateUser(t *testing.T) {
}
func TestServer_DeleteUser(t *testing.T) {
projectResp, err := Instance.CreateProject(CTX)
require.NoError(t, err)
projectResp := Instance.CreateProject(CTX, t, "", gofakeit.AppName(), false, false)
type args struct {
ctx context.Context
req *user.DeleteUserRequest
prepare func(request *user.DeleteUserRequest) error
prepare func(request *user.DeleteUserRequest)
}
tests := []struct {
name string
@@ -1726,7 +1725,7 @@ func TestServer_DeleteUser(t *testing.T) {
&user.DeleteUserRequest{
UserId: "notexisting",
},
func(request *user.DeleteUserRequest) error { return nil },
nil,
},
wantErr: true,
},
@@ -1735,10 +1734,9 @@ func TestServer_DeleteUser(t *testing.T) {
args: args{
ctx: CTX,
req: &user.DeleteUserRequest{},
prepare: func(request *user.DeleteUserRequest) error {
prepare: func(request *user.DeleteUserRequest) {
resp := Instance.CreateHumanUser(CTX)
request.UserId = resp.GetUserId()
return err
},
},
want: &user.DeleteUserResponse{
@@ -1753,10 +1751,9 @@ func TestServer_DeleteUser(t *testing.T) {
args: args{
ctx: CTX,
req: &user.DeleteUserRequest{},
prepare: func(request *user.DeleteUserRequest) error {
prepare: func(request *user.DeleteUserRequest) {
resp := Instance.CreateMachineUser(CTX)
request.UserId = resp.GetUserId()
return err
},
},
want: &user.DeleteUserResponse{
@@ -1771,13 +1768,12 @@ func TestServer_DeleteUser(t *testing.T) {
args: args{
ctx: CTX,
req: &user.DeleteUserRequest{},
prepare: func(request *user.DeleteUserRequest) error {
prepare: func(request *user.DeleteUserRequest) {
resp := Instance.CreateHumanUser(CTX)
request.UserId = resp.GetUserId()
Instance.CreateProjectUserGrant(t, CTX, projectResp.GetId(), request.UserId)
Instance.CreateProjectMembership(t, CTX, projectResp.GetId(), request.UserId)
Instance.CreateOrgMembership(t, CTX, request.UserId)
return err
},
},
want: &user.DeleteUserResponse{
@@ -1790,8 +1786,9 @@ func TestServer_DeleteUser(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.prepare(tt.args.req)
require.NoError(t, err)
if tt.args.prepare != nil {
tt.args.prepare(tt.args.req)
}
got, err := Client.DeleteUser(tt.args.ctx, tt.args.req)
if tt.wantErr {
+1 -1
View File
@@ -122,7 +122,7 @@ func (s *Server) assertClientScopesForPAT(ctx context.Context, token *accessToke
if err != nil {
return zerrors.ThrowInternal(err, "OIDC-Cyc78", "Errors.Internal")
}
roles, err := s.query.SearchProjectRoles(ctx, authz.GetFeatures(ctx).TriggerIntrospectionProjections, &query.ProjectRoleSearchQueries{Queries: []query.SearchQuery{projectIDQuery}})
roles, err := s.query.SearchProjectRoles(ctx, authz.GetFeatures(ctx).TriggerIntrospectionProjections, &query.ProjectRoleSearchQueries{Queries: []query.SearchQuery{projectIDQuery}}, nil)
if err != nil {
return err
}
+3 -3
View File
@@ -459,7 +459,7 @@ func (o *OPStorage) assertProjectRoleScopes(ctx context.Context, clientID string
if err != nil {
return nil, zerrors.ThrowInternal(err, "OIDC-Cyc78", "Errors.Internal")
}
roles, err := o.query.SearchProjectRoles(ctx, true, &query.ProjectRoleSearchQueries{Queries: []query.SearchQuery{projectIDQuery}})
roles, err := o.query.SearchProjectRoles(ctx, true, &query.ProjectRoleSearchQueries{Queries: []query.SearchQuery{projectIDQuery}}, nil)
if err != nil {
return nil, err
}
@@ -482,7 +482,7 @@ func (o *OPStorage) assertProjectRoleScopesByProject(ctx context.Context, projec
if err != nil {
return nil, zerrors.ThrowInternal(err, "OIDC-Cyc78", "Errors.Internal")
}
roles, err := o.query.SearchProjectRoles(ctx, true, &query.ProjectRoleSearchQueries{Queries: []query.SearchQuery{projectIDQuery}})
roles, err := o.query.SearchProjectRoles(ctx, true, &query.ProjectRoleSearchQueries{Queries: []query.SearchQuery{projectIDQuery}}, nil)
if err != nil {
return nil, err
}
@@ -498,7 +498,7 @@ func (o *OPStorage) assertClientScopesForPAT(ctx context.Context, token *model.T
if err != nil {
return zerrors.ThrowInternal(err, "OIDC-Cyc78", "Errors.Internal")
}
roles, err := o.query.SearchProjectRoles(ctx, true, &query.ProjectRoleSearchQueries{Queries: []query.SearchQuery{projectIDQuery}})
roles, err := o.query.SearchProjectRoles(ctx, true, &query.ProjectRoleSearchQueries{Queries: []query.SearchQuery{projectIDQuery}}, nil)
if err != nil {
return err
}
@@ -7,6 +7,7 @@ import (
"testing"
"time"
"github.com/brianvoe/gofakeit/v6"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zitadel/oidc/v3/pkg/client"
@@ -24,8 +25,7 @@ import (
)
func TestServer_Introspect(t *testing.T) {
project, err := Instance.CreateProject(CTX)
require.NoError(t, err)
project := Instance.CreateProject(CTX, t, "", gofakeit.AppName(), false, false)
app, err := Instance.CreateOIDCNativeClient(CTX, redirectURI, logoutRedirectURI, project.GetId(), false)
require.NoError(t, err)
@@ -188,10 +188,8 @@ func assertIntrospection(
// with clients that have different authentication methods.
func TestServer_VerifyClient(t *testing.T) {
sessionID, sessionToken, startTime, changeTime := Instance.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
project, err := Instance.CreateProject(CTX)
require.NoError(t, err)
projectInactive, err := Instance.CreateProject(CTX)
require.NoError(t, err)
project := Instance.CreateProject(CTX, t, "", gofakeit.AppName(), false, false)
projectInactive := Instance.CreateProject(CTX, t, "", gofakeit.AppName(), false, false)
inactiveClient, err := Instance.CreateOIDCInactivateClient(CTX, redirectURI, logoutRedirectURI, project.GetId())
require.NoError(t, err)
@@ -421,21 +421,20 @@ type clientOpts struct {
func createClientWithOpts(t testing.TB, instance *integration.Instance, opts clientOpts) (clientID, projectID string) {
ctx := instance.WithAuthorization(CTX, integration.UserTypeOrgOwner)
project, err := instance.CreateProject(ctx)
require.NoError(t, err)
project := instance.CreateProject(ctx, t.(*testing.T), "", gofakeit.AppName(), false, false)
app, err := instance.CreateOIDCClientLoginVersion(ctx, opts.redirectURI, opts.logoutURI, project.GetId(), app.OIDCAppType_OIDC_APP_TYPE_NATIVE, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE, opts.devMode, opts.LoginVersion)
require.NoError(t, err)
return app.GetClientId(), project.GetId()
}
func createImplicitClient(t testing.TB) string {
app, err := Instance.CreateOIDCImplicitFlowClient(CTX, redirectURIImplicit, nil)
app, err := Instance.CreateOIDCImplicitFlowClient(CTX, t.(*testing.T), redirectURIImplicit, nil)
require.NoError(t, err)
return app.GetClientId()
}
func createImplicitClientNoLoginClientHeader(t testing.TB) string {
app, err := Instance.CreateOIDCImplicitFlowClient(CTX, redirectURIImplicit, &app.LoginVersion{Version: &app.LoginVersion_LoginV2{LoginV2: &app.LoginV2{BaseUri: nil}}})
app, err := Instance.CreateOIDCImplicitFlowClient(CTX, t.(*testing.T), redirectURIImplicit, &app.LoginVersion{Version: &app.LoginVersion_LoginV2{LoginV2: &app.LoginV2{BaseUri: nil}}})
require.NoError(t, err)
return app.GetClientId()
}
@@ -8,6 +8,7 @@ import (
"testing"
"time"
"github.com/brianvoe/gofakeit/v6"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zitadel/oidc/v3/pkg/client/rp"
@@ -21,8 +22,7 @@ import (
)
func TestServer_DeviceAuth(t *testing.T) {
project, err := Instance.CreateProject(CTX)
require.NoError(t, err)
project := Instance.CreateProject(CTX, t, "", gofakeit.AppName(), false, false)
client, err := Instance.CreateOIDCClient(CTX, redirectURI, logoutRedirectURI, project.GetId(), app.OIDCAppType_OIDC_APP_TYPE_NATIVE, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE, false, app.OIDCGrantType_OIDC_GRANT_TYPE_DEVICE_CODE)
require.NoError(t, err)
@@ -147,7 +147,7 @@ func TestServer_TokenExchange(t *testing.T) {
ctx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
userResp := instance.CreateHumanUser(ctx)
client, keyData, err := instance.CreateOIDCTokenExchangeClient(ctx)
client, keyData, err := instance.CreateOIDCTokenExchangeClient(ctx, t)
require.NoError(t, err)
signer, err := rp.SignerFromKeyFile(keyData)()
require.NoError(t, err)
@@ -371,7 +371,7 @@ func TestServer_TokenExchangeImpersonation(t *testing.T) {
setTokenExchangeFeature(t, instance, true)
setImpersonationPolicy(t, instance, true)
client, keyData, err := instance.CreateOIDCTokenExchangeClient(ctx)
client, keyData, err := instance.CreateOIDCTokenExchangeClient(ctx, t)
require.NoError(t, err)
signer, err := rp.SignerFromKeyFile(keyData)()
require.NoError(t, err)
@@ -591,7 +591,7 @@ func TestImpersonation_API_Call(t *testing.T) {
instance := integration.NewInstance(CTX)
ctx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
client, keyData, err := instance.CreateOIDCTokenExchangeClient(ctx)
client, keyData, err := instance.CreateOIDCTokenExchangeClient(ctx, t)
require.NoError(t, err)
signer, err := rp.SignerFromKeyFile(keyData)()
require.NoError(t, err)
@@ -309,9 +309,7 @@ func TestServer_UserInfo_Issue6662(t *testing.T) {
roleBar = "bar"
)
project, err := Instance.CreateProject(CTX)
projectID := project.GetId()
require.NoError(t, err)
projectID := Instance.CreateProject(CTX, t, "", gofakeit.AppName(), false, false).GetId()
user, _, clientID, clientSecret, err := Instance.CreateOIDCCredentialsClient(CTX)
require.NoError(t, err)
addProjectRolesGrants(t, user.GetUserId(), projectID, roleFoo, roleBar)
@@ -8,6 +8,7 @@ import (
"testing"
"time"
"github.com/brianvoe/gofakeit/v6"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
@@ -76,13 +77,12 @@ func TestDeleteUser_errors(t *testing.T) {
func TestDeleteUser_ensureReallyDeleted(t *testing.T) {
// create user and dependencies
createUserResp := Instance.CreateHumanUser(CTX)
proj, err := Instance.CreateProject(CTX)
require.NoError(t, err)
proj := Instance.CreateProject(CTX, t, "", gofakeit.AppName(), false, false)
Instance.CreateProjectUserGrant(t, CTX, proj.Id, createUserResp.UserId)
// delete user via scim
err = Instance.Client.SCIM.Users.Delete(CTX, Instance.DefaultOrg.Id, createUserResp.UserId)
err := Instance.Client.SCIM.Users.Delete(CTX, Instance.DefaultOrg.Id, createUserResp.UserId)
assert.NoError(t, err)
// ensure it is really deleted => try to delete again => should 404
@@ -125,7 +125,7 @@ type userGrantProvider interface {
type projectProvider interface {
ProjectByClientID(context.Context, string) (*query.Project, error)
SearchProjectGrants(ctx context.Context, queries *query.ProjectGrantSearchQueries) (projects *query.ProjectGrants, err error)
SearchProjectGrants(ctx context.Context, queries *query.ProjectGrantSearchQueries, permissionCheck domain.PermissionCheck) (projects *query.ProjectGrants, err error)
}
type applicationProvider interface {
@@ -1841,7 +1841,7 @@ func projectRequired(ctx context.Context, request *domain.AuthRequest, projectPr
if err != nil {
return false, err
}
grants, err := projectProvider.SearchProjectGrants(ctx, &query.ProjectGrantSearchQueries{Queries: []query.SearchQuery{projectID, grantedOrg}})
grants, err := projectProvider.SearchProjectGrants(ctx, &query.ProjectGrantSearchQueries{Queries: []query.SearchQuery{projectID, grantedOrg}}, nil)
if err != nil {
return false, err
}
@@ -286,7 +286,7 @@ func (m *mockProject) ProjectByClientID(ctx context.Context, s string) (*query.P
return &query.Project{ResourceOwner: m.resourceOwner, HasProjectCheck: m.projectCheck}, nil
}
func (m *mockProject) SearchProjectGrants(ctx context.Context, queries *query.ProjectGrantSearchQueries) (*query.ProjectGrants, error) {
func (m *mockProject) SearchProjectGrants(ctx context.Context, queries *query.ProjectGrantSearchQueries, permissionCheck domain.PermissionCheck) (*query.ProjectGrants, error) {
if m.hasProject {
mockProjectGrant := new(query.ProjectGrant)
return &query.ProjectGrants{ProjectGrants: []*query.ProjectGrant{mockProjectGrant}}, nil
+1 -1
View File
@@ -468,7 +468,7 @@ func (c *Commands) prepareRemoveOrg(a *org.Aggregate) preparation.Validation {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMA-wG9p1", "Errors.Org.DefaultOrgNotDeletable")
}
err := c.checkProjectExists(ctx, instance.ProjectID(), a.ID)
_, err := c.checkProjectExists(ctx, instance.ProjectID(), a.ID)
// if there is no error, the ZITADEL project was found on the org to be deleted
if err == nil {
return nil, zerrors.ThrowPreconditionFailed(err, "COMMA-AF3JW", "Errors.Org.ZitadelOrgNotDeletable")
+13
View File
@@ -6,6 +6,7 @@ import (
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/project"
"github.com/zitadel/zitadel/internal/v2/user"
"github.com/zitadel/zitadel/internal/zerrors"
)
@@ -58,3 +59,15 @@ func (c *Commands) checkPermissionUpdateUser(ctx context.Context, resourceOwner,
func (c *Commands) checkPermissionUpdateUserCredentials(ctx context.Context, resourceOwner, userID string) error {
return c.checkPermissionOnUser(ctx, domain.PermissionUserCredentialWrite)(resourceOwner, userID)
}
func (c *Commands) checkPermissionDeleteProject(ctx context.Context, resourceOwner, projectID string) error {
return c.newPermissionCheck(ctx, domain.PermissionProjectDelete, project.AggregateType)(resourceOwner, projectID)
}
func (c *Commands) checkPermissionUpdateProject(ctx context.Context, resourceOwner, projectID string) error {
return c.newPermissionCheck(ctx, domain.PermissionProjectWrite, project.AggregateType)(resourceOwner, projectID)
}
func (c *Commands) checkPermissionWriteProjectGrant(ctx context.Context, resourceOwner, projectGrantID string) error {
return c.newPermissionCheck(ctx, domain.PermissionProjectGrantWrite, project.AggregateType)(resourceOwner, projectGrantID)
}
+154 -84
View File
@@ -3,6 +3,7 @@ package command
import (
"context"
"strings"
"time"
"github.com/zitadel/logging"
@@ -10,6 +11,7 @@ import (
"github.com/zitadel/zitadel/internal/command/preparation"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/feature"
"github.com/zitadel/zitadel/internal/query/projection"
"github.com/zitadel/zitadel/internal/repository/project"
@@ -17,67 +19,60 @@ import (
"github.com/zitadel/zitadel/internal/zerrors"
)
func (c *Commands) AddProjectWithID(ctx context.Context, project *domain.Project, resourceOwner, projectID string) (_ *domain.Project, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
if resourceOwner == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-w8tnSoJxtn", "Errors.ResourceOwnerMissing")
}
if projectID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-nDXf5vXoUj", "Errors.IDMissing")
}
if !project.IsValid() {
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-IOVCC", "Errors.Project.Invalid")
}
project, err = c.addProjectWithID(ctx, project, resourceOwner, projectID)
if err != nil {
return nil, err
}
return project, nil
type AddProject struct {
models.ObjectRoot
Name string
ProjectRoleAssertion bool
ProjectRoleCheck bool
HasProjectCheck bool
PrivateLabelingSetting domain.PrivateLabelingSetting
}
func (c *Commands) AddProject(ctx context.Context, project *domain.Project, resourceOwner string) (_ *domain.Project, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
if !project.IsValid() {
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-IOVCC", "Errors.Project.Invalid")
func (p *AddProject) IsValid() error {
if p.ResourceOwner == "" {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-fmq7bqQX1s", "Errors.ResourceOwnerMissing")
}
if resourceOwner == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-fmq7bqQX1s", "Errors.ResourceOwnerMissing")
if p.Name == "" {
return zerrors.ThrowInvalidArgument(nil, "PROJECT-IOVCC", "Errors.Project.Invalid")
}
projectID, err := c.idGenerator.Next()
if err != nil {
return nil, err
}
project, err = c.addProjectWithID(ctx, project, resourceOwner, projectID)
if err != nil {
return nil, err
}
return project, nil
return nil
}
func (c *Commands) addProjectWithID(ctx context.Context, projectAdd *domain.Project, resourceOwner, projectID string) (_ *domain.Project, err error) {
projectAdd.AggregateID = projectID
projectWriteModel, err := c.getProjectWriteModelByID(ctx, projectAdd.AggregateID, resourceOwner)
func (c *Commands) AddProject(ctx context.Context, add *AddProject) (_ *domain.ObjectDetails, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
if err := add.IsValid(); err != nil {
return nil, err
}
if add.AggregateID == "" {
add.AggregateID, err = c.idGenerator.Next()
if err != nil {
return nil, err
}
}
wm, err := c.getProjectWriteModelByID(ctx, add.AggregateID, add.ResourceOwner)
if err != nil {
return nil, err
}
if isProjectStateExists(projectWriteModel.State) {
if isProjectStateExists(wm.State) {
return nil, zerrors.ThrowAlreadyExists(nil, "COMMAND-opamwu", "Errors.Project.AlreadyExisting")
}
if err := c.checkPermissionUpdateProject(ctx, wm.ResourceOwner, wm.AggregateID); err != nil {
return nil, err
}
events := []eventstore.Command{
project.NewProjectAddedEvent(
ctx,
//nolint: contextcheck
ProjectAggregateFromWriteModel(&projectWriteModel.WriteModel),
projectAdd.Name,
projectAdd.ProjectRoleAssertion,
projectAdd.ProjectRoleCheck,
projectAdd.HasProjectCheck,
projectAdd.PrivateLabelingSetting),
ProjectAggregateFromWriteModelWithCTX(ctx, &wm.WriteModel),
add.Name,
add.ProjectRoleAssertion,
add.ProjectRoleCheck,
add.HasProjectCheck,
add.PrivateLabelingSetting),
}
postCommit, err := c.projectCreatedMilestone(ctx, &events)
if err != nil {
@@ -88,11 +83,11 @@ func (c *Commands) addProjectWithID(ctx context.Context, projectAdd *domain.Proj
return nil, err
}
postCommit(ctx)
err = AppendAndReduce(projectWriteModel, pushedEvents...)
err = AppendAndReduce(wm, pushedEvents...)
if err != nil {
return nil, err
}
return projectWriteModelToProject(projectWriteModel), nil
return writeModelToObjectDetails(&wm.WriteModel), nil
}
func AddProjectCommand(
@@ -143,14 +138,14 @@ func projectWriteModel(ctx context.Context, filter preparation.FilterToQueryRedu
return project, nil
}
func (c *Commands) projectAggregateByID(ctx context.Context, projectID, resourceOwner string) (*eventstore.Aggregate, domain.ProjectState, error) {
result, err := c.projectState(ctx, projectID, resourceOwner)
func (c *Commands) projectAggregateByID(ctx context.Context, projectID string) (*eventstore.Aggregate, domain.ProjectState, error) {
result, err := c.projectState(ctx, projectID)
if err != nil {
return nil, domain.ProjectStateUnspecified, zerrors.ThrowNotFound(err, "COMMA-NDQoF", "Errors.Project.NotFound")
}
if len(result) == 0 {
_ = projection.ProjectGrantFields.Trigger(ctx)
result, err = c.projectState(ctx, projectID, resourceOwner)
result, err = c.projectState(ctx, projectID)
if err != nil || len(result) == 0 {
return nil, domain.ProjectStateUnspecified, zerrors.ThrowNotFound(err, "COMMA-U1nza", "Errors.Project.NotFound")
}
@@ -164,7 +159,7 @@ func (c *Commands) projectAggregateByID(ctx context.Context, projectID, resource
return &result[0].Aggregate, state, nil
}
func (c *Commands) projectState(ctx context.Context, projectID, resourceOwner string) ([]*eventstore.SearchResult, error) {
func (c *Commands) projectState(ctx context.Context, projectID string) ([]*eventstore.SearchResult, error) {
return c.eventstore.Search(
ctx,
map[eventstore.FieldType]any{
@@ -172,12 +167,11 @@ func (c *Commands) projectState(ctx context.Context, projectID, resourceOwner st
eventstore.FieldTypeObjectID: projectID,
eventstore.FieldTypeObjectRevision: project.ProjectObjectRevision,
eventstore.FieldTypeFieldName: project.ProjectStateSearchField,
eventstore.FieldTypeResourceOwner: resourceOwner,
},
)
}
func (c *Commands) checkProjectExists(ctx context.Context, projectID, resourceOwner string) (err error) {
func (c *Commands) checkProjectExists(ctx context.Context, projectID, resourceOwner string) (_ string, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
@@ -185,50 +179,69 @@ func (c *Commands) checkProjectExists(ctx context.Context, projectID, resourceOw
return c.checkProjectExistsOld(ctx, projectID, resourceOwner)
}
_, state, err := c.projectAggregateByID(ctx, projectID, resourceOwner)
agg, state, err := c.projectAggregateByID(ctx, projectID)
if err != nil || !state.Valid() {
return zerrors.ThrowPreconditionFailed(err, "COMMA-VCnwD", "Errors.Project.NotFound")
return "", zerrors.ThrowPreconditionFailed(err, "COMMA-VCnwD", "Errors.Project.NotFound")
}
return agg.ResourceOwner, nil
}
type ChangeProject struct {
models.ObjectRoot
Name *string
ProjectRoleAssertion *bool
ProjectRoleCheck *bool
HasProjectCheck *bool
PrivateLabelingSetting *domain.PrivateLabelingSetting
}
func (p *ChangeProject) IsValid() error {
if p.AggregateID == "" {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-4m9vS", "Errors.Project.Invalid")
}
if p.Name != nil && *p.Name == "" {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-4m9vS", "Errors.Project.Invalid")
}
return nil
}
func (c *Commands) ChangeProject(ctx context.Context, projectChange *domain.Project, resourceOwner string) (*domain.Project, error) {
if !projectChange.IsValid() || projectChange.AggregateID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-4m9vS", "Errors.Project.Invalid")
func (c *Commands) ChangeProject(ctx context.Context, change *ChangeProject) (_ *domain.ObjectDetails, err error) {
if err := change.IsValid(); err != nil {
return nil, err
}
existingProject, err := c.getProjectWriteModelByID(ctx, projectChange.AggregateID, resourceOwner)
existing, err := c.getProjectWriteModelByID(ctx, change.AggregateID, change.ResourceOwner)
if err != nil {
return nil, err
}
if !isProjectStateExists(existingProject.State) {
if !isProjectStateExists(existing.State) {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-3M9sd", "Errors.Project.NotFound")
}
if err := c.checkPermissionUpdateProject(ctx, existing.ResourceOwner, existing.AggregateID); err != nil {
return nil, err
}
projectAgg := ProjectAggregateFromWriteModel(&existingProject.WriteModel)
changedEvent, hasChanged, err := existingProject.NewChangedEvent(
changedEvent := existing.NewChangedEvent(
ctx,
projectAgg,
projectChange.Name,
projectChange.ProjectRoleAssertion,
projectChange.ProjectRoleCheck,
projectChange.HasProjectCheck,
projectChange.PrivateLabelingSetting)
ProjectAggregateFromWriteModelWithCTX(ctx, &existing.WriteModel),
change.Name,
change.ProjectRoleAssertion,
change.ProjectRoleCheck,
change.HasProjectCheck,
change.PrivateLabelingSetting)
if changedEvent == nil {
return writeModelToObjectDetails(&existing.WriteModel), nil
}
err = c.pushAppendAndReduce(ctx, existing, changedEvent)
if err != nil {
return nil, err
}
if !hasChanged {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-2M0fs", "Errors.NoChangesFound")
}
err = c.pushAppendAndReduce(ctx, existingProject, changedEvent)
if err != nil {
return nil, err
}
return projectWriteModelToProject(existingProject), nil
return writeModelToObjectDetails(&existing.WriteModel), nil
}
func (c *Commands) DeactivateProject(ctx context.Context, projectID string, resourceOwner string) (*domain.ObjectDetails, error) {
if projectID == "" || resourceOwner == "" {
if projectID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-88iF0", "Errors.Project.ProjectIDMissing")
}
@@ -236,7 +249,7 @@ func (c *Commands) DeactivateProject(ctx context.Context, projectID string, reso
return c.deactivateProjectOld(ctx, projectID, resourceOwner)
}
projectAgg, state, err := c.projectAggregateByID(ctx, projectID, resourceOwner)
projectAgg, state, err := c.projectAggregateByID(ctx, projectID)
if err != nil {
return nil, err
}
@@ -247,6 +260,9 @@ func (c *Commands) DeactivateProject(ctx context.Context, projectID string, reso
if state != domain.ProjectStateActive {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-mki55", "Errors.Project.NotActive")
}
if err := c.checkPermissionUpdateProject(ctx, projectAgg.ResourceOwner, projectAgg.ID); err != nil {
return nil, err
}
pushedEvents, err := c.eventstore.Push(ctx, project.NewProjectDeactivatedEvent(ctx, projectAgg))
if err != nil {
@@ -261,7 +277,7 @@ func (c *Commands) DeactivateProject(ctx context.Context, projectID string, reso
}
func (c *Commands) ReactivateProject(ctx context.Context, projectID string, resourceOwner string) (*domain.ObjectDetails, error) {
if projectID == "" || resourceOwner == "" {
if projectID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-3ihsF", "Errors.Project.ProjectIDMissing")
}
@@ -269,7 +285,7 @@ func (c *Commands) ReactivateProject(ctx context.Context, projectID string, reso
return c.reactivateProjectOld(ctx, projectID, resourceOwner)
}
projectAgg, state, err := c.projectAggregateByID(ctx, projectID, resourceOwner)
projectAgg, state, err := c.projectAggregateByID(ctx, projectID)
if err != nil {
return nil, err
}
@@ -280,6 +296,9 @@ func (c *Commands) ReactivateProject(ctx context.Context, projectID string, reso
if state != domain.ProjectStateInactive {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-5M9bs", "Errors.Project.NotInactive")
}
if err := c.checkPermissionUpdateProject(ctx, projectAgg.ResourceOwner, projectAgg.ID); err != nil {
return nil, err
}
pushedEvents, err := c.eventstore.Push(ctx, project.NewProjectReactivatedEvent(ctx, projectAgg))
if err != nil {
@@ -293,6 +312,7 @@ func (c *Commands) ReactivateProject(ctx context.Context, projectID string, reso
}, nil
}
// Deprecated: use commands.DeleteProject
func (c *Commands) RemoveProject(ctx context.Context, projectID, resourceOwner string, cascadingUserGrantIDs ...string) (*domain.ObjectDetails, error) {
if projectID == "" || resourceOwner == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-66hM9", "Errors.Project.ProjectIDMissing")
@@ -316,15 +336,18 @@ func (c *Commands) RemoveProject(ctx context.Context, projectID, resourceOwner s
uniqueConstraints[i] = project.NewRemoveSAMLConfigEntityIDUniqueConstraint(entityID.EntityID)
}
projectAgg := ProjectAggregateFromWriteModel(&existingProject.WriteModel)
events := []eventstore.Command{
project.NewProjectRemovedEvent(ctx, projectAgg, existingProject.Name, uniqueConstraints),
project.NewProjectRemovedEvent(ctx,
ProjectAggregateFromWriteModelWithCTX(ctx, &existingProject.WriteModel),
existingProject.Name,
uniqueConstraints,
),
}
for _, grantID := range cascadingUserGrantIDs {
event, _, err := c.removeUserGrant(ctx, grantID, "", true)
if err != nil {
logging.LogWithFields("COMMAND-b8Djf", "usergrantid", grantID).WithError(err).Warn("could not cascade remove user grant")
logging.WithFields("id", "COMMAND-b8Djf", "usergrantid", grantID).WithError(err).Warn("could not cascade remove user grant")
continue
}
events = append(events, event)
@@ -341,6 +364,53 @@ func (c *Commands) RemoveProject(ctx context.Context, projectID, resourceOwner s
return writeModelToObjectDetails(&existingProject.WriteModel), nil
}
func (c *Commands) DeleteProject(ctx context.Context, id, resourceOwner string, cascadingUserGrantIDs ...string) (time.Time, error) {
if id == "" {
return time.Time{}, zerrors.ThrowInvalidArgument(nil, "COMMAND-obqos2l3no", "Errors.IDMissing")
}
existing, err := c.getProjectWriteModelByID(ctx, id, resourceOwner)
if err != nil {
return time.Time{}, err
}
if !isProjectStateExists(existing.State) {
return existing.WriteModel.ChangeDate, nil
}
if err := c.checkPermissionDeleteProject(ctx, existing.ResourceOwner, existing.AggregateID); err != nil {
return time.Time{}, err
}
samlEntityIDsAgg, err := c.getSAMLEntityIdsWriteModelByProjectID(ctx, id, resourceOwner)
if err != nil {
return time.Time{}, err
}
uniqueConstraints := make([]*eventstore.UniqueConstraint, len(samlEntityIDsAgg.EntityIDs))
for i, entityID := range samlEntityIDsAgg.EntityIDs {
uniqueConstraints[i] = project.NewRemoveSAMLConfigEntityIDUniqueConstraint(entityID.EntityID)
}
events := []eventstore.Command{
project.NewProjectRemovedEvent(ctx,
ProjectAggregateFromWriteModelWithCTX(ctx, &existing.WriteModel),
existing.Name,
uniqueConstraints,
),
}
for _, grantID := range cascadingUserGrantIDs {
event, _, err := c.removeUserGrant(ctx, grantID, "", true)
if err != nil {
logging.WithFields("id", "COMMAND-b8Djf", "usergrantid", grantID).WithError(err).Warn("could not cascade remove user grant")
continue
}
events = append(events, event)
}
if err := c.pushAppendAndReduce(ctx, existing, events...); err != nil {
return time.Time{}, err
}
return existing.WriteModel.ChangeDate, nil
}
func (c *Commands) getProjectWriteModelByID(ctx context.Context, projectID, resourceOwner string) (_ *ProjectWriteModel, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
+2 -2
View File
@@ -79,7 +79,7 @@ func (c *Commands) AddAPIApplicationWithID(ctx context.Context, apiApp *domain.A
return nil, zerrors.ThrowPreconditionFailed(nil, "PROJECT-mabu12", "Errors.Project.App.AlreadyExisting")
}
if err := c.checkProjectExists(ctx, apiApp.AggregateID, resourceOwner); err != nil {
if _, err := c.checkProjectExists(ctx, apiApp.AggregateID, resourceOwner); err != nil {
return nil, err
}
return c.addAPIApplicationWithID(ctx, apiApp, resourceOwner, appID)
@@ -90,7 +90,7 @@ func (c *Commands) AddAPIApplication(ctx context.Context, apiApp *domain.APIApp,
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-5m9E", "Errors.Project.App.Invalid")
}
if err := c.checkProjectExists(ctx, apiApp.AggregateID, resourceOwner); err != nil {
if _, err := c.checkProjectExists(ctx, apiApp.AggregateID, resourceOwner); err != nil {
return nil, err
}
if !apiApp.IsValid() {
+2 -2
View File
@@ -132,7 +132,7 @@ func (c *Commands) AddOIDCApplicationWithID(ctx context.Context, oidcApp *domain
return nil, zerrors.ThrowPreconditionFailed(nil, "PROJECT-lxowmp", "Errors.Project.App.AlreadyExisting")
}
if err := c.checkProjectExists(ctx, oidcApp.AggregateID, resourceOwner); err != nil {
if _, err := c.checkProjectExists(ctx, oidcApp.AggregateID, resourceOwner); err != nil {
return nil, err
}
return c.addOIDCApplicationWithID(ctx, oidcApp, resourceOwner, appID)
@@ -142,7 +142,7 @@ func (c *Commands) AddOIDCApplication(ctx context.Context, oidcApp *domain.OIDCA
if oidcApp == nil || oidcApp.AggregateID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-34Fm0", "Errors.Project.App.Invalid")
}
if err := c.checkProjectExists(ctx, oidcApp.AggregateID, resourceOwner); err != nil {
if _, err := c.checkProjectExists(ctx, oidcApp.AggregateID, resourceOwner); err != nil {
return nil, err
}
if oidcApp.AppName == "" || !oidcApp.IsValid() {
+1 -1
View File
@@ -16,7 +16,7 @@ func (c *Commands) AddSAMLApplication(ctx context.Context, application *domain.S
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-35Fn0", "Errors.Project.App.Invalid")
}
if err := c.checkProjectExists(ctx, application.AggregateID, resourceOwner); err != nil {
if _, err := c.checkProjectExists(ctx, application.AggregateID, resourceOwner); err != nil {
return nil, err
}
addedApplication := NewSAMLApplicationWriteModel(application.AggregateID, resourceOwner)
+184 -92
View File
@@ -9,6 +9,7 @@ import (
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/feature"
"github.com/zitadel/zitadel/internal/repository/org"
"github.com/zitadel/zitadel/internal/repository/project"
@@ -16,69 +17,107 @@ import (
"github.com/zitadel/zitadel/internal/zerrors"
)
func (c *Commands) AddProjectGrantWithID(ctx context.Context, grant *domain.ProjectGrant, grantID string, resourceOwner string) (_ *domain.ProjectGrant, err error) {
type AddProjectGrant struct {
es_models.ObjectRoot
GrantID string
GrantedOrgID string
RoleKeys []string
}
func (p *AddProjectGrant) IsValid() error {
if p.AggregateID == "" {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-FYRnWEzBzV", "Errors.Project.Grant.Invalid")
}
if p.GrantedOrgID == "" {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-PPhHpWGRAE", "Errors.Project.Grant.Invalid")
}
return nil
}
func (c *Commands) AddProjectGrant(ctx context.Context, grant *AddProjectGrant) (_ *domain.ObjectDetails, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
return c.addProjectGrantWithID(ctx, grant, grantID, resourceOwner)
if err := grant.IsValid(); err != nil {
return nil, err
}
if grant.GrantID == "" {
grant.GrantID, err = c.idGenerator.Next()
if err != nil {
return nil, err
}
}
projectResourceOwner, err := c.checkProjectGrantPreCondition(ctx, grant.AggregateID, grant.GrantedOrgID, grant.ResourceOwner, grant.RoleKeys)
if err != nil {
return nil, err
}
// if there is no resourceowner provided then use the resourceowner of the project
if grant.ResourceOwner == "" {
grant.ResourceOwner = projectResourceOwner
}
if err := c.checkPermissionWriteProjectGrant(ctx, grant.ResourceOwner, grant.GrantID); err != nil {
return nil, err
}
wm := NewProjectGrantWriteModel(grant.GrantID, grant.AggregateID, grant.ResourceOwner)
// error if provided resourceowner is not equal to the resourceowner of the project or the project grant is for the same organization
if projectResourceOwner != wm.ResourceOwner || wm.ResourceOwner == grant.GrantedOrgID {
return nil, zerrors.ThrowPreconditionFailed(nil, "PROJECT-ckUpbvboAH", "Errors.Project.Grant.Invalid")
}
if err := c.pushAppendAndReduce(ctx,
wm,
project.NewGrantAddedEvent(ctx,
ProjectAggregateFromWriteModelWithCTX(ctx, &wm.WriteModel),
grant.GrantID,
grant.GrantedOrgID,
grant.RoleKeys),
); err != nil {
return nil, err
}
return writeModelToObjectDetails(&wm.WriteModel), nil
}
func (c *Commands) AddProjectGrant(ctx context.Context, grant *domain.ProjectGrant, resourceOwner string) (_ *domain.ProjectGrant, err error) {
if !grant.IsValid() {
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-3b8fs", "Errors.Project.Grant.Invalid")
}
err = c.checkProjectGrantPreCondition(ctx, grant, resourceOwner)
if err != nil {
return nil, err
}
type ChangeProjectGrant struct {
es_models.ObjectRoot
grantID, err := c.idGenerator.Next()
if err != nil {
return nil, err
}
return c.addProjectGrantWithID(ctx, grant, grantID, resourceOwner)
GrantID string
RoleKeys []string
}
func (c *Commands) addProjectGrantWithID(ctx context.Context, grant *domain.ProjectGrant, grantID string, resourceOwner string) (_ *domain.ProjectGrant, err error) {
grant.GrantID = grantID
addedGrant := NewProjectGrantWriteModel(grant.GrantID, grant.AggregateID, resourceOwner)
projectAgg := ProjectAggregateFromWriteModel(&addedGrant.WriteModel)
pushedEvents, err := c.eventstore.Push(
ctx,
project.NewGrantAddedEvent(ctx, projectAgg, grant.GrantID, grant.GrantedOrgID, grant.RoleKeys))
if err != nil {
return nil, err
}
err = AppendAndReduce(addedGrant, pushedEvents...)
if err != nil {
return nil, err
}
return projectGrantWriteModelToProjectGrant(addedGrant), nil
}
func (c *Commands) ChangeProjectGrant(ctx context.Context, grant *domain.ProjectGrant, resourceOwner string, cascadeUserGrantIDs ...string) (_ *domain.ProjectGrant, err error) {
func (c *Commands) ChangeProjectGrant(ctx context.Context, grant *ChangeProjectGrant, cascadeUserGrantIDs ...string) (_ *domain.ObjectDetails, err error) {
if grant.GrantID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-1j83s", "Errors.IDMissing")
}
existingGrant, err := c.projectGrantWriteModelByID(ctx, grant.GrantID, grant.AggregateID, resourceOwner)
existingGrant, err := c.projectGrantWriteModelByID(ctx, grant.GrantID, grant.AggregateID, grant.ResourceOwner)
if err != nil {
return nil, err
}
grant.GrantedOrgID = existingGrant.GrantedOrgID
err = c.checkProjectGrantPreCondition(ctx, grant, resourceOwner)
if err := c.checkPermissionWriteProjectGrant(ctx, existingGrant.ResourceOwner, existingGrant.GrantID); err != nil {
return nil, err
}
projectResourceOwner, err := c.checkProjectGrantPreCondition(ctx, existingGrant.AggregateID, existingGrant.GrantedOrgID, existingGrant.ResourceOwner, grant.RoleKeys)
if err != nil {
return nil, err
}
projectAgg := ProjectAggregateFromWriteModel(&existingGrant.WriteModel)
// error if provided resourceowner is not equal to the resourceowner of the project
if existingGrant.ResourceOwner != projectResourceOwner {
return nil, zerrors.ThrowPreconditionFailed(nil, "PROJECT-q1BhA68RBC", "Errors.Project.Grant.Invalid")
}
// return if there are no changes to the project grant roles
if reflect.DeepEqual(existingGrant.RoleKeys, grant.RoleKeys) {
return nil, zerrors.ThrowPreconditionFailed(nil, "PROJECT-0o0pL", "Errors.NoChangesFoundc")
return writeModelToObjectDetails(&existingGrant.WriteModel), nil
}
events := []eventstore.Command{
project.NewGrantChangedEvent(ctx, projectAgg, grant.GrantID, grant.RoleKeys),
project.NewGrantChangedEvent(ctx,
ProjectAggregateFromWriteModelWithCTX(ctx, &existingGrant.WriteModel),
existingGrant.GrantID,
grant.RoleKeys,
),
}
removedRoles := domain.GetRemovedRoles(existingGrant.RoleKeys, grant.RoleKeys)
@@ -91,7 +130,7 @@ func (c *Commands) ChangeProjectGrant(ctx context.Context, grant *domain.Project
if err != nil {
return nil, err
}
return projectGrantWriteModelToProjectGrant(existingGrant), nil
return writeModelToObjectDetails(&existingGrant.WriteModel), nil
}
for _, userGrantID := range cascadeUserGrantIDs {
@@ -109,7 +148,7 @@ func (c *Commands) ChangeProjectGrant(ctx context.Context, grant *domain.Project
if err != nil {
return nil, err
}
return projectGrantWriteModelToProjectGrant(existingGrant), nil
return writeModelToObjectDetails(&existingGrant.WriteModel), nil
}
func (c *Commands) removeRoleFromProjectGrant(ctx context.Context, projectAgg *eventstore.Aggregate, projectID, projectGrantID, roleKey string, cascade bool) (_ eventstore.Command, _ *ProjectGrantWriteModel, err error) {
@@ -147,7 +186,7 @@ func (c *Commands) DeactivateProjectGrant(ctx context.Context, projectID, grantI
return details, zerrors.ThrowInvalidArgument(nil, "PROJECT-p0s4V", "Errors.IDMissing")
}
err = c.checkProjectExists(ctx, projectID, resourceOwner)
projectResourceOwner, err := c.checkProjectExists(ctx, projectID, resourceOwner)
if err != nil {
return nil, err
}
@@ -156,12 +195,27 @@ func (c *Commands) DeactivateProjectGrant(ctx context.Context, projectID, grantI
if err != nil {
return details, err
}
// error if provided resourceowner is not equal to the resourceowner of the project
if projectResourceOwner != existingGrant.ResourceOwner {
return nil, zerrors.ThrowPreconditionFailed(nil, "PROJECT-0l10S9OmZV", "Errors.Project.Grant.Invalid")
}
// return if project grant is already inactive
if existingGrant.State == domain.ProjectGrantStateInactive {
return writeModelToObjectDetails(&existingGrant.WriteModel), nil
}
// error if project grant is neither active nor inactive
if existingGrant.State != domain.ProjectGrantStateActive {
return details, zerrors.ThrowPreconditionFailed(nil, "PROJECT-47fu8", "Errors.Project.Grant.NotActive")
}
projectAgg := ProjectAggregateFromWriteModel(&existingGrant.WriteModel)
pushedEvents, err := c.eventstore.Push(ctx, project.NewGrantDeactivateEvent(ctx, projectAgg, grantID))
if err := c.checkPermissionWriteProjectGrant(ctx, existingGrant.ResourceOwner, existingGrant.GrantID); err != nil {
return nil, err
}
pushedEvents, err := c.eventstore.Push(ctx,
project.NewGrantDeactivateEvent(ctx,
ProjectAggregateFromWriteModelWithCTX(ctx, &existingGrant.WriteModel),
grantID,
),
)
if err != nil {
return nil, err
}
@@ -177,7 +231,7 @@ func (c *Commands) ReactivateProjectGrant(ctx context.Context, projectID, grantI
return details, zerrors.ThrowInvalidArgument(nil, "PROJECT-p0s4V", "Errors.IDMissing")
}
err = c.checkProjectExists(ctx, projectID, resourceOwner)
projectResourceOwner, err := c.checkProjectExists(ctx, projectID, resourceOwner)
if err != nil {
return nil, err
}
@@ -186,11 +240,27 @@ func (c *Commands) ReactivateProjectGrant(ctx context.Context, projectID, grantI
if err != nil {
return details, err
}
// error if provided resourceowner is not equal to the resourceowner of the project
if projectResourceOwner != existingGrant.ResourceOwner {
return nil, zerrors.ThrowPreconditionFailed(nil, "PROJECT-byscAarAST", "Errors.Project.Grant.Invalid")
}
// return if project grant is already active
if existingGrant.State == domain.ProjectGrantStateActive {
return writeModelToObjectDetails(&existingGrant.WriteModel), nil
}
// error if project grant is neither active nor inactive
if existingGrant.State != domain.ProjectGrantStateInactive {
return details, zerrors.ThrowPreconditionFailed(nil, "PROJECT-47fu8", "Errors.Project.Grant.NotInactive")
}
projectAgg := ProjectAggregateFromWriteModel(&existingGrant.WriteModel)
pushedEvents, err := c.eventstore.Push(ctx, project.NewGrantReactivatedEvent(ctx, projectAgg, grantID))
if err := c.checkPermissionWriteProjectGrant(ctx, existingGrant.ResourceOwner, existingGrant.GrantID); err != nil {
return nil, err
}
pushedEvents, err := c.eventstore.Push(ctx,
project.NewGrantReactivatedEvent(ctx,
ProjectAggregateFromWriteModelWithCTX(ctx, &existingGrant.WriteModel),
grantID,
),
)
if err != nil {
return nil, err
}
@@ -209,9 +279,20 @@ func (c *Commands) RemoveProjectGrant(ctx context.Context, projectID, grantID, r
if err != nil {
return details, err
}
// return if project grant does not exist, or was removed already
if !existingGrant.State.Exists() {
return writeModelToObjectDetails(&existingGrant.WriteModel), nil
}
if err := c.checkPermissionDeleteProjectGrant(ctx, existingGrant.ResourceOwner, existingGrant.GrantID); err != nil {
return nil, err
}
events := make([]eventstore.Command, 0)
projectAgg := ProjectAggregateFromWriteModel(&existingGrant.WriteModel)
events = append(events, project.NewGrantRemovedEvent(ctx, projectAgg, grantID, existingGrant.GrantedOrgID))
events = append(events, project.NewGrantRemovedEvent(ctx,
ProjectAggregateFromWriteModelWithCTX(ctx, &existingGrant.WriteModel),
grantID,
existingGrant.GrantedOrgID,
),
)
for _, userGrantID := range cascadeUserGrantIDs {
event, _, err := c.removeUserGrant(ctx, userGrantID, "", true)
@@ -232,6 +313,10 @@ func (c *Commands) RemoveProjectGrant(ctx context.Context, projectID, grantID, r
return writeModelToObjectDetails(&existingGrant.WriteModel), nil
}
func (c *Commands) checkPermissionDeleteProjectGrant(ctx context.Context, resourceOwner, projectGrantID string) error {
return c.checkPermission(ctx, domain.PermissionProjectGrantDelete, resourceOwner, projectGrantID)
}
func (c *Commands) projectGrantWriteModelByID(ctx context.Context, grantID, projectID, resourceOwner string) (member *ProjectGrantWriteModel, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
@@ -249,55 +334,61 @@ func (c *Commands) projectGrantWriteModelByID(ctx context.Context, grantID, proj
return writeModel, nil
}
func (c *Commands) checkProjectGrantPreCondition(ctx context.Context, projectGrant *domain.ProjectGrant, resourceOwner string) error {
func (c *Commands) checkProjectGrantPreCondition(ctx context.Context, projectID, grantedOrgID, resourceOwner string, roles []string) (string, error) {
if !authz.GetFeatures(ctx).ShouldUseImprovedPerformance(feature.ImprovedPerformanceTypeProjectGrant) {
return c.checkProjectGrantPreConditionOld(ctx, projectGrant, resourceOwner)
return c.checkProjectGrantPreConditionOld(ctx, projectID, grantedOrgID, resourceOwner, roles)
}
existingRoleKeys, err := c.searchProjectGrantState(ctx, projectGrant.AggregateID, projectGrant.GrantedOrgID, resourceOwner)
projectResourceOwner, existingRoleKeys, err := c.searchProjectGrantState(ctx, projectID, grantedOrgID, resourceOwner)
if err != nil {
return err
return "", err
}
if projectGrant.HasInvalidRoles(existingRoleKeys) {
return zerrors.ThrowPreconditionFailed(err, "COMMAND-6m9gd", "Errors.Project.Role.NotFound")
if domain.HasInvalidRoles(existingRoleKeys, roles) {
return "", zerrors.ThrowPreconditionFailed(err, "COMMAND-6m9gd", "Errors.Project.Role.NotFound")
}
return nil
return projectResourceOwner, nil
}
func (c *Commands) searchProjectGrantState(ctx context.Context, projectID, grantedOrgID, resourceOwner string) (existingRoleKeys []string, err error) {
func (c *Commands) searchProjectGrantState(ctx context.Context, projectID, grantedOrgID, resourceOwner string) (_ string, existingRoleKeys []string, err error) {
projectStateQuery := map[eventstore.FieldType]any{
eventstore.FieldTypeAggregateType: project.AggregateType,
eventstore.FieldTypeAggregateID: projectID,
eventstore.FieldTypeFieldName: project.ProjectStateSearchField,
eventstore.FieldTypeObjectType: project.ProjectSearchType,
}
grantedOrgQuery := map[eventstore.FieldType]any{
eventstore.FieldTypeAggregateType: org.AggregateType,
eventstore.FieldTypeAggregateID: grantedOrgID,
eventstore.FieldTypeFieldName: org.OrgStateSearchField,
eventstore.FieldTypeObjectType: org.OrgSearchType,
}
roleQuery := map[eventstore.FieldType]any{
eventstore.FieldTypeAggregateType: project.AggregateType,
eventstore.FieldTypeAggregateID: projectID,
eventstore.FieldTypeFieldName: project.ProjectRoleKeySearchField,
eventstore.FieldTypeObjectType: project.ProjectRoleSearchType,
}
// as resourceowner is not always provided, it has to be separately
if resourceOwner != "" {
projectStateQuery[eventstore.FieldTypeResourceOwner] = resourceOwner
roleQuery[eventstore.FieldTypeResourceOwner] = resourceOwner
}
results, err := c.eventstore.Search(
ctx,
// project state query
map[eventstore.FieldType]any{
eventstore.FieldTypeResourceOwner: resourceOwner,
eventstore.FieldTypeAggregateType: project.AggregateType,
eventstore.FieldTypeAggregateID: projectID,
eventstore.FieldTypeFieldName: project.ProjectStateSearchField,
eventstore.FieldTypeObjectType: project.ProjectSearchType,
},
// granted org query
map[eventstore.FieldType]any{
eventstore.FieldTypeAggregateType: org.AggregateType,
eventstore.FieldTypeAggregateID: grantedOrgID,
eventstore.FieldTypeFieldName: org.OrgStateSearchField,
eventstore.FieldTypeObjectType: org.OrgSearchType,
},
// role query
map[eventstore.FieldType]any{
eventstore.FieldTypeResourceOwner: resourceOwner,
eventstore.FieldTypeAggregateType: project.AggregateType,
eventstore.FieldTypeAggregateID: projectID,
eventstore.FieldTypeFieldName: project.ProjectRoleKeySearchField,
eventstore.FieldTypeObjectType: project.ProjectRoleSearchType,
},
projectStateQuery,
grantedOrgQuery,
roleQuery,
)
if err != nil {
return nil, err
return "", nil, err
}
var (
existsProject bool
existsGrantedOrg bool
existsProject bool
existingProjectResourceOwner string
existsGrantedOrg bool
)
for _, result := range results {
@@ -306,31 +397,32 @@ func (c *Commands) searchProjectGrantState(ctx context.Context, projectID, grant
var role string
err := result.Value.Unmarshal(&role)
if err != nil {
return nil, err
return "", nil, err
}
existingRoleKeys = append(existingRoleKeys, role)
case org.OrgSearchType:
var state domain.OrgState
err := result.Value.Unmarshal(&state)
if err != nil {
return nil, err
return "", nil, err
}
existsGrantedOrg = state.Valid() && state != domain.OrgStateRemoved
case project.ProjectSearchType:
var state domain.ProjectState
err := result.Value.Unmarshal(&state)
if err != nil {
return nil, err
return "", nil, err
}
existsProject = state.Valid() && state != domain.ProjectStateRemoved
existingProjectResourceOwner = result.Aggregate.ResourceOwner
}
}
if !existsProject {
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-m9gsd", "Errors.Project.NotFound")
return "", nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-m9gsd", "Errors.Project.NotFound")
}
if !existsGrantedOrg {
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-3m9gg", "Errors.Org.NotFound")
return "", nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-3m9gg", "Errors.Org.NotFound")
}
return existingRoleKeys, nil
return existingProjectResourceOwner, existingRoleKeys, nil
}
+6 -2
View File
@@ -133,14 +133,18 @@ func (wm *ProjectGrantPreConditionReadModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *project.ProjectAddedEvent:
if e.Aggregate().ResourceOwner != wm.ResourceOwner {
if wm.ResourceOwner == "" {
wm.ResourceOwner = e.Aggregate().ResourceOwner
}
if wm.ResourceOwner != e.Aggregate().ResourceOwner {
continue
}
wm.ProjectExists = true
case *project.ProjectRemovedEvent:
if e.Aggregate().ResourceOwner != wm.ResourceOwner {
if wm.ResourceOwner != e.Aggregate().ResourceOwner {
continue
}
wm.ResourceOwner = ""
wm.ProjectExists = false
case *project.RoleAddedEvent:
if e.Aggregate().ResourceOwner != wm.ResourceOwner {
File diff suppressed because it is too large Load Diff
+20 -21
View File
@@ -88,40 +88,35 @@ func (wm *ProjectWriteModel) Query() *eventstore.SearchQueryBuilder {
func (wm *ProjectWriteModel) NewChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
name string,
name *string,
projectRoleAssertion,
projectRoleCheck,
hasProjectCheck bool,
privateLabelingSetting domain.PrivateLabelingSetting,
) (*project.ProjectChangeEvent, bool, error) {
hasProjectCheck *bool,
privateLabelingSetting *domain.PrivateLabelingSetting,
) *project.ProjectChangeEvent {
changes := make([]project.ProjectChanges, 0)
var err error
oldName := ""
if wm.Name != name {
if name != nil && wm.Name != *name {
oldName = wm.Name
changes = append(changes, project.ChangeName(name))
changes = append(changes, project.ChangeName(*name))
}
if wm.ProjectRoleAssertion != projectRoleAssertion {
changes = append(changes, project.ChangeProjectRoleAssertion(projectRoleAssertion))
if projectRoleAssertion != nil && wm.ProjectRoleAssertion != *projectRoleAssertion {
changes = append(changes, project.ChangeProjectRoleAssertion(*projectRoleAssertion))
}
if wm.ProjectRoleCheck != projectRoleCheck {
changes = append(changes, project.ChangeProjectRoleCheck(projectRoleCheck))
if projectRoleCheck != nil && wm.ProjectRoleCheck != *projectRoleCheck {
changes = append(changes, project.ChangeProjectRoleCheck(*projectRoleCheck))
}
if wm.HasProjectCheck != hasProjectCheck {
changes = append(changes, project.ChangeHasProjectCheck(hasProjectCheck))
if hasProjectCheck != nil && wm.HasProjectCheck != *hasProjectCheck {
changes = append(changes, project.ChangeHasProjectCheck(*hasProjectCheck))
}
if wm.PrivateLabelingSetting != privateLabelingSetting {
changes = append(changes, project.ChangePrivateLabelingSetting(privateLabelingSetting))
if privateLabelingSetting != nil && wm.PrivateLabelingSetting != *privateLabelingSetting {
changes = append(changes, project.ChangePrivateLabelingSetting(*privateLabelingSetting))
}
if len(changes) == 0 {
return nil, false, nil
return nil
}
changeEvent, err := project.NewProjectChangeEvent(ctx, aggregate, oldName, changes)
if err != nil {
return nil, false, err
}
return changeEvent, true, nil
return project.NewProjectChangeEvent(ctx, aggregate, oldName, changes)
}
func isProjectStateExists(state domain.ProjectState) bool {
@@ -132,6 +127,10 @@ func ProjectAggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggre
return eventstore.AggregateFromWriteModel(wm, project.AggregateType, project.AggregateVersion)
}
func ProjectAggregateFromWriteModelWithCTX(ctx context.Context, wm *eventstore.WriteModel) *eventstore.Aggregate {
return project.AggregateFromWriteModel(ctx, wm)
}
func hasProjectState(check domain.ProjectState, states ...domain.ProjectState) bool {
for _, state := range states {
if check == state {
+18 -12
View File
@@ -9,18 +9,18 @@ import (
"github.com/zitadel/zitadel/internal/zerrors"
)
func (c *Commands) checkProjectExistsOld(ctx context.Context, projectID, resourceOwner string) (err error) {
func (c *Commands) checkProjectExistsOld(ctx context.Context, projectID, resourceOwner string) (_ string, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
projectWriteModel, err := c.getProjectWriteModelByID(ctx, projectID, resourceOwner)
if err != nil {
return err
return "", err
}
if !isProjectStateExists(projectWriteModel.State) {
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-EbFMN", "Errors.Project.NotFound")
return "", zerrors.ThrowPreconditionFailed(nil, "COMMAND-EbFMN", "Errors.Project.NotFound")
}
return nil
return projectWriteModel.ResourceOwner, nil
}
func (c *Commands) deactivateProjectOld(ctx context.Context, projectID string, resourceOwner string) (*domain.ObjectDetails, error) {
@@ -34,6 +34,9 @@ func (c *Commands) deactivateProjectOld(ctx context.Context, projectID string, r
if existingProject.State != domain.ProjectStateActive {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-mki55", "Errors.Project.NotActive")
}
if err := c.checkPermissionUpdateProject(ctx, existingProject.ResourceOwner, existingProject.AggregateID); err != nil {
return nil, err
}
//nolint: contextcheck
projectAgg := ProjectAggregateFromWriteModel(&existingProject.WriteModel)
@@ -59,6 +62,9 @@ func (c *Commands) reactivateProjectOld(ctx context.Context, projectID string, r
if existingProject.State != domain.ProjectStateInactive {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-5M9bs", "Errors.Project.NotInactive")
}
if err := c.checkPermissionUpdateProject(ctx, existingProject.ResourceOwner, existingProject.AggregateID); err != nil {
return nil, err
}
//nolint: contextcheck
projectAgg := ProjectAggregateFromWriteModel(&existingProject.WriteModel)
@@ -73,20 +79,20 @@ func (c *Commands) reactivateProjectOld(ctx context.Context, projectID string, r
return writeModelToObjectDetails(&existingProject.WriteModel), nil
}
func (c *Commands) checkProjectGrantPreConditionOld(ctx context.Context, projectGrant *domain.ProjectGrant, resourceOwner string) error {
preConditions := NewProjectGrantPreConditionReadModel(projectGrant.AggregateID, projectGrant.GrantedOrgID, resourceOwner)
func (c *Commands) checkProjectGrantPreConditionOld(ctx context.Context, projectID, grantedOrgID, resourceOwner string, roles []string) (string, error) {
preConditions := NewProjectGrantPreConditionReadModel(projectID, grantedOrgID, resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, preConditions)
if err != nil {
return err
return "", err
}
if !preConditions.ProjectExists {
return zerrors.ThrowPreconditionFailed(err, "COMMAND-m9gsd", "Errors.Project.NotFound")
return "", zerrors.ThrowPreconditionFailed(err, "COMMAND-m9gsd", "Errors.Project.NotFound")
}
if !preConditions.GrantedOrgExists {
return zerrors.ThrowPreconditionFailed(err, "COMMAND-3m9gg", "Errors.Org.NotFound")
return "", zerrors.ThrowPreconditionFailed(err, "COMMAND-3m9gg", "Errors.Org.NotFound")
}
if projectGrant.HasInvalidRoles(preConditions.ExistingRoleKeys) {
return zerrors.ThrowPreconditionFailed(err, "COMMAND-6m9gd", "Errors.Project.Role.NotFound")
if domain.HasInvalidRoles(preConditions.ExistingRoleKeys, roles) {
return "", zerrors.ThrowPreconditionFailed(err, "COMMAND-6m9gd", "Errors.Project.Role.NotFound")
}
return nil
return preConditions.ResourceOwner, nil
}
+91 -47
View File
@@ -7,22 +7,45 @@ import (
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/repository/project"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
"github.com/zitadel/zitadel/internal/zerrors"
)
func (c *Commands) AddProjectRole(ctx context.Context, projectRole *domain.ProjectRole, resourceOwner string) (_ *domain.ProjectRole, err error) {
type AddProjectRole struct {
models.ObjectRoot
Key string
DisplayName string
Group string
}
func (p *AddProjectRole) IsValid() bool {
return p.AggregateID != "" && p.Key != ""
}
func (c *Commands) AddProjectRole(ctx context.Context, projectRole *AddProjectRole) (_ *domain.ObjectDetails, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
err = c.checkProjectExists(ctx, projectRole.AggregateID, resourceOwner)
projectResourceOwner, err := c.checkProjectExists(ctx, projectRole.AggregateID, projectRole.ResourceOwner)
if err != nil {
return nil, err
}
if projectRole.ResourceOwner == "" {
projectRole.ResourceOwner = projectResourceOwner
}
if err := c.checkPermissionWriteProjectRole(ctx, projectRole.ResourceOwner, projectRole.Key); err != nil {
return nil, err
}
roleWriteModel := NewProjectRoleWriteModelWithKey(projectRole.Key, projectRole.AggregateID, resourceOwner)
projectAgg := ProjectAggregateFromWriteModel(&roleWriteModel.WriteModel)
roleWriteModel := NewProjectRoleWriteModelWithKey(projectRole.Key, projectRole.AggregateID, projectRole.ResourceOwner)
if roleWriteModel.ResourceOwner != projectResourceOwner {
return nil, zerrors.ThrowPreconditionFailed(nil, "PROJECT-RLB4UpqQSd", "Errors.Project.Role.Invalid")
}
projectAgg := ProjectAggregateFromWriteModelWithCTX(ctx, &roleWriteModel.WriteModel)
events, err := c.addProjectRoles(ctx, projectAgg, projectRole)
if err != nil {
return nil, err
@@ -35,37 +58,45 @@ func (c *Commands) AddProjectRole(ctx context.Context, projectRole *domain.Proje
if err != nil {
return nil, err
}
return roleWriteModelToRole(roleWriteModel), nil
return writeModelToObjectDetails(&roleWriteModel.WriteModel), nil
}
func (c *Commands) BulkAddProjectRole(ctx context.Context, projectID, resourceOwner string, projectRoles []*domain.ProjectRole) (details *domain.ObjectDetails, err error) {
err = c.checkProjectExists(ctx, projectID, resourceOwner)
func (c *Commands) checkPermissionWriteProjectRole(ctx context.Context, resourceOwner, roleKey string) error {
return c.checkPermission(ctx, domain.PermissionProjectRoleWrite, resourceOwner, roleKey)
}
func (c *Commands) BulkAddProjectRole(ctx context.Context, projectID, resourceOwner string, projectRoles []*AddProjectRole) (details *domain.ObjectDetails, err error) {
projectResourceOwner, err := c.checkProjectExists(ctx, projectID, resourceOwner)
if err != nil {
return nil, err
}
for _, projectRole := range projectRoles {
if projectRole.ResourceOwner == "" {
projectRole.ResourceOwner = projectResourceOwner
}
if err := c.checkPermissionWriteProjectRole(ctx, projectRole.ResourceOwner, projectRole.Key); err != nil {
return nil, err
}
if projectRole.ResourceOwner != projectResourceOwner {
return nil, zerrors.ThrowPreconditionFailed(nil, "PROJECT-9ZXtdaJKJJ", "Errors.Project.Role.Invalid")
}
}
roleWriteModel := NewProjectRoleWriteModel(projectID, resourceOwner)
projectAgg := ProjectAggregateFromWriteModel(&roleWriteModel.WriteModel)
projectAgg := ProjectAggregateFromWriteModelWithCTX(ctx, &roleWriteModel.WriteModel)
events, err := c.addProjectRoles(ctx, projectAgg, projectRoles...)
if err != nil {
return details, err
}
pushedEvents, err := c.eventstore.Push(ctx, events...)
if err != nil {
return nil, err
}
err = AppendAndReduce(roleWriteModel, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&roleWriteModel.WriteModel), nil
return c.pushAppendAndReduceDetails(ctx, roleWriteModel, events...)
}
func (c *Commands) addProjectRoles(ctx context.Context, projectAgg *eventstore.Aggregate, projectRoles ...*domain.ProjectRole) ([]eventstore.Command, error) {
func (c *Commands) addProjectRoles(ctx context.Context, projectAgg *eventstore.Aggregate, projectRoles ...*AddProjectRole) ([]eventstore.Command, error) {
var events []eventstore.Command
for _, projectRole := range projectRoles {
projectRole.AggregateID = projectAgg.ID
if projectRole.ResourceOwner != projectAgg.ResourceOwner {
return nil, zerrors.ThrowPreconditionFailed(nil, "PROJECT-4Q2WjlbHvc", "Errors.Project.Role.Invalid")
}
if !projectRole.IsValid() {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-4m9vS", "Errors.Project.Role.Invalid")
}
@@ -81,42 +112,55 @@ func (c *Commands) addProjectRoles(ctx context.Context, projectAgg *eventstore.A
return events, nil
}
func (c *Commands) ChangeProjectRole(ctx context.Context, projectRole *domain.ProjectRole, resourceOwner string) (_ *domain.ProjectRole, err error) {
type ChangeProjectRole struct {
models.ObjectRoot
Key string
DisplayName string
Group string
}
func (p *ChangeProjectRole) IsValid() bool {
return p.AggregateID != "" && p.Key != ""
}
func (c *Commands) ChangeProjectRole(ctx context.Context, projectRole *ChangeProjectRole) (_ *domain.ObjectDetails, err error) {
if !projectRole.IsValid() {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-2ilfW", "Errors.Project.Invalid")
}
err = c.checkProjectExists(ctx, projectRole.AggregateID, resourceOwner)
projectResourceOwner, err := c.checkProjectExists(ctx, projectRole.AggregateID, projectRole.ResourceOwner)
if err != nil {
return nil, err
}
if projectRole.ResourceOwner == "" {
projectRole.ResourceOwner = projectResourceOwner
}
if err := c.checkPermissionWriteProjectRole(ctx, projectRole.ResourceOwner, projectRole.Key); err != nil {
return nil, err
}
existingRole, err := c.getProjectRoleWriteModelByID(ctx, projectRole.Key, projectRole.AggregateID, resourceOwner)
existingRole, err := c.getProjectRoleWriteModelByID(ctx, projectRole.Key, projectRole.AggregateID, projectRole.ResourceOwner)
if err != nil {
return nil, err
}
if existingRole.State == domain.ProjectRoleStateUnspecified || existingRole.State == domain.ProjectRoleStateRemoved {
if !existingRole.State.Exists() {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-vv8M9", "Errors.Project.Role.NotExisting")
}
if existingRole.ResourceOwner != projectResourceOwner {
return nil, zerrors.ThrowPreconditionFailed(nil, "PROJECT-3MizLWveMf", "Errors.Project.Role.Invalid")
}
projectAgg := ProjectAggregateFromWriteModel(&existingRole.WriteModel)
projectAgg := ProjectAggregateFromWriteModelWithCTX(ctx, &existingRole.WriteModel)
changeEvent, changed, err := existingRole.NewProjectRoleChangedEvent(ctx, projectAgg, projectRole.Key, projectRole.DisplayName, projectRole.Group)
if err != nil {
return nil, err
}
if !changed {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-5M0cs", "Errors.NoChangesFound")
return writeModelToObjectDetails(&existingRole.WriteModel), nil
}
pushedEvents, err := c.eventstore.Push(ctx, changeEvent)
if err != nil {
return nil, err
}
err = AppendAndReduce(existingRole, pushedEvents...)
if err != nil {
return nil, err
}
return roleWriteModelToRole(existingRole), nil
return c.pushAppendAndReduceDetails(ctx, existingRole, changeEvent)
}
func (c *Commands) RemoveProjectRole(ctx context.Context, projectID, key, resourceOwner string, cascadingProjectGrantIds []string, cascadeUserGrantIDs ...string) (details *domain.ObjectDetails, err error) {
@@ -127,10 +171,14 @@ func (c *Commands) RemoveProjectRole(ctx context.Context, projectID, key, resour
if err != nil {
return details, err
}
if existingRole.State == domain.ProjectRoleStateUnspecified || existingRole.State == domain.ProjectRoleStateRemoved {
return details, zerrors.ThrowNotFound(nil, "COMMAND-m9vMf", "Errors.Project.Role.NotExisting")
// return if project role is not existing
if !existingRole.State.Exists() {
return writeModelToObjectDetails(&existingRole.WriteModel), nil
}
projectAgg := ProjectAggregateFromWriteModel(&existingRole.WriteModel)
if err := c.checkPermissionDeleteProjectRole(ctx, existingRole.ResourceOwner, existingRole.Key); err != nil {
return nil, err
}
projectAgg := ProjectAggregateFromWriteModelWithCTX(ctx, &existingRole.WriteModel)
events := []eventstore.Command{
project.NewRoleRemovedEvent(ctx, projectAgg, key),
}
@@ -153,15 +201,11 @@ func (c *Commands) RemoveProjectRole(ctx context.Context, projectID, key, resour
events = append(events, event)
}
pushedEvents, err := c.eventstore.Push(ctx, events...)
if err != nil {
return nil, err
}
err = AppendAndReduce(existingRole, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingRole.WriteModel), nil
return c.pushAppendAndReduceDetails(ctx, existingRole, events...)
}
func (c *Commands) checkPermissionDeleteProjectRole(ctx context.Context, resourceOwner, roleKey string) error {
return c.checkPermission(ctx, domain.PermissionProjectRoleDelete, resourceOwner, roleKey)
}
func (c *Commands) getProjectRoleWriteModelByID(ctx context.Context, key, projectID, resourceOwner string) (*ProjectRoleWriteModel, error) {
+4
View File
@@ -17,6 +17,10 @@ type ProjectRoleWriteModel struct {
State domain.ProjectRoleState
}
func (wm *ProjectRoleWriteModel) GetWriteModel() *eventstore.WriteModel {
return &wm.WriteModel
}
func NewProjectRoleWriteModelWithKey(key, projectID, resourceOwner string) *ProjectRoleWriteModel {
return &ProjectRoleWriteModel{
WriteModel: eventstore.WriteModel{
+145 -102
View File
@@ -16,15 +16,15 @@ import (
func TestCommandSide_AddProjectRole(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(t *testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck
}
type args struct {
ctx context.Context
role *domain.ProjectRole
resourceOwner string
ctx context.Context
role *AddProjectRole
}
type res struct {
want *domain.ProjectRole
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
@@ -36,8 +36,7 @@ func TestCommandSide_AddProjectRole(t *testing.T) {
{
name: "project not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
@@ -55,16 +54,16 @@ func TestCommandSide_AddProjectRole(t *testing.T) {
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
ctx: context.Background(),
role: &domain.ProjectRole{
role: &AddProjectRole{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
Key: "key1",
},
resourceOwner: "org1",
},
res: res{
err: zerrors.IsPreconditionFailed,
@@ -73,8 +72,7 @@ func TestCommandSide_AddProjectRole(t *testing.T) {
{
name: "invalid role, error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
@@ -85,15 +83,15 @@ func TestCommandSide_AddProjectRole(t *testing.T) {
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
ctx: context.Background(),
role: &domain.ProjectRole{
role: &AddProjectRole{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
},
resourceOwner: "org1",
},
res: res{
err: zerrors.IsErrorInvalidArgument,
@@ -102,8 +100,7 @@ func TestCommandSide_AddProjectRole(t *testing.T) {
{
name: "role key already exists, already exists error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
@@ -123,10 +120,11 @@ func TestCommandSide_AddProjectRole(t *testing.T) {
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
ctx: context.Background(),
role: &domain.ProjectRole{
role: &AddProjectRole{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
@@ -134,7 +132,6 @@ func TestCommandSide_AddProjectRole(t *testing.T) {
DisplayName: "key",
Group: "group",
},
resourceOwner: "org1",
},
res: res{
err: zerrors.IsErrorAlreadyExists,
@@ -143,8 +140,7 @@ func TestCommandSide_AddProjectRole(t *testing.T) {
{
name: "add role,ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
@@ -164,10 +160,11 @@ func TestCommandSide_AddProjectRole(t *testing.T) {
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
ctx: context.Background(),
role: &domain.ProjectRole{
role: &AddProjectRole{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
@@ -175,10 +172,41 @@ func TestCommandSide_AddProjectRole(t *testing.T) {
DisplayName: "key",
Group: "group",
},
resourceOwner: "org1",
},
res: res{
want: &domain.ProjectRole{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
{
name: "add role, resourceowner, ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"projectname1", true, true, true,
domain.PrivateLabelingSettingUnspecified,
),
),
),
expectPush(
project.NewRoleAddedEvent(
context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"key1",
"key",
"group",
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
ctx: context.Background(),
role: &AddProjectRole{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
ResourceOwner: "org1",
@@ -188,14 +216,20 @@ func TestCommandSide_AddProjectRole(t *testing.T) {
Group: "group",
},
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
checkPermission: tt.fields.checkPermission,
}
got, err := r.AddProjectRole(tt.args.ctx, tt.args.role, tt.args.resourceOwner)
got, err := r.AddProjectRole(tt.args.ctx, tt.args.role)
if tt.res.err == nil {
assert.NoError(t, err)
}
@@ -203,7 +237,7 @@ func TestCommandSide_AddProjectRole(t *testing.T) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
assertObjectDetails(t, tt.res.want, got)
}
})
}
@@ -211,11 +245,12 @@ func TestCommandSide_AddProjectRole(t *testing.T) {
func TestCommandSide_BulkAddProjectRole(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(t *testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck
}
type args struct {
ctx context.Context
roles []*domain.ProjectRole
roles []*AddProjectRole
projectID string
resourceOwner string
}
@@ -232,8 +267,7 @@ func TestCommandSide_BulkAddProjectRole(t *testing.T) {
{
name: "project not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
@@ -251,10 +285,11 @@ func TestCommandSide_BulkAddProjectRole(t *testing.T) {
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
ctx: context.Background(),
roles: []*domain.ProjectRole{
roles: []*AddProjectRole{
{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
@@ -271,8 +306,7 @@ func TestCommandSide_BulkAddProjectRole(t *testing.T) {
{
name: "invalid role, error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
@@ -283,10 +317,11 @@ func TestCommandSide_BulkAddProjectRole(t *testing.T) {
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
ctx: context.Background(),
roles: []*domain.ProjectRole{
roles: []*AddProjectRole{
{
ObjectRoot: models.ObjectRoot{},
},
@@ -304,8 +339,7 @@ func TestCommandSide_BulkAddProjectRole(t *testing.T) {
{
name: "role key already exists, already exists error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
@@ -332,16 +366,23 @@ func TestCommandSide_BulkAddProjectRole(t *testing.T) {
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
ctx: context.Background(),
roles: []*domain.ProjectRole{
roles: []*AddProjectRole{
{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
Key: "key1",
DisplayName: "key",
Group: "group",
},
{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
Key: "key2",
DisplayName: "key2",
Group: "group",
@@ -357,8 +398,7 @@ func TestCommandSide_BulkAddProjectRole(t *testing.T) {
{
name: "add roles,ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
@@ -385,16 +425,23 @@ func TestCommandSide_BulkAddProjectRole(t *testing.T) {
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
ctx: context.Background(),
roles: []*domain.ProjectRole{
roles: []*AddProjectRole{
{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
Key: "key1",
DisplayName: "key",
Group: "group",
},
{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
Key: "key2",
DisplayName: "key2",
Group: "group",
@@ -413,7 +460,8 @@ func TestCommandSide_BulkAddProjectRole(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
checkPermission: tt.fields.checkPermission,
}
got, err := r.BulkAddProjectRole(tt.args.ctx, tt.args.projectID, tt.args.resourceOwner, tt.args.roles)
if tt.res.err == nil {
@@ -431,15 +479,15 @@ func TestCommandSide_BulkAddProjectRole(t *testing.T) {
func TestCommandSide_ChangeProjectRole(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(t *testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck
}
type args struct {
ctx context.Context
role *domain.ProjectRole
resourceOwner string
ctx context.Context
role *ChangeProjectRole
}
type res struct {
want *domain.ProjectRole
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
@@ -451,18 +499,16 @@ func TestCommandSide_ChangeProjectRole(t *testing.T) {
{
name: "invalid role, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
ctx: context.Background(),
role: &domain.ProjectRole{
role: &ChangeProjectRole{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
},
resourceOwner: "org1",
},
res: res{
err: zerrors.IsErrorInvalidArgument,
@@ -471,8 +517,7 @@ func TestCommandSide_ChangeProjectRole(t *testing.T) {
{
name: "project not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
@@ -490,16 +535,16 @@ func TestCommandSide_ChangeProjectRole(t *testing.T) {
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
ctx: context.Background(),
role: &domain.ProjectRole{
role: &ChangeProjectRole{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
Key: "key1",
},
resourceOwner: "org1",
},
res: res{
err: zerrors.IsPreconditionFailed,
@@ -508,8 +553,7 @@ func TestCommandSide_ChangeProjectRole(t *testing.T) {
{
name: "role removed, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
@@ -536,10 +580,11 @@ func TestCommandSide_ChangeProjectRole(t *testing.T) {
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
ctx: context.Background(),
role: &domain.ProjectRole{
role: &ChangeProjectRole{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
@@ -547,7 +592,6 @@ func TestCommandSide_ChangeProjectRole(t *testing.T) {
DisplayName: "key",
Group: "group",
},
resourceOwner: "org1",
},
res: res{
err: zerrors.IsNotFound,
@@ -556,8 +600,7 @@ func TestCommandSide_ChangeProjectRole(t *testing.T) {
{
name: "role not changed, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
@@ -578,10 +621,11 @@ func TestCommandSide_ChangeProjectRole(t *testing.T) {
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
ctx: context.Background(),
role: &domain.ProjectRole{
role: &ChangeProjectRole{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
@@ -589,17 +633,17 @@ func TestCommandSide_ChangeProjectRole(t *testing.T) {
DisplayName: "key",
Group: "group",
},
resourceOwner: "org1",
},
res: res{
err: zerrors.IsPreconditionFailed,
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
{
name: "role changed, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
@@ -623,10 +667,11 @@ func TestCommandSide_ChangeProjectRole(t *testing.T) {
newRoleChangedEvent(context.Background(), "project1", "org1", "key1", "keychanged", "groupchanged"),
),
),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
ctx: context.Background(),
role: &domain.ProjectRole{
role: &ChangeProjectRole{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
@@ -634,17 +679,10 @@ func TestCommandSide_ChangeProjectRole(t *testing.T) {
DisplayName: "keychanged",
Group: "groupchanged",
},
resourceOwner: "org1",
},
res: res{
want: &domain.ProjectRole{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
ResourceOwner: "org1",
},
Key: "key1",
DisplayName: "keychanged",
Group: "groupchanged",
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
@@ -652,9 +690,10 @@ func TestCommandSide_ChangeProjectRole(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
checkPermission: tt.fields.checkPermission,
}
got, err := r.ChangeProjectRole(tt.args.ctx, tt.args.role, tt.args.resourceOwner)
got, err := r.ChangeProjectRole(tt.args.ctx, tt.args.role)
if tt.res.err == nil {
assert.NoError(t, err)
}
@@ -662,7 +701,7 @@ func TestCommandSide_ChangeProjectRole(t *testing.T) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
assertObjectDetails(t, tt.res.want, got)
}
})
}
@@ -670,7 +709,8 @@ func TestCommandSide_ChangeProjectRole(t *testing.T) {
func TestCommandSide_RemoveProjectRole(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(t *testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck
}
type args struct {
ctx context.Context
@@ -693,9 +733,8 @@ func TestCommandSide_RemoveProjectRole(t *testing.T) {
{
name: "invalid projectid, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
ctx: context.Background(),
@@ -709,9 +748,8 @@ func TestCommandSide_RemoveProjectRole(t *testing.T) {
{
name: "invalid key, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
ctx: context.Background(),
@@ -726,10 +764,10 @@ func TestCommandSide_RemoveProjectRole(t *testing.T) {
{
name: "role not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
ctx: context.Background(),
@@ -738,14 +776,15 @@ func TestCommandSide_RemoveProjectRole(t *testing.T) {
resourceOwner: "org1",
},
res: res{
err: zerrors.IsNotFound,
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
{
name: "role removed, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewRoleAddedEvent(context.Background(),
@@ -763,6 +802,7 @@ func TestCommandSide_RemoveProjectRole(t *testing.T) {
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
ctx: context.Background(),
@@ -771,14 +811,15 @@ func TestCommandSide_RemoveProjectRole(t *testing.T) {
resourceOwner: "org1",
},
res: res{
err: zerrors.IsNotFound,
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
{
name: "role removed, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewRoleAddedEvent(context.Background(),
@@ -796,6 +837,7 @@ func TestCommandSide_RemoveProjectRole(t *testing.T) {
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
ctx: context.Background(),
@@ -812,8 +854,7 @@ func TestCommandSide_RemoveProjectRole(t *testing.T) {
{
name: "role removed with cascadingProjectGrantids, grant not found, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewRoleAddedEvent(context.Background(),
@@ -832,6 +873,7 @@ func TestCommandSide_RemoveProjectRole(t *testing.T) {
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
ctx: context.Background(),
@@ -849,8 +891,7 @@ func TestCommandSide_RemoveProjectRole(t *testing.T) {
{
name: "role removed with cascadingProjectGrantids, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewRoleAddedEvent(context.Background(),
@@ -883,6 +924,7 @@ func TestCommandSide_RemoveProjectRole(t *testing.T) {
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
ctx: context.Background(),
@@ -900,8 +942,7 @@ func TestCommandSide_RemoveProjectRole(t *testing.T) {
{
name: "role removed with cascadingUserGrantIDs, grant not found, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewRoleAddedEvent(context.Background(),
@@ -920,6 +961,7 @@ func TestCommandSide_RemoveProjectRole(t *testing.T) {
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
ctx: context.Background(),
@@ -937,8 +979,7 @@ func TestCommandSide_RemoveProjectRole(t *testing.T) {
{
name: "role removed with cascadingUserGrantIDs, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewRoleAddedEvent(context.Background(),
@@ -969,6 +1010,7 @@ func TestCommandSide_RemoveProjectRole(t *testing.T) {
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
ctx: context.Background(),
@@ -987,7 +1029,8 @@ func TestCommandSide_RemoveProjectRole(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
checkPermission: tt.fields.checkPermission,
}
got, err := r.RemoveProjectRole(tt.args.ctx, tt.args.projectID, tt.args.key, tt.args.resourceOwner, tt.args.cascadingProjectGrantIDs, tt.args.cascadingUserGrantIDs...)
if tt.res.err == nil {
File diff suppressed because it is too large Load Diff
+9
View File
@@ -38,6 +38,15 @@ const (
PermissionOrgRead = "org.read"
PermissionIDPRead = "iam.idp.read"
PermissionOrgIDPRead = "org.idp.read"
PermissionProjectWrite = "project.write"
PermissionProjectRead = "project.read"
PermissionProjectDelete = "project.delete"
PermissionProjectGrantWrite = "project.grant.write"
PermissionProjectGrantRead = "project.grant.read"
PermissionProjectGrantDelete = "project.grant.delete"
PermissionProjectRoleWrite = "project.role.write"
PermissionProjectRoleRead = "project.role.read"
PermissionProjectRoleDelete = "project.role.delete"
)
// ProjectPermissionCheck is used as a check for preconditions dependent on application, project, user resourceowner and usergrants.
+4 -9
View File
@@ -26,17 +26,12 @@ func (s ProjectGrantState) Valid() bool {
return s > ProjectGrantStateUnspecified && s < projectGrantStateMax
}
func (p *ProjectGrant) IsValid() bool {
return p.GrantedOrgID != ""
func (s ProjectGrantState) Exists() bool {
return s != ProjectGrantStateUnspecified && s != ProjectGrantStateRemoved
}
func (g *ProjectGrant) HasInvalidRoles(validRoles []string) bool {
for _, roleKey := range g.RoleKeys {
if !containsRoleKey(roleKey, validRoles) {
return true
}
}
return false
func (p *ProjectGrant) IsValid() bool {
return p.GrantedOrgID != ""
}
func GetRemovedRoles(existingRoles, newRoles []string) []string {
+11 -2
View File
@@ -20,14 +20,23 @@ const (
ProjectRoleStateRemoved
)
func NewProjectRole(projectID, key string) *ProjectRole {
return &ProjectRole{ObjectRoot: models.ObjectRoot{AggregateID: projectID}, Key: key}
func (s ProjectRoleState) Exists() bool {
return s != ProjectRoleStateUnspecified && s != ProjectRoleStateRemoved
}
func (p *ProjectRole) IsValid() bool {
return p.AggregateID != "" && p.Key != ""
}
func HasInvalidRoles(validRoles, roles []string) bool {
for _, roleKey := range roles {
if !containsRoleKey(roleKey, validRoles) {
return true
}
}
return false
}
func containsRoleKey(roleKey string, validRoles []string) bool {
for _, validRole := range validRoles {
if roleKey == validRole {
+100 -5
View File
@@ -34,6 +34,7 @@ import (
oidc_pb_v2beta "github.com/zitadel/zitadel/pkg/grpc/oidc/v2beta"
"github.com/zitadel/zitadel/pkg/grpc/org/v2"
org_v2beta "github.com/zitadel/zitadel/pkg/grpc/org/v2beta"
project_v2beta "github.com/zitadel/zitadel/pkg/grpc/project/v2beta"
user_v3alpha "github.com/zitadel/zitadel/pkg/grpc/resources/user/v3alpha"
userschema_v3alpha "github.com/zitadel/zitadel/pkg/grpc/resources/userschema/v3alpha"
saml_pb "github.com/zitadel/zitadel/pkg/grpc/saml/v2"
@@ -71,6 +72,7 @@ type Client struct {
UserV3Alpha user_v3alpha.ZITADELUsersClient
SAMLv2 saml_pb.SAMLServiceClient
SCIM *scim.Client
Projectv2Beta project_v2beta.ProjectServiceClient
InstanceV2Beta instance.InstanceServiceClient
}
@@ -109,6 +111,7 @@ func newClient(ctx context.Context, target string) (*Client, error) {
UserV3Alpha: user_v3alpha.NewZITADELUsersClient(cc),
SAMLv2: saml_pb.NewSAMLServiceClient(cc),
SCIM: scim.NewScimClient(target),
Projectv2Beta: project_v2beta.NewProjectServiceClient(cc),
InstanceV2Beta: instance.NewInstanceServiceClient(cc),
}
return client, client.pollHealth(ctx)
@@ -446,6 +449,70 @@ func (i *Instance) SetUserPassword(ctx context.Context, userID, password string,
return resp.GetDetails()
}
func (i *Instance) CreateProject(ctx context.Context, t *testing.T, orgID, name string, projectRoleCheck, hasProjectCheck bool) *project_v2beta.CreateProjectResponse {
if orgID == "" {
orgID = i.DefaultOrg.GetId()
}
resp, err := i.Client.Projectv2Beta.CreateProject(ctx, &project_v2beta.CreateProjectRequest{
OrganizationId: orgID,
Name: name,
AuthorizationRequired: projectRoleCheck,
ProjectAccessRequired: hasProjectCheck,
})
require.NoError(t, err)
return resp
}
func (i *Instance) DeleteProject(ctx context.Context, t *testing.T, projectID string) *project_v2beta.DeleteProjectResponse {
resp, err := i.Client.Projectv2Beta.DeleteProject(ctx, &project_v2beta.DeleteProjectRequest{
Id: projectID,
})
require.NoError(t, err)
return resp
}
func (i *Instance) DeactivateProject(ctx context.Context, t *testing.T, projectID string) *project_v2beta.DeactivateProjectResponse {
resp, err := i.Client.Projectv2Beta.DeactivateProject(ctx, &project_v2beta.DeactivateProjectRequest{
Id: projectID,
})
require.NoError(t, err)
return resp
}
func (i *Instance) ActivateProject(ctx context.Context, t *testing.T, projectID string) *project_v2beta.ActivateProjectResponse {
resp, err := i.Client.Projectv2Beta.ActivateProject(ctx, &project_v2beta.ActivateProjectRequest{
Id: projectID,
})
require.NoError(t, err)
return resp
}
func (i *Instance) AddProjectRole(ctx context.Context, t *testing.T, projectID, roleKey, displayName, group string) *project_v2beta.AddProjectRoleResponse {
var groupP *string
if group != "" {
groupP = &group
}
resp, err := i.Client.Projectv2Beta.AddProjectRole(ctx, &project_v2beta.AddProjectRoleRequest{
ProjectId: projectID,
RoleKey: roleKey,
DisplayName: displayName,
Group: groupP,
})
require.NoError(t, err)
return resp
}
func (i *Instance) RemoveProjectRole(ctx context.Context, t *testing.T, projectID, roleKey string) *project_v2beta.RemoveProjectRoleResponse {
resp, err := i.Client.Projectv2Beta.RemoveProjectRole(ctx, &project_v2beta.RemoveProjectRoleRequest{
ProjectId: projectID,
RoleKey: roleKey,
})
require.NoError(t, err)
return resp
}
func (i *Instance) AddProviderToDefaultLoginPolicy(ctx context.Context, id string) {
_, err := i.Client.Admin.AddIDPToLoginPolicy(ctx, &admin.AddIDPToLoginPolicyRequest{
IdpId: id,
@@ -705,12 +772,40 @@ func (i *Instance) CreateIntentSession(t *testing.T, ctx context.Context, userID
createResp.GetDetails().GetChangeDate().AsTime(), createResp.GetDetails().GetChangeDate().AsTime()
}
func (i *Instance) CreateProjectGrant(ctx context.Context, projectID, grantedOrgID string) *mgmt.AddProjectGrantResponse {
resp, err := i.Client.Mgmt.AddProjectGrant(ctx, &mgmt.AddProjectGrantRequest{
GrantedOrgId: grantedOrgID,
ProjectId: projectID,
func (i *Instance) CreateProjectGrant(ctx context.Context, t *testing.T, projectID, grantedOrgID string, roles ...string) *project_v2beta.CreateProjectGrantResponse {
resp, err := i.Client.Projectv2Beta.CreateProjectGrant(ctx, &project_v2beta.CreateProjectGrantRequest{
GrantedOrganizationId: grantedOrgID,
ProjectId: projectID,
RoleKeys: roles,
})
logging.OnError(err).Panic("create project grant")
require.NoError(t, err)
return resp
}
func (i *Instance) DeleteProjectGrant(ctx context.Context, t *testing.T, projectID, grantedOrgID string) *project_v2beta.DeleteProjectGrantResponse {
resp, err := i.Client.Projectv2Beta.DeleteProjectGrant(ctx, &project_v2beta.DeleteProjectGrantRequest{
GrantedOrganizationId: grantedOrgID,
ProjectId: projectID,
})
require.NoError(t, err)
return resp
}
func (i *Instance) DeactivateProjectGrant(ctx context.Context, t *testing.T, projectID, grantedOrgID string) *project_v2beta.DeactivateProjectGrantResponse {
resp, err := i.Client.Projectv2Beta.DeactivateProjectGrant(ctx, &project_v2beta.DeactivateProjectGrantRequest{
ProjectId: projectID,
GrantedOrganizationId: grantedOrgID,
})
require.NoError(t, err)
return resp
}
func (i *Instance) ActivateProjectGrant(ctx context.Context, t *testing.T, projectID, grantedOrgID string) *project_v2beta.ActivateProjectGrantResponse {
resp, err := i.Client.Projectv2Beta.ActivateProjectGrant(ctx, &project_v2beta.ActivateProjectGrantRequest{
ProjectId: projectID,
GrantedOrganizationId: grantedOrgID,
})
require.NoError(t, err)
return resp
}
+6 -28
View File
@@ -7,6 +7,7 @@ import (
"net/http"
"net/url"
"strings"
"testing"
"time"
"github.com/brianvoe/gofakeit/v6"
@@ -133,13 +134,9 @@ func (i *Instance) CreateOIDCInactivateProjectClient(ctx context.Context, redire
return client, err
}
func (i *Instance) CreateOIDCImplicitFlowClient(ctx context.Context, redirectURI string, loginVersion *app.LoginVersion) (*management.AddOIDCAppResponse, error) {
project, err := i.Client.Mgmt.AddProject(ctx, &management.AddProjectRequest{
Name: fmt.Sprintf("project-%d", time.Now().UnixNano()),
})
if err != nil {
return nil, err
}
func (i *Instance) CreateOIDCImplicitFlowClient(ctx context.Context, t *testing.T, redirectURI string, loginVersion *app.LoginVersion) (*management.AddOIDCAppResponse, error) {
project := i.CreateProject(ctx, t, "", gofakeit.AppName(), false, false)
resp, err := i.Client.Mgmt.AddOIDCApp(ctx, &management.AddOIDCAppRequest{
ProjectId: project.GetId(),
Name: fmt.Sprintf("app-%d", time.Now().UnixNano()),
@@ -178,30 +175,11 @@ func (i *Instance) CreateOIDCImplicitFlowClient(ctx context.Context, redirectURI
})
}
func (i *Instance) CreateOIDCTokenExchangeClient(ctx context.Context) (client *management.AddOIDCAppResponse, keyData []byte, err error) {
project, err := i.Client.Mgmt.AddProject(ctx, &management.AddProjectRequest{
Name: fmt.Sprintf("project-%d", time.Now().UnixNano()),
})
if err != nil {
return nil, nil, err
}
func (i *Instance) CreateOIDCTokenExchangeClient(ctx context.Context, t *testing.T) (client *management.AddOIDCAppResponse, keyData []byte, err error) {
project := i.CreateProject(ctx, t, "", gofakeit.AppName(), false, false)
return i.CreateOIDCWebClientJWT(ctx, "", "", project.GetId(), app.OIDCGrantType_OIDC_GRANT_TYPE_TOKEN_EXCHANGE, app.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE, app.OIDCGrantType_OIDC_GRANT_TYPE_REFRESH_TOKEN)
}
func (i *Instance) CreateProject(ctx context.Context) (*management.AddProjectResponse, error) {
return i.Client.Mgmt.AddProject(ctx, &management.AddProjectRequest{
Name: fmt.Sprintf("project-%d", time.Now().UnixNano()),
})
}
func (i *Instance) CreateProjectWithPermissionCheck(ctx context.Context, projectRoleCheck, hasProjectCheck bool) (*management.AddProjectResponse, error) {
return i.Client.Mgmt.AddProject(ctx, &management.AddProjectRequest{
Name: fmt.Sprintf("project-%d", time.Now().UnixNano()),
HasProjectCheck: hasProjectCheck,
ProjectRoleCheck: projectRoleCheck,
})
}
func (i *Instance) CreateAPIClientJWT(ctx context.Context, projectID string) (*management.AddAPIAppResponse, error) {
return i.Client.Mgmt.AddAPIApp(ctx, &management.AddAPIAppRequest{
ProjectId: projectID,
+370 -2
View File
@@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"errors"
"slices"
"time"
sq "github.com/Masterminds/squirrel"
@@ -72,11 +73,104 @@ var (
}
)
var (
grantedProjectsAlias = table{
name: "granted_projects",
instanceIDCol: projection.ProjectColumnInstanceID,
}
GrantedProjectColumnID = Column{
name: projection.ProjectColumnID,
table: grantedProjectsAlias,
}
GrantedProjectColumnCreationDate = Column{
name: projection.ProjectColumnCreationDate,
table: grantedProjectsAlias,
}
GrantedProjectColumnChangeDate = Column{
name: projection.ProjectColumnChangeDate,
table: grantedProjectsAlias,
}
grantedProjectColumnResourceOwner = Column{
name: projection.ProjectColumnResourceOwner,
table: grantedProjectsAlias,
}
grantedProjectColumnInstanceID = Column{
name: projection.ProjectGrantColumnInstanceID,
table: grantedProjectsAlias,
}
grantedProjectColumnState = Column{
name: "project_state",
table: grantedProjectsAlias,
}
GrantedProjectColumnName = Column{
name: "project_name",
table: grantedProjectsAlias,
}
grantedProjectColumnProjectRoleAssertion = Column{
name: projection.ProjectColumnProjectRoleAssertion,
table: grantedProjectsAlias,
}
grantedProjectColumnProjectRoleCheck = Column{
name: projection.ProjectColumnProjectRoleCheck,
table: grantedProjectsAlias,
}
grantedProjectColumnHasProjectCheck = Column{
name: projection.ProjectColumnHasProjectCheck,
table: grantedProjectsAlias,
}
grantedProjectColumnPrivateLabelingSetting = Column{
name: projection.ProjectColumnPrivateLabelingSetting,
table: grantedProjectsAlias,
}
grantedProjectColumnGrantResourceOwner = Column{
name: "project_grant_resource_owner",
table: grantedProjectsAlias,
}
grantedProjectColumnGrantedOrganization = Column{
name: projection.ProjectGrantColumnGrantedOrgID,
table: grantedProjectsAlias,
}
grantedProjectColumnGrantedOrganizationName = Column{
name: "granted_org_name",
table: grantedProjectsAlias,
}
grantedProjectColumnGrantState = Column{
name: "project_grant_state",
table: grantedProjectsAlias,
}
)
type Projects struct {
SearchResponse
Projects []*Project
}
func projectsCheckPermission(ctx context.Context, projects *Projects, permissionCheck domain.PermissionCheck) {
projects.Projects = slices.DeleteFunc(projects.Projects,
func(project *Project) bool {
return projectCheckPermission(ctx, project.ResourceOwner, project.ID, permissionCheck) != nil
},
)
}
func projectCheckPermission(ctx context.Context, resourceOwner string, projectID string, permissionCheck domain.PermissionCheck) error {
return permissionCheck(ctx, domain.PermissionProjectRead, resourceOwner, projectID)
}
func projectPermissionCheckV2(ctx context.Context, query sq.SelectBuilder, enabled bool, queries *ProjectAndGrantedProjectSearchQueries) sq.SelectBuilder {
if !enabled {
return query
}
join, args := PermissionClause(
ctx,
grantedProjectColumnResourceOwner,
domain.PermissionProjectRead,
SingleOrgPermissionOption(queries.Queries),
OwnedRowsPermissionOption(GrantedProjectColumnID),
)
return query.JoinClause(join, args...)
}
type Project struct {
ID string
CreationDate time.Time
@@ -94,7 +188,19 @@ type Project struct {
type ProjectSearchQueries struct {
SearchRequest
Queries []SearchQuery
Queries []SearchQuery
GrantQueries []SearchQuery
}
func (q *Queries) GetProjectByIDWithPermission(ctx context.Context, shouldTriggerBulk bool, id string, permissionCheck domain.PermissionCheck) (*Project, error) {
project, err := q.ProjectByID(ctx, shouldTriggerBulk, id)
if err != nil {
return nil, err
}
if err := projectCheckPermission(ctx, project.ResourceOwner, project.ID, permissionCheck); err != nil {
return nil, err
}
return project, nil
}
func (q *Queries) ProjectByID(ctx context.Context, shouldTriggerBulk bool, id string) (project *Project, err error) {
@@ -125,7 +231,18 @@ func (q *Queries) ProjectByID(ctx context.Context, shouldTriggerBulk bool, id st
return project, err
}
func (q *Queries) SearchProjects(ctx context.Context, queries *ProjectSearchQueries) (projects *Projects, err error) {
func (q *Queries) SearchProjects(ctx context.Context, queries *ProjectSearchQueries, permissionCheck domain.PermissionCheck) (*Projects, error) {
projects, err := q.searchProjects(ctx, queries)
if err != nil {
return nil, err
}
if permissionCheck != nil {
projectsCheckPermission(ctx, projects, permissionCheck)
}
return projects, nil
}
func (q *Queries) searchProjects(ctx context.Context, queries *ProjectSearchQueries) (projects *Projects, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
@@ -147,6 +264,89 @@ func (q *Queries) SearchProjects(ctx context.Context, queries *ProjectSearchQuer
return projects, err
}
type ProjectAndGrantedProjectSearchQueries struct {
SearchRequest
Queries []SearchQuery
}
func (q *ProjectAndGrantedProjectSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
query = q.SearchRequest.toQuery(query)
for _, q := range q.Queries {
query = q.toQuery(query)
}
return query
}
func (q *Queries) SearchGrantedProjects(ctx context.Context, queries *ProjectAndGrantedProjectSearchQueries, permissionCheck domain.PermissionCheck) (*GrantedProjects, error) {
permissionCheckV2 := PermissionV2(ctx, permissionCheck)
projects, err := q.searchGrantedProjects(ctx, queries, permissionCheckV2)
if err != nil {
return nil, err
}
if permissionCheck != nil && !authz.GetFeatures(ctx).PermissionCheckV2 {
grantedProjectsCheckPermission(ctx, projects, permissionCheck)
}
return projects, nil
}
func (q *Queries) searchGrantedProjects(ctx context.Context, queries *ProjectAndGrantedProjectSearchQueries, permissionCheckV2 bool) (grantedProjects *GrantedProjects, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
query, scan := prepareGrantedProjectsQuery()
query = projectPermissionCheckV2(ctx, query, permissionCheckV2, queries)
eq := sq.Eq{grantedProjectColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID()}
stmt, args, err := queries.toQuery(query).Where(eq).ToSql()
if err != nil {
return nil, zerrors.ThrowInvalidArgument(err, "QUERY-T84X9", "Errors.Query.InvalidRequest")
}
err = q.client.QueryContext(ctx, func(rows *sql.Rows) error {
grantedProjects, err = scan(rows)
return err
}, stmt, args...)
if err != nil {
return nil, err
}
return grantedProjects, nil
}
func NewGrantedProjectNameSearchQuery(method TextComparison, value string) (SearchQuery, error) {
return NewTextQuery(GrantedProjectColumnName, value, method)
}
func NewGrantedProjectResourceOwnerSearchQuery(value string) (SearchQuery, error) {
return NewTextQuery(grantedProjectColumnResourceOwner, value, TextEquals)
}
func NewGrantedProjectIDSearchQuery(ids []string) (SearchQuery, error) {
list := make([]interface{}, len(ids))
for i, value := range ids {
list[i] = value
}
return NewListQuery(GrantedProjectColumnID, list, ListIn)
}
func NewGrantedProjectOrganizationIDSearchQuery(value string) (SearchQuery, error) {
project, err := NewTextQuery(grantedProjectColumnResourceOwner, value, TextEquals)
if err != nil {
return nil, err
}
grant, err := NewTextQuery(grantedProjectColumnGrantedOrganization, value, TextEquals)
if err != nil {
return nil, err
}
return NewOrQuery(project, grant)
}
func NewGrantedProjectGrantResourceOwnerSearchQuery(value string) (SearchQuery, error) {
return NewTextQuery(grantedProjectColumnGrantResourceOwner, value, TextEquals)
}
func NewGrantedProjectGrantedOrganizationIDSearchQuery(value string) (SearchQuery, error) {
return NewTextQuery(grantedProjectColumnGrantedOrganization, value, TextEquals)
}
func NewProjectNameSearchQuery(method TextComparison, value string) (SearchQuery, error) {
return NewTextQuery(ProjectColumnName, value, method)
}
@@ -285,3 +485,171 @@ func prepareProjectsQuery() (sq.SelectBuilder, func(*sql.Rows) (*Projects, error
}, nil
}
}
type GrantedProjects struct {
SearchResponse
GrantedProjects []*GrantedProject
}
func grantedProjectsCheckPermission(ctx context.Context, grantedProjects *GrantedProjects, permissionCheck domain.PermissionCheck) {
grantedProjects.GrantedProjects = slices.DeleteFunc(grantedProjects.GrantedProjects,
func(grantedProject *GrantedProject) bool {
return projectCheckPermission(ctx, grantedProject.ResourceOwner, grantedProject.ProjectID, permissionCheck) != nil
},
)
}
type GrantedProject struct {
ProjectID string
CreationDate time.Time
ChangeDate time.Time
ResourceOwner string
InstanceID string
ProjectState domain.ProjectState
ProjectName string
ProjectRoleAssertion bool
ProjectRoleCheck bool
HasProjectCheck bool
PrivateLabelingSetting domain.PrivateLabelingSetting
GrantedOrgID string
OrgName string
ProjectGrantState domain.ProjectGrantState
}
func prepareGrantedProjectsQuery() (sq.SelectBuilder, func(*sql.Rows) (*GrantedProjects, error)) {
return sq.Select(
GrantedProjectColumnID.identifier(),
GrantedProjectColumnCreationDate.identifier(),
GrantedProjectColumnChangeDate.identifier(),
grantedProjectColumnResourceOwner.identifier(),
grantedProjectColumnInstanceID.identifier(),
grantedProjectColumnState.identifier(),
GrantedProjectColumnName.identifier(),
grantedProjectColumnProjectRoleAssertion.identifier(),
grantedProjectColumnProjectRoleCheck.identifier(),
grantedProjectColumnHasProjectCheck.identifier(),
grantedProjectColumnPrivateLabelingSetting.identifier(),
grantedProjectColumnGrantedOrganization.identifier(),
grantedProjectColumnGrantedOrganizationName.identifier(),
grantedProjectColumnGrantState.identifier(),
countColumn.identifier(),
).From(getProjectsAndGrantedProjectsFromQuery()).
PlaceholderFormat(sq.Dollar),
func(rows *sql.Rows) (*GrantedProjects, error) {
projects := make([]*GrantedProject, 0)
var (
count uint64
orgID = sql.NullString{}
orgName = sql.NullString{}
projectGrantState = sql.NullInt16{}
)
for rows.Next() {
grantedProject := new(GrantedProject)
err := rows.Scan(
&grantedProject.ProjectID,
&grantedProject.CreationDate,
&grantedProject.ChangeDate,
&grantedProject.ResourceOwner,
&grantedProject.InstanceID,
&grantedProject.ProjectState,
&grantedProject.ProjectName,
&grantedProject.ProjectRoleAssertion,
&grantedProject.ProjectRoleCheck,
&grantedProject.HasProjectCheck,
&grantedProject.PrivateLabelingSetting,
&orgID,
&orgName,
&projectGrantState,
&count,
)
if err != nil {
return nil, err
}
if orgID.Valid {
grantedProject.GrantedOrgID = orgID.String
}
if orgName.Valid {
grantedProject.OrgName = orgName.String
}
if projectGrantState.Valid {
grantedProject.ProjectGrantState = domain.ProjectGrantState(projectGrantState.Int16)
}
projects = append(projects, grantedProject)
}
if err := rows.Close(); err != nil {
return nil, zerrors.ThrowInternal(err, "QUERY-K9gEE", "Errors.Query.CloseRows")
}
return &GrantedProjects{
GrantedProjects: projects,
SearchResponse: SearchResponse{
Count: count,
},
}, nil
}
}
func getProjectsAndGrantedProjectsFromQuery() string {
return "(" +
prepareProjects() +
" UNION ALL " +
prepareGrantedProjects() +
") AS " + grantedProjectsAlias.identifier()
}
func prepareProjects() string {
builder := sq.Select(
ProjectColumnID.identifier()+" AS "+GrantedProjectColumnID.name,
ProjectColumnCreationDate.identifier()+" AS "+GrantedProjectColumnCreationDate.name,
ProjectColumnChangeDate.identifier()+" AS "+GrantedProjectColumnChangeDate.name,
ProjectColumnResourceOwner.identifier()+" AS "+grantedProjectColumnResourceOwner.name,
ProjectColumnInstanceID.identifier()+" AS "+grantedProjectColumnInstanceID.name,
ProjectColumnState.identifier()+" AS "+grantedProjectColumnState.name,
ProjectColumnName.identifier()+" AS "+GrantedProjectColumnName.name,
ProjectColumnProjectRoleAssertion.identifier()+" AS "+grantedProjectColumnProjectRoleAssertion.name,
ProjectColumnProjectRoleCheck.identifier()+" AS "+grantedProjectColumnProjectRoleCheck.name,
ProjectColumnHasProjectCheck.identifier()+" AS "+grantedProjectColumnHasProjectCheck.name,
ProjectColumnPrivateLabelingSetting.identifier()+" AS "+grantedProjectColumnPrivateLabelingSetting.name,
"NULL::TEXT AS "+grantedProjectColumnGrantResourceOwner.name,
"NULL::TEXT AS "+grantedProjectColumnGrantedOrganization.name,
"NULL::TEXT AS "+grantedProjectColumnGrantedOrganizationName.name,
"NULL::SMALLINT AS "+grantedProjectColumnGrantState.name,
countColumn.identifier()).
From(projectsTable.identifier()).
PlaceholderFormat(sq.Dollar)
stmt, _ := builder.MustSql()
return stmt
}
func prepareGrantedProjects() string {
grantedOrgTable := orgsTable.setAlias(ProjectGrantGrantedOrgTableAlias)
grantedOrgIDColumn := OrgColumnID.setTable(grantedOrgTable)
builder := sq.Select(
ProjectGrantColumnProjectID.identifier()+" AS "+GrantedProjectColumnID.name,
ProjectGrantColumnCreationDate.identifier()+" AS "+GrantedProjectColumnCreationDate.name,
ProjectGrantColumnChangeDate.identifier()+" AS "+GrantedProjectColumnChangeDate.name,
ProjectColumnResourceOwner.identifier()+" AS "+grantedProjectColumnResourceOwner.name,
ProjectGrantColumnInstanceID.identifier()+" AS "+grantedProjectColumnInstanceID.name,
ProjectColumnState.identifier()+" AS "+grantedProjectColumnState.name,
ProjectColumnName.identifier()+" AS "+GrantedProjectColumnName.name,
ProjectColumnProjectRoleAssertion.identifier()+" AS "+grantedProjectColumnProjectRoleAssertion.name,
ProjectColumnProjectRoleCheck.identifier()+" AS "+grantedProjectColumnProjectRoleCheck.name,
ProjectColumnHasProjectCheck.identifier()+" AS "+grantedProjectColumnHasProjectCheck.name,
ProjectColumnPrivateLabelingSetting.identifier()+" AS "+grantedProjectColumnPrivateLabelingSetting.name,
ProjectGrantColumnResourceOwner.identifier()+" AS "+grantedProjectColumnGrantResourceOwner.name,
ProjectGrantColumnGrantedOrgID.identifier()+" AS "+grantedProjectColumnGrantedOrganization.name,
ProjectGrantColumnGrantedOrgName.identifier()+" AS "+grantedProjectColumnGrantedOrganizationName.name,
ProjectGrantColumnState.identifier()+" AS "+grantedProjectColumnGrantState.name,
countColumn.identifier()).
From(projectGrantsTable.identifier()).
PlaceholderFormat(sq.Dollar).
LeftJoin(join(ProjectColumnID, ProjectGrantColumnProjectID)).
LeftJoin(join(grantedOrgIDColumn, ProjectGrantColumnGrantedOrgID))
stmt, _ := builder.MustSql()
return stmt
}
+74 -14
View File
@@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"errors"
"slices"
"time"
sq "github.com/Masterminds/squirrel"
@@ -104,6 +105,43 @@ type ProjectGrantSearchQueries struct {
Queries []SearchQuery
}
func projectGrantsCheckPermission(ctx context.Context, projectGrants *ProjectGrants, permissionCheck domain.PermissionCheck) {
projectGrants.ProjectGrants = slices.DeleteFunc(projectGrants.ProjectGrants,
func(projectGrant *ProjectGrant) bool {
return projectGrantCheckPermission(ctx, projectGrant.ResourceOwner, projectGrant.GrantID, permissionCheck) != nil
},
)
}
func projectGrantCheckPermission(ctx context.Context, resourceOwner string, grantID string, permissionCheck domain.PermissionCheck) error {
return permissionCheck(ctx, domain.PermissionProjectGrantRead, resourceOwner, grantID)
}
func projectGrantPermissionCheckV2(ctx context.Context, query sq.SelectBuilder, enabled bool, queries *ProjectGrantSearchQueries) sq.SelectBuilder {
if !enabled {
return query
}
join, args := PermissionClause(
ctx,
ProjectGrantColumnResourceOwner,
domain.PermissionProjectGrantRead,
SingleOrgPermissionOption(queries.Queries),
OwnedRowsPermissionOption(ProjectGrantColumnGrantID),
)
return query.JoinClause(join, args...)
}
func (q *Queries) GetProjectGrantByIDWithPermission(ctx context.Context, shouldTriggerBulk bool, id string, permissionCheck domain.PermissionCheck) (*ProjectGrant, error) {
projectGrant, err := q.ProjectGrantByID(ctx, shouldTriggerBulk, id)
if err != nil {
return nil, err
}
if err := projectCheckPermission(ctx, projectGrant.ResourceOwner, projectGrant.GrantID, permissionCheck); err != nil {
return nil, err
}
return projectGrant, nil
}
func (q *Queries) ProjectGrantByID(ctx context.Context, shouldTriggerBulk bool, id string) (grant *ProjectGrant, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
@@ -154,11 +192,24 @@ func (q *Queries) ProjectGrantByIDAndGrantedOrg(ctx context.Context, id, granted
return grant, err
}
func (q *Queries) SearchProjectGrants(ctx context.Context, queries *ProjectGrantSearchQueries) (grants *ProjectGrants, err error) {
func (q *Queries) SearchProjectGrants(ctx context.Context, queries *ProjectGrantSearchQueries, permissionCheck domain.PermissionCheck) (grants *ProjectGrants, err error) {
permissionCheckV2 := PermissionV2(ctx, permissionCheck)
projectsGrants, err := q.searchProjectGrants(ctx, queries, permissionCheckV2)
if err != nil {
return nil, err
}
if permissionCheck != nil && !authz.GetFeatures(ctx).PermissionCheckV2 {
projectGrantsCheckPermission(ctx, projectsGrants, permissionCheck)
}
return projectsGrants, nil
}
func (q *Queries) searchProjectGrants(ctx context.Context, queries *ProjectGrantSearchQueries, permissionCheckV2 bool) (grants *ProjectGrants, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
query, scan := prepareProjectGrantsQuery()
query = projectGrantPermissionCheckV2(ctx, query, permissionCheckV2, queries)
eq := sq.Eq{
ProjectGrantColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
}
@@ -179,6 +230,7 @@ func (q *Queries) SearchProjectGrants(ctx context.Context, queries *ProjectGrant
return grants, err
}
// SearchProjectGrantsByProjectIDAndRoleKey is used internally to remove the roles of a project grant, so no permission check necessary
func (q *Queries) SearchProjectGrantsByProjectIDAndRoleKey(ctx context.Context, projectID, roleKey string) (projects *ProjectGrants, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
@@ -195,13 +247,33 @@ func (q *Queries) SearchProjectGrantsByProjectIDAndRoleKey(ctx context.Context,
if err != nil {
return nil, err
}
return q.SearchProjectGrants(ctx, searchQuery)
return q.SearchProjectGrants(ctx, searchQuery, nil)
}
func NewProjectGrantProjectIDSearchQuery(value string) (SearchQuery, error) {
return NewTextQuery(ProjectGrantColumnProjectID, value, TextEquals)
}
func (q *ProjectGrantSearchQueries) AppendPermissionQueries(permissions []string) error {
if !authz.HasGlobalPermission(permissions) {
ids := authz.GetAllPermissionCtxIDs(permissions)
query, err := NewProjectGrantIDsSearchQuery(ids)
if err != nil {
return err
}
q.Queries = append(q.Queries, query)
}
return nil
}
func NewProjectGrantProjectIDsSearchQuery(ids []string) (SearchQuery, error) {
list := make([]interface{}, len(ids))
for i, value := range ids {
list[i] = value
}
return NewListQuery(ProjectGrantColumnProjectID, list, ListIn)
}
func NewProjectGrantIDsSearchQuery(values []string) (SearchQuery, error) {
list := make([]interface{}, len(values))
for i, value := range values {
@@ -243,18 +315,6 @@ func (q *ProjectGrantSearchQueries) AppendGrantedOrgQuery(orgID string) error {
return nil
}
func (q *ProjectGrantSearchQueries) AppendPermissionQueries(permissions []string) error {
if !authz.HasGlobalPermission(permissions) {
ids := authz.GetAllPermissionCtxIDs(permissions)
query, err := NewProjectGrantIDsSearchQuery(ids)
if err != nil {
return err
}
q.Queries = append(q.Queries, query)
}
return nil
}
func (q *ProjectGrantSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
query = q.SearchRequest.toQuery(query)
for _, q := range q.Queries {
+42 -1
View File
@@ -3,12 +3,14 @@ package query
import (
"context"
"database/sql"
"slices"
"time"
sq "github.com/Masterminds/squirrel"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
"github.com/zitadel/zitadel/internal/query/projection"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
@@ -80,7 +82,45 @@ type ProjectRoleSearchQueries struct {
Queries []SearchQuery
}
func (q *Queries) SearchProjectRoles(ctx context.Context, shouldTriggerBulk bool, queries *ProjectRoleSearchQueries) (roles *ProjectRoles, err error) {
func projectRolesCheckPermission(ctx context.Context, projectRoles *ProjectRoles, permissionCheck domain.PermissionCheck) {
projectRoles.ProjectRoles = slices.DeleteFunc(projectRoles.ProjectRoles,
func(projectRole *ProjectRole) bool {
return projectRoleCheckPermission(ctx, projectRole.ResourceOwner, projectRole.Key, permissionCheck) != nil
},
)
}
func projectRoleCheckPermission(ctx context.Context, resourceOwner string, grantID string, permissionCheck domain.PermissionCheck) error {
return permissionCheck(ctx, domain.PermissionProjectGrantRead, resourceOwner, grantID)
}
func projectRolePermissionCheckV2(ctx context.Context, query sq.SelectBuilder, enabled bool, queries *ProjectRoleSearchQueries) sq.SelectBuilder {
if !enabled {
return query
}
join, args := PermissionClause(
ctx,
ProjectRoleColumnResourceOwner,
domain.PermissionProjectRoleRead,
SingleOrgPermissionOption(queries.Queries),
OwnedRowsPermissionOption(ProjectRoleColumnKey),
)
return query.JoinClause(join, args...)
}
func (q *Queries) SearchProjectRoles(ctx context.Context, shouldTriggerBulk bool, queries *ProjectRoleSearchQueries, permissionCheck domain.PermissionCheck) (roles *ProjectRoles, err error) {
permissionCheckV2 := PermissionV2(ctx, permissionCheck)
projectRoles, err := q.searchProjectRoles(ctx, shouldTriggerBulk, queries, permissionCheckV2)
if err != nil {
return nil, err
}
if permissionCheck != nil && !authz.GetFeatures(ctx).PermissionCheckV2 {
projectRolesCheckPermission(ctx, projectRoles, permissionCheck)
}
return projectRoles, nil
}
func (q *Queries) searchProjectRoles(ctx context.Context, shouldTriggerBulk bool, queries *ProjectRoleSearchQueries, permissionCheckV2 bool) (roles *ProjectRoles, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
@@ -94,6 +134,7 @@ func (q *Queries) SearchProjectRoles(ctx context.Context, shouldTriggerBulk bool
eq := sq.Eq{ProjectRoleColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID()}
query, scan := prepareProjectRolesQuery()
query = projectRolePermissionCheckV2(ctx, query, permissionCheckV2, queries)
stmt, args, err := queries.toQuery(query).Where(eq).ToSql()
if err != nil {
return nil, zerrors.ThrowInvalidArgument(err, "QUERY-3N9ff", "Errors.Query.InvalidRequest")
+6
View File
@@ -1,6 +1,8 @@
package project
import (
"context"
"github.com/zitadel/zitadel/internal/eventstore"
)
@@ -23,3 +25,7 @@ func NewAggregate(id, resourceOwner string) *Aggregate {
},
}
}
func AggregateFromWriteModel(ctx context.Context, wm *eventstore.WriteModel) *eventstore.Aggregate {
return eventstore.AggregateFromWriteModelCtx(ctx, wm, AggregateType, AggregateVersion)
}
+2 -5
View File
@@ -184,10 +184,7 @@ func NewProjectChangeEvent(
aggregate *eventstore.Aggregate,
oldName string,
changes []ProjectChanges,
) (*ProjectChangeEvent, error) {
if len(changes) == 0 {
return nil, zerrors.ThrowPreconditionFailed(nil, "PROJECT-mV9xc", "Errors.NoChangesFound")
}
) *ProjectChangeEvent {
changeEvent := &ProjectChangeEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
ctx,
@@ -199,7 +196,7 @@ func NewProjectChangeEvent(
for _, change := range changes {
change(changeEvent)
}
return changeEvent, nil
return changeEvent
}
type ProjectChanges func(event *ProjectChangeEvent)
+132 -44
View File
@@ -2678,6 +2678,11 @@ service ManagementService {
};
}
// Get Project By ID
//
// Deprecated: [Get Project](apis/resources/project_service_v2/project-service-get-project.api.mdx) to get project by ID.
//
// Returns a project owned by the organization (no granted projects). A Project is a vessel for different applications sharing the same role context.
rpc GetProjectByID(GetProjectByIDRequest) returns (GetProjectByIDResponse) {
option (google.api.http) = {
get: "/projects/{id}"
@@ -2690,8 +2695,7 @@ service ManagementService {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Projects";
summary: "Get Project By ID";
description: "Returns a project owned by the organization (no granted projects). A Project is a vessel for different applications sharing the same role context."
deprecated: true;
parameters: {
headers: {
name: "x-zitadel-orgid";
@@ -2703,6 +2707,11 @@ service ManagementService {
};
}
// Get Granted Project By ID
//
// Deprecated: [List Projects](apis/resources/project_service_v2/project-service-list-projects.api.mdx) to get granted projects.
//
// Returns a project owned by another organization and granted to my organization. A Project is a vessel for different applications sharing the same role context.
rpc GetGrantedProjectByID(GetGrantedProjectByIDRequest) returns (GetGrantedProjectByIDResponse) {
option (google.api.http) = {
get: "/granted_projects/{project_id}/grants/{grant_id}"
@@ -2715,8 +2724,7 @@ service ManagementService {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Projects";
summary: "Get Granted Project By ID";
description: "Returns a project owned by another organization and granted to my organization. A Project is a vessel for different applications sharing the same role context."
deprecated: true;
parameters: {
headers: {
name: "x-zitadel-orgid";
@@ -2728,6 +2736,11 @@ service ManagementService {
};
}
// List Projects
//
// Deprecated: [List Projects](apis/resources/project_service_v2/project-service-list-projects.api.mdx) to list all projects and granted projects.
//
// Lists projects my organization is the owner of (no granted projects). A Project is a vessel for different applications sharing the same role context.
rpc ListProjects(ListProjectsRequest) returns (ListProjectsResponse) {
option (google.api.http) = {
post: "/projects/_search"
@@ -2740,8 +2753,7 @@ service ManagementService {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Projects";
summary: "Search Project";
description: "Lists projects my organization is the owner of (no granted projects). A Project is a vessel for different applications sharing the same role context."
deprecated: true;
parameters: {
headers: {
name: "x-zitadel-orgid";
@@ -2753,6 +2765,11 @@ service ManagementService {
};
}
// List Granted Projects
//
// Deprecated: [List Projects](apis/resources/project_service_v2/project-service-list-projects.api.mdx) to list all projects and granted projects.
//
// Lists projects my organization got granted from another organization. A Project is a vessel for different applications sharing the same role context.
rpc ListGrantedProjects(ListGrantedProjectsRequest) returns (ListGrantedProjectsResponse) {
option (google.api.http) = {
post: "/granted_projects/_search"
@@ -2765,8 +2782,7 @@ service ManagementService {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Projects";
summary: "Search Granted Project";
description: "Lists projects my organization got granted from another organization. A Project is a vessel for different applications sharing the same role context."
deprecated: true;
parameters: {
headers: {
name: "x-zitadel-orgid";
@@ -2828,6 +2844,11 @@ service ManagementService {
};
}
// Create Project
//
// Deprecated: [Create Project](apis/resources/project_service_v2/project-service-create-project.api.mdx) to create a project.
//
// Create a new project. A Project is a vessel for different applications sharing the same role context.
rpc AddProject(AddProjectRequest) returns (AddProjectResponse) {
option (google.api.http) = {
post: "/projects"
@@ -2840,8 +2861,7 @@ service ManagementService {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Projects";
summary: "Create Project";
description: "Create a new project. A Project is a vessel for different applications sharing the same role context."
deprecated: true;
parameters: {
headers: {
name: "x-zitadel-orgid";
@@ -2853,6 +2873,11 @@ service ManagementService {
};
}
// Update Project
//
// Deprecated: [Update Project](apis/resources/project_service_v2/project-service-update-project.api.mdx) to update a project.
//
// Update a project and its settings. A Project is a vessel for different applications sharing the same role context.
rpc UpdateProject(UpdateProjectRequest) returns (UpdateProjectResponse) {
option (google.api.http) = {
put: "/projects/{id}"
@@ -2866,8 +2891,7 @@ service ManagementService {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Projects";
summary: "Update Project";
description: "Update a project and its settings. A Project is a vessel for different applications sharing the same role context."
deprecated: true;
parameters: {
headers: {
name: "x-zitadel-orgid";
@@ -2879,6 +2903,11 @@ service ManagementService {
};
}
// Deactivate Project
//
// Deprecated: [Deactivate Project](apis/resources/project_service_v2/project-service-deactivate-project.api.mdx) to deactivate a project.
//
// Set the state of a project to deactivated. Request returns an error if the project is already deactivated.
rpc DeactivateProject(DeactivateProjectRequest) returns (DeactivateProjectResponse) {
option (google.api.http) = {
post: "/projects/{id}/_deactivate"
@@ -2892,8 +2921,7 @@ service ManagementService {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Projects";
summary: "Deactivate Project";
description: "Set the state of a project to deactivated. Request returns an error if the project is already deactivated."
deprecated: true;
parameters: {
headers: {
name: "x-zitadel-orgid";
@@ -2905,6 +2933,11 @@ service ManagementService {
};
}
// Activate Project
//
// Deprecated: [Activate Project](apis/resources/project_service_v2/project-service-activate-project.api.mdx) to activate a project.
//
// Set the state of a project to active. Request returns an error if the project is not deactivated.
rpc ReactivateProject(ReactivateProjectRequest) returns (ReactivateProjectResponse) {
option (google.api.http) = {
post: "/projects/{id}/_reactivate"
@@ -2918,8 +2951,7 @@ service ManagementService {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Projects";
summary: "Reactivate Project";
description: "Set the state of a project to active. Request returns an error if the project is not deactivated."
deprecated: true;
parameters: {
headers: {
name: "x-zitadel-orgid";
@@ -2931,6 +2963,11 @@ service ManagementService {
};
}
// Remove Project
//
// Deprecated: [Delete Project](apis/resources/project_service_v2/project-service-delete-project.api.mdx) to remove a project.
//
// Project and all its sub-resources like project grants, applications, roles and user grants will be removed.
rpc RemoveProject(RemoveProjectRequest) returns (RemoveProjectResponse) {
option (google.api.http) = {
delete: "/projects/{id}"
@@ -2943,8 +2980,7 @@ service ManagementService {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Projects";
summary: "Remove Project";
description: "Project and all its sub-resources like project grants, applications, roles and user grants will be removed."
deprecated: true;
parameters: {
headers: {
name: "x-zitadel-orgid";
@@ -2956,6 +2992,11 @@ service ManagementService {
};
}
// Search Project Roles
//
// Deprecated: [List Project Roles](apis/resources/project_service_v2/project-service-list-project-roles.api.mdx) to get project roles.
//
// Returns all roles of a project matching the search query.
rpc ListProjectRoles(ListProjectRolesRequest) returns (ListProjectRolesResponse) {
option (google.api.http) = {
post: "/projects/{project_id}/roles/_search"
@@ -2969,8 +3010,7 @@ service ManagementService {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Project Roles";
summary: "Search Project Roles";
description: "Returns all roles of a project matching the search query."
deprecated: true;
parameters: {
headers: {
name: "x-zitadel-orgid";
@@ -2982,6 +3022,11 @@ service ManagementService {
};
}
// Add Project Role
//
// Deprecated: [Add Project Role](apis/resources/project_service_v2/project-service-add-project-role.api.mdx) to add a project role.
//
// Add a new project role to a project. The key must be unique within the project.\n\nDeprecated: please use user service v2 AddProjectRole.
rpc AddProjectRole(AddProjectRoleRequest) returns (AddProjectRoleResponse) {
option (google.api.http) = {
post: "/projects/{project_id}/roles"
@@ -2995,8 +3040,7 @@ service ManagementService {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Project Roles";
summary: "Add Project Role";
description: "Add a new project role to a project. The key must be unique within the project."
deprecated: true;
parameters: {
headers: {
name: "x-zitadel-orgid";
@@ -3008,6 +3052,11 @@ service ManagementService {
};
}
// Bulk add Project Role
//
// Deprecated: [Add Project Role](apis/resources/project_service_v2/project-service-add-project-role.api.mdx) to add a project role.
//
// Add a list of roles to a project. The keys must be unique within the project.
rpc BulkAddProjectRoles(BulkAddProjectRolesRequest) returns (BulkAddProjectRolesResponse) {
option (google.api.http) = {
post: "/projects/{project_id}/roles/_bulk"
@@ -3021,8 +3070,7 @@ service ManagementService {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Project Roles";
summary: "Bulk Add Project Role";
description: "Add a list of roles to a project. The keys must be unique within the project."
deprecated: true;
parameters: {
headers: {
name: "x-zitadel-orgid";
@@ -3034,6 +3082,11 @@ service ManagementService {
};
}
// Update Project Role
//
// Deprecated: [Update Project Role](apis/resources/project_service_v2/project-service-update-project-role.api.mdx) to update a project role.
//
// Change a project role. The key is not editable. If a key should change, remove the role and create a new one.
rpc UpdateProjectRole(UpdateProjectRoleRequest) returns (UpdateProjectRoleResponse) {
option (google.api.http) = {
put: "/projects/{project_id}/roles/{role_key}"
@@ -3047,8 +3100,7 @@ service ManagementService {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Project Roles";
summary: "Change Project Role";
description: "Change a project role. The key is not editable. If a key should change, remove the role and create a new one."
deprecated: true;
parameters: {
headers: {
name: "x-zitadel-orgid";
@@ -3060,6 +3112,11 @@ service ManagementService {
};
}
// Remove Project Role
//
// Deprecated: [Delete Project Role](apis/resources/project_service_v2/project-service-update-project-role.api.mdx) to remove a project role.
//
// Removes the role from the project and on every resource it has a dependency. This includes project grants and user grants.
rpc RemoveProjectRole(RemoveProjectRoleRequest) returns (RemoveProjectRoleResponse) {
option (google.api.http) = {
delete: "/projects/{project_id}/roles/{role_key}"
@@ -3072,8 +3129,7 @@ service ManagementService {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Project Roles";
summary: "Remove Project Role";
description: "Removes the role from the project and on every resource it has a dependency. This includes project grants and user grants."
deprecated: true;
parameters: {
headers: {
name: "x-zitadel-orgid";
@@ -3737,6 +3793,11 @@ service ManagementService {
};
}
// Get Project Grant By ID
//
// Deprecated: [List Project Grants](apis/resources/project_service_v2/project-service-list-project-grants.api.mdx) to get a project grant.
//
// Returns a project grant. A project grant is when the organization grants its project to another organization.
rpc GetProjectGrantByID(GetProjectGrantByIDRequest) returns (GetProjectGrantByIDResponse) {
option (google.api.http) = {
get: "/projects/{project_id}/grants/{grant_id}"
@@ -3748,8 +3809,7 @@ service ManagementService {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Projects";
summary: "Project Grant By ID";
description: "Returns a project grant. A project grant is when the organization grants its project to another organization."
deprecated: true;
parameters: {
headers: {
name: "x-zitadel-orgid";
@@ -3761,6 +3821,11 @@ service ManagementService {
};
}
// List Project Grants
//
// Deprecated: [List Project Grants](apis/resources/project_service_v2/project-service-list-project-grants.api.mdx) to list project grants.
//
// Returns a list of project grants for a specific project. A project grant is when the organization grants its project to another organization.
rpc ListProjectGrants(ListProjectGrantsRequest) returns (ListProjectGrantsResponse) {
option (google.api.http) = {
post: "/projects/{project_id}/grants/_search"
@@ -3774,8 +3839,7 @@ service ManagementService {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Project Grants";
summary: "Search Project Grants from Project";
description: "Returns a list of project grants for a specific project. A project grant is when the organization grants its project to another organization."
deprecated: true;
parameters: {
headers: {
name: "x-zitadel-orgid";
@@ -3787,6 +3851,11 @@ service ManagementService {
};
}
// Search Project Grants
//
// Deprecated: [List Project Grants](apis/resources/project_service_v2/project-service-list-project-grants.api.mdx) to list project grants.
//
// Returns a list of project grants. A project grant is when the organization grants its project to another organization.
rpc ListAllProjectGrants(ListAllProjectGrantsRequest) returns (ListAllProjectGrantsResponse) {
option (google.api.http) = {
post: "/projectgrants/_search"
@@ -3799,8 +3868,7 @@ service ManagementService {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Project Grants";
summary: "Search Project Grants";
description: "Returns a list of project grants. A project grant is when the organization grants its project to another organization."
deprecated: true;
parameters: {
headers: {
name: "x-zitadel-orgid";
@@ -3812,6 +3880,11 @@ service ManagementService {
};
}
// Add Project Grant
//
// Deprecated: [Create Project Grant](apis/resources/project_service_v2/project-service-create-project-grant.api.mdx) to add a project grant.
//
// Grant a project to another organization. The project grant will allow the granted organization to access the project and manage the authorizations for its users. Project Grant will be listed in the granted project of the granted organization.
rpc AddProjectGrant(AddProjectGrantRequest) returns (AddProjectGrantResponse) {
option (google.api.http) = {
post: "/projects/{project_id}/grants"
@@ -3824,8 +3897,7 @@ service ManagementService {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Project Grants";
summary: "Add Project Grant";
description: "Grant a project to another organization. The project grant will allow the granted organization to access the project and manage the authorizations for its users. Project Grant will be listed in the granted project of the granted organization"
deprecated: true;
parameters: {
headers: {
name: "x-zitadel-orgid";
@@ -3837,6 +3909,11 @@ service ManagementService {
};
}
// Update Project Grant
//
// Deprecated: [Update Project Grant](apis/resources/project_service_v2/project-service-update-project-grant.api.mdx) to update a project grant.
//
// Change the roles of the project that is granted to another organization. The project grant will allow the granted organization to access the project and manage the authorizations for its users. Project Grant will be listed in the granted project of the granted organization.
rpc UpdateProjectGrant(UpdateProjectGrantRequest) returns (UpdateProjectGrantResponse) {
option (google.api.http) = {
put: "/projects/{project_id}/grants/{grant_id}"
@@ -3849,8 +3926,7 @@ service ManagementService {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Project Grants";
summary: "Change Project Grant";
description: "Change the roles of the project that is granted to another organization. The project grant will allow the granted organization to access the project and manage the authorizations for its users. Project Grant will be listed in the granted project of the granted organization"
deprecated: true;
parameters: {
headers: {
name: "x-zitadel-orgid";
@@ -3862,6 +3938,11 @@ service ManagementService {
};
}
// Deactivate Project Grant
//
// Deprecated: [Deactivate Project Grant](apis/resources/project_service_v2/project-service-deactivate-project-grant.api.mdx) to deactivate a project grant.
//
// Set the state of the project grant to deactivated. The grant has to be active to be able to deactivate.
rpc DeactivateProjectGrant(DeactivateProjectGrantRequest) returns (DeactivateProjectGrantResponse) {
option (google.api.http) = {
post: "/projects/{project_id}/grants/{grant_id}/_deactivate"
@@ -3874,8 +3955,7 @@ service ManagementService {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Project Grants";
summary: "Deactivate Project Grant";
description: "Set the state of the project grant to deactivated. The grant has to be active to be able to deactivate."
deprecated: true;
parameters: {
headers: {
name: "x-zitadel-orgid";
@@ -3887,6 +3967,11 @@ service ManagementService {
};
}
// Reactivate Project Grant
//
// Deprecated: [Activate Project Grant](apis/resources/project_service_v2/project-service-activate-project-grant.api.mdx) to activate a project grant.
//
// Set the state of the project grant to active. The grant has to be deactivated to be able to reactivate.
rpc ReactivateProjectGrant(ReactivateProjectGrantRequest) returns (ReactivateProjectGrantResponse) {
option (google.api.http) = {
post: "/projects/{project_id}/grants/{grant_id}/_reactivate"
@@ -3899,8 +3984,7 @@ service ManagementService {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Project Grants";
summary: "Reactivate Project Grant";
description: "Set the state of the project grant to active. The grant has to be deactivated to be able to reactivate."
deprecated: true;
parameters: {
headers: {
name: "x-zitadel-orgid";
@@ -3912,6 +3996,11 @@ service ManagementService {
};
}
// Remove Project Grant
//
// Deprecated: [Delete Project Grant](apis/resources/project_service_v2/project-service-delete-project-grant.api.mdx) to remove a project grant.
//
// Remove a project grant. All user grants for this project grant will also be removed. A user will not have access to the project afterward (if permissions are checked).
rpc RemoveProjectGrant(RemoveProjectGrantRequest) returns (RemoveProjectGrantResponse) {
option (google.api.http) = {
delete: "/projects/{project_id}/grants/{grant_id}"
@@ -3923,8 +4012,7 @@ service ManagementService {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Project Grants";
summary: "Remove Project Grant";
description: "Remove a project grant. All user grants for this project grant will also be removed. A user will not have access to the project afterward (if permissions are checked)."
deprecated: true;
parameters: {
headers: {
name: "x-zitadel-orgid";
File diff suppressed because it is too large Load Diff
+347
View File
@@ -0,0 +1,347 @@
syntax = "proto3";
package zitadel.project.v2beta;
option go_package = "github.com/zitadel/zitadel/pkg/grpc/project/v2beta;project";
import "google/api/field_behavior.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
import "validate/validate.proto";
import "google/protobuf/timestamp.proto";
import "zitadel/filter/v2beta/filter.proto";
message ProjectGrant {
// The unique identifier of the organization which granted the project to the granted_organization_id.
string organization_id = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"69629012906488334\"";
}
];
// The timestamp of the granted project creation.
google.protobuf.Timestamp creation_date = 3 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"2024-12-18T07:50:47.492Z\"";
}
];
// The timestamp of the last change to the granted project (e.g. creation, activation, deactivation).
google.protobuf.Timestamp change_date = 4 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"2025-01-23T10:34:18.051Z\"";
}
];
// The ID of the organization the project is granted to.
string granted_organization_id = 5 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"69629023906488334\""
}
];
// The name of the organization the project is granted to.
string granted_organization_name = 6 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"Some Organization\""
}
];
// The roles of the granted project.
repeated string granted_role_keys = 7 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"role.super.man\"]"
}
];
// The ID of the granted project.
string project_id = 8 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"69629023906488334\""
}
];
// The name of the granted project.
string project_name = 9 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"ZITADEL\""
}
];
// Describes the current state of the granted project.
ProjectGrantState state = 10 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "current state of the project";
}
];
}
enum ProjectGrantState {
PROJECT_GRANT_STATE_UNSPECIFIED = 0;
PROJECT_GRANT_STATE_ACTIVE = 1;
PROJECT_GRANT_STATE_INACTIVE = 2;
}
message Project {
// The unique identifier of the project.
string id = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"69629012906488334\"";
}
];
// The unique identifier of the organization the project belongs to.
string organization_id = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"69629012906488334\"";
}
];
// The timestamp of the project creation.
google.protobuf.Timestamp creation_date = 3 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"2024-12-18T07:50:47.492Z\"";
}
];
// The timestamp of the last change to the project (e.g. creation, activation, deactivation).
google.protobuf.Timestamp change_date = 4 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"2025-01-23T10:34:18.051Z\"";
}
];
// The name of the project.
string name = 5 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"ip_allow_list\"";
}
];
// Describes the current state of the project.
ProjectState state = 6 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "current state of the project";
}
];
// Describes if the roles of the user should be added to the token.
bool project_role_assertion = 7;
// When enabled ZITADEL will check if a user has an authorization to use this project assigned when login into an application of this project.
bool authorization_required = 8;
// When enabled ZITADEL will check if the organization of the user, that is trying to log in, has access to this project (either owns the project or is granted).
bool project_access_required = 9;
// Defines from where the private labeling should be triggered.
PrivateLabelingSetting private_labeling_setting = 10;
// The ID of the organization the project is granted to.
optional string granted_organization_id = 12 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"69629023906488334\""
}
];
// The name of the organization the project is granted to.
optional string granted_organization_name = 13 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"Some Organization\""
}
];
// Describes the current state of the granted project.
GrantedProjectState granted_state = 14 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "current state of the granted project";
}
];
}
enum ProjectState {
PROJECT_STATE_UNSPECIFIED = 0;
PROJECT_STATE_ACTIVE = 1;
PROJECT_STATE_INACTIVE = 2;
}
enum GrantedProjectState {
GRANTED_PROJECT_STATE_UNSPECIFIED = 0;
GRANTED_PROJECT_STATE_ACTIVE = 1;
GRANTED_PROJECT_STATE_INACTIVE = 2;
}
enum PrivateLabelingSetting {
PRIVATE_LABELING_SETTING_UNSPECIFIED = 0;
PRIVATE_LABELING_SETTING_ENFORCE_PROJECT_RESOURCE_OWNER_POLICY = 1;
PRIVATE_LABELING_SETTING_ALLOW_LOGIN_USER_RESOURCE_OWNER_POLICY = 2;
}
enum ProjectFieldName {
PROJECT_FIELD_NAME_UNSPECIFIED = 0;
PROJECT_FIELD_NAME_ID = 1;
PROJECT_FIELD_NAME_CREATION_DATE = 2;
PROJECT_FIELD_NAME_CHANGE_DATE = 3;
PROJECT_FIELD_NAME_NAME = 4;
}
enum ProjectGrantFieldName {
PROJECT_GRANT_FIELD_NAME_UNSPECIFIED = 0;
PROJECT_GRANT_FIELD_NAME_PROJECT_ID = 1;
PROJECT_GRANT_FIELD_NAME_CREATION_DATE = 2;
PROJECT_GRANT_FIELD_NAME_CHANGE_DATE = 3;
}
enum ProjectRoleFieldName {
PROJECT_ROLE_FIELD_NAME_UNSPECIFIED = 0;
PROJECT_ROLE_FIELD_NAME_KEY = 1;
PROJECT_ROLE_FIELD_NAME_CREATION_DATE = 2;
PROJECT_ROLE_FIELD_NAME_CHANGE_DATE = 3;
}
message ProjectSearchFilter {
oneof filter {
option (validate.required) = true;
ProjectNameFilter project_name_filter = 1;
InProjectIDsFilter in_project_ids_filter = 2;
ProjectResourceOwnerFilter project_resource_owner_filter = 3;
ProjectGrantResourceOwnerFilter project_grant_resource_owner_filter = 4;
ProjectOrganizationIDFilter project_organization_id_filter = 5;
}
}
message ProjectNameFilter {
// Defines the name of the project to query for.
string project_name = 1 [
(validate.rules).string = {max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
max_length: 200;
example: "\"ip_allow_list\"";
}
];
// Defines which text comparison method used for the name query.
zitadel.filter.v2beta.TextFilterMethod method = 2 [
(validate.rules).enum.defined_only = true,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "defines which text equality method is used";
}
];
}
message InProjectIDsFilter {
// Defines the ids to query for.
repeated string project_ids = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "the ids of the projects to include"
example: "[\"69629023906488334\",\"69622366012355662\"]";
}
];
}
message ProjectResourceOwnerFilter {
// Defines the ID of organization the project belongs to query for.
string project_resource_owner = 1 [
(validate.rules).string = {max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"69629023906488334\""
}
];
}
message ProjectGrantResourceOwnerFilter {
// Defines the ID of organization the project grant belongs to query for.
string project_grant_resource_owner = 1 [
(validate.rules).string = {max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"69629023906488334\""
}
];
}
message ProjectOrganizationIDFilter {
// Defines the ID of organization the project and granted project belong to query for.
string project_organization_id = 1 [
(validate.rules).string = {max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"69629023906488334\""
}
];
}
message ProjectGrantSearchFilter {
oneof filter {
option (validate.required) = true;
ProjectNameFilter project_name_filter = 1;
ProjectRoleKeyFilter role_key_filter = 2;
InProjectIDsFilter in_project_ids_filter = 3;
ProjectResourceOwnerFilter project_resource_owner_filter = 4;
ProjectGrantResourceOwnerFilter project_grant_resource_owner_filter = 5;
}
}
message GrantedOrganizationIDFilter {
// Defines the ID of organization the project is granted to query for.
string granted_organization_id = 1 [
(validate.rules).string = {max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"69629023906488334\""
}
];
}
message ProjectRole {
// ID of the project.
string project_id = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"69629026806489455\"";
}
];
// Key of the project role.
string key = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"role.super.man\""
}
];
// The timestamp of the project role creation.
google.protobuf.Timestamp creation_date = 3 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"2024-12-18T07:50:47.492Z\"";
}
];
// The timestamp of the last change to the project role.
google.protobuf.Timestamp change_date = 4 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"2025-01-23T10:34:18.051Z\"";
}
];
// Display name of the project role.
string display_name = 5 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"Super man\""
}
];
// Group of the project role.
string group = 6 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"people\""
}
];
}
message ProjectRoleSearchFilter {
oneof filter {
option (validate.required) = true;
ProjectRoleKeyFilter role_key_filter = 1;
ProjectRoleDisplayNameFilter display_name_filter = 2;
}
}
message ProjectRoleKeyFilter {
string key = 1 [
(validate.rules).string = {max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"role.super.man\""
}
];
// Defines which text comparison method used for the name query.
zitadel.filter.v2beta.TextFilterMethod method = 2 [
(validate.rules).enum.defined_only = true
];
}
message ProjectRoleDisplayNameFilter {
string display_name = 1 [
(validate.rules).string = {max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"SUPER\""
}
];
// Defines which text comparison method used for the name query.
zitadel.filter.v2beta.TextFilterMethod method = 2 [
(validate.rules).enum.defined_only = true
];
}