Files
zitadel/internal/api/grpc/project/v2/project.go
Livio Spring c3a3766445 feat(api): move project service v2beta to GA (and deprecate v2beta) (#10844)
# Which Problems Are Solved

As part of our efforts to simplify the structure and versions of our
APIs, were moving all existing v2beta endpoints to v2 and deprecate
them. They will be removed in Zitadel V5.

# How the Problems Are Solved

- This PR moves project v2beta service and its endpoints to a
corresponding v2 version. The v2beta service and endpoints are
deprecated.
- The comments and have been improved and, where not already done, moved
from swagger annotations to proto.
- All required fields have been marked with `(google.api.field_behavior)
= REQUIRED` and validation rules have been added where missing.
- Name ID of the project always `project_id`
- `UpdateProjectRequest` has been updated to align with the creation and
retrieval of a project:
  - `project_role_check` has been renamed to `authorization_required`
  - `has_project_check` has been renamed to `project_access_required`
- `ListProjectRequest` has been changed:
- `project_grant_resource_owner_filter`,
`project_grant_resource_owner_filter` and
`project_organization_id_filter` have been removed and merged into a
single `organization_id_filter` where a `type` can optionally be
specified to select `owned`, `granted` or both project types within a
specified organization.
- `ListProjectGrantReques` has been changed:
- `project_resource_owner_filter` has been renamed to
`project_organization_id_filter`
- `grant_resource_owner_filter` has been renamed to
`granted_organization_id_filter`

# Additional Changes

Replaced deprecated `intergration.WithAuthorization` with
`integration.WithAuthorizationToken` in integration tests.

# Additional Context

- part of #10772
- requires backport to v4.x

(cherry picked from commit 32500e3b0c)
2025-10-28 15:10:03 +01:00

163 lines
5.5 KiB
Go

package project
import (
"context"
"connectrpc.com/connect"
"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/v2"
)
func (s *Server) CreateProject(ctx context.Context, req *connect.Request[project_pb.CreateProjectRequest]) (*connect.Response[project_pb.CreateProjectResponse], error) {
add := projectCreateToCommand(req.Msg)
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 connect.NewResponse(&project_pb.CreateProjectResponse{
ProjectId: add.AggregateID,
CreationDate: creationDate,
}), nil
}
func projectCreateToCommand(req *project_pb.CreateProjectRequest) *command.AddProject {
var aggregateID string
if req.ProjectId != nil {
aggregateID = *req.ProjectId
}
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 *connect.Request[project_pb.UpdateProjectRequest]) (*connect.Response[project_pb.UpdateProjectResponse], error) {
project, err := s.command.ChangeProject(ctx, projectUpdateToCommand(req.Msg))
if err != nil {
return nil, err
}
var changeDate *timestamppb.Timestamp
if !project.EventDate.IsZero() {
changeDate = timestamppb.New(project.EventDate)
}
return connect.NewResponse(&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.ProjectId,
},
Name: req.Name,
ProjectRoleAssertion: req.ProjectRoleAssertion,
ProjectRoleCheck: req.AuthorizationRequired,
HasProjectCheck: req.ProjectAccessRequired,
PrivateLabelingSetting: labeling,
}
}
func (s *Server) DeleteProject(ctx context.Context, req *connect.Request[project_pb.DeleteProjectRequest]) (*connect.Response[project_pb.DeleteProjectResponse], error) {
userGrantIDs, err := s.userGrantsFromProject(ctx, req.Msg.GetProjectId())
if err != nil {
return nil, err
}
deletedAt, err := s.command.DeleteProject(ctx, req.Msg.GetProjectId(), "", userGrantIDs...)
if err != nil {
return nil, err
}
var deletionDate *timestamppb.Timestamp
if !deletedAt.IsZero() {
deletionDate = timestamppb.New(deletedAt)
}
return connect.NewResponse(&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, nil)
if err != nil {
return nil, err
}
return userGrantsToIDs(userGrants.UserGrants), nil
}
func (s *Server) DeactivateProject(ctx context.Context, req *connect.Request[project_pb.DeactivateProjectRequest]) (*connect.Response[project_pb.DeactivateProjectResponse], error) {
details, err := s.command.DeactivateProject(ctx, req.Msg.GetProjectId(), "")
if err != nil {
return nil, err
}
var changeDate *timestamppb.Timestamp
if !details.EventDate.IsZero() {
changeDate = timestamppb.New(details.EventDate)
}
return connect.NewResponse(&project_pb.DeactivateProjectResponse{
ChangeDate: changeDate,
}), nil
}
func (s *Server) ActivateProject(ctx context.Context, req *connect.Request[project_pb.ActivateProjectRequest]) (*connect.Response[project_pb.ActivateProjectResponse], error) {
details, err := s.command.ReactivateProject(ctx, req.Msg.GetProjectId(), "")
if err != nil {
return nil, err
}
var changeDate *timestamppb.Timestamp
if !details.EventDate.IsZero() {
changeDate = timestamppb.New(details.EventDate)
}
return connect.NewResponse(&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
}