mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-23 16:02:14 +00:00
This pull request introduces a new feature that allows adding, updating, and querying project grants The changes are primarily in the backend and include new database tables, repositories, and domain logic to support project grants. ## Changes * New `project_grants` table: A new table `zitadel.project_grants` is introduced to store project grants It includes columns for `instance_id`, `id`, `state`, `project_id`, `granted_org_id` and `granting_org_id`. * New `ProjectGrantRepository`: A new repository `ProjectGrantRepository` is created to handle all database operations for project grants. It provides methods to `Get`, `List`, `Create`, `Update`, and `Delete`. * New `project_grant_roles` table: A new table `zitadel.project_grant_roles` is introduced to store project grant roles It includes columns for `instance_id`, `grant_id`, `key`, `project_id` and `project_org_id`. * New `ProjectGrantRoleRepository`: A new repository `ProjectGrantRoleRepository` is created to handle all database operations for project grant roles. It provides methods to `Get`, `List`, `Add`, and `Delete`. * New `project_grant_relational_projection`: A new projection `project_grant_relational_projection` is added to update the `zitadel.project_grants` and `zitadel.project_grant_roles` tables based on events. It handles all project grant events. * Updated `ProjectRepository`: The `ProjectRepository` is updated to list granted projects. # Additional Context Closes https://github.com/zitadel/zitadel/issues/10766 --------- Co-authored-by: Stefan Benz <[email protected]> Co-authored-by: Marco A. <[email protected]> Co-authored-by: Silvan <[email protected]>
299 lines
9.3 KiB
Go
299 lines
9.3 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/zitadel/zitadel/backend/v3/domain"
|
|
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
|
)
|
|
|
|
// -------------------------------------------------------------
|
|
// repository
|
|
// -------------------------------------------------------------
|
|
|
|
type project struct{}
|
|
|
|
// ProjectRepository manages projects and project roles.
|
|
func ProjectRepository() domain.ProjectRepository {
|
|
return project{}
|
|
}
|
|
|
|
func (project) Role() domain.ProjectRoleRepository {
|
|
return projectRole{}
|
|
}
|
|
|
|
func (p project) Get(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) (*domain.Project, error) {
|
|
builder, err := p.prepareGetQuery(opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return getOne[domain.Project](ctx, client, builder)
|
|
}
|
|
|
|
func (p project) List(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) ([]*domain.Project, error) {
|
|
builder, err := p.prepareListQuery(opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return getMany[domain.Project](ctx, client, builder)
|
|
}
|
|
|
|
const insertProjectStmt = `INSERT INTO zitadel.projects(
|
|
instance_id, organization_id, id, name, state, should_assert_role, is_authorization_required, is_project_access_required, used_labeling_setting_owner
|
|
)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
RETURNING created_at, updated_at`
|
|
|
|
func (project) Create(ctx context.Context, client database.QueryExecutor, project *domain.Project) error {
|
|
builder := database.NewStatementBuilder(insertProjectStmt,
|
|
project.InstanceID,
|
|
project.OrganizationID,
|
|
project.ID,
|
|
project.Name,
|
|
project.State,
|
|
project.ShouldAssertRole,
|
|
project.IsAuthorizationRequired,
|
|
project.IsProjectAccessRequired,
|
|
project.UsedLabelingSettingOwner,
|
|
)
|
|
return client.QueryRow(ctx, builder.String(), builder.Args()...).
|
|
Scan(&project.CreatedAt, &project.UpdatedAt)
|
|
}
|
|
|
|
func (p project) Update(ctx context.Context, client database.QueryExecutor, condition database.Condition, changes ...database.Change) (int64, error) {
|
|
return updateOne(ctx, client, p, condition, changes...)
|
|
}
|
|
|
|
func (p project) Delete(ctx context.Context, client database.QueryExecutor, condition database.Condition) (int64, error) {
|
|
return deleteOne(ctx, client, p, condition)
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
// changes
|
|
// -------------------------------------------------------------
|
|
|
|
func (p project) SetUpdatedAt(updatedAt time.Time) database.Change {
|
|
return database.NewChange(p.UpdatedAtColumn(), updatedAt)
|
|
}
|
|
|
|
func (p project) SetName(name string) database.Change {
|
|
return database.NewChange(p.NameColumn(), name)
|
|
}
|
|
|
|
func (p project) SetState(state domain.ProjectState) database.Change {
|
|
return database.NewChange(p.StateColumn(), state)
|
|
}
|
|
|
|
func (p project) SetShouldAssertRole(shouldAssertRole bool) database.Change {
|
|
return database.NewChange(p.ShouldAssertRoleColumn(), shouldAssertRole)
|
|
}
|
|
|
|
func (p project) SetIsAuthorizationRequired(isAuthorizationRequired bool) database.Change {
|
|
return database.NewChange(p.IsAuthorizationRequiredColumn(), isAuthorizationRequired)
|
|
}
|
|
|
|
func (p project) SetIsProjectAccessRequired(isProjectAccessRequired bool) database.Change {
|
|
return database.NewChange(p.IsProjectAccessRequiredColumn(), isProjectAccessRequired)
|
|
}
|
|
|
|
func (p project) SetUsedLabelingSettingOwner(usedLabelingSettingOwner int16) database.Change {
|
|
return database.NewChange(p.UsedLabelingSettingOwnerColumn(), usedLabelingSettingOwner)
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
// conditions
|
|
// -------------------------------------------------------------
|
|
|
|
func (p project) PrimaryKeyCondition(instanceID, projectID string) database.Condition {
|
|
return database.And(
|
|
p.InstanceIDCondition(instanceID),
|
|
p.IDCondition(projectID),
|
|
)
|
|
}
|
|
|
|
func (p project) InstanceIDCondition(instanceID string) database.Condition {
|
|
return database.NewTextCondition(p.InstanceIDColumn(), database.TextOperationEqual, instanceID)
|
|
}
|
|
|
|
func (p project) OrganizationIDCondition(organizationID string) database.Condition {
|
|
return database.NewTextCondition(p.OrganizationIDColumn(), database.TextOperationEqual, organizationID)
|
|
}
|
|
|
|
func (p project) GrantedOrganizationIDCondition(organizationID string) database.Condition {
|
|
return database.NewTextCondition(p.GrantedOrganizationIDColumn(), database.TextOperationEqual, organizationID)
|
|
}
|
|
|
|
func (p project) IDCondition(projectID string) database.Condition {
|
|
return database.NewTextCondition(p.IDColumn(), database.TextOperationEqual, projectID)
|
|
}
|
|
|
|
func (p project) NameCondition(op database.TextOperation, name string) database.Condition {
|
|
return database.NewTextCondition(p.NameColumn(), op, name)
|
|
}
|
|
|
|
func (p project) StateCondition(state domain.ProjectState) database.Condition {
|
|
return database.NewTextCondition(p.StateColumn(), database.TextOperationEqual, state.String())
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
// columns
|
|
// -------------------------------------------------------------
|
|
|
|
func (p project) qualifiedTableName() string {
|
|
return "zitadel." + p.unqualifiedTableName()
|
|
}
|
|
|
|
func (project) unqualifiedTableName() string {
|
|
return "projects"
|
|
}
|
|
|
|
// PrimaryKeyColumns implements the [pkRepository] interface
|
|
func (p project) PrimaryKeyColumns() []database.Column {
|
|
return []database.Column{
|
|
p.InstanceIDColumn(),
|
|
p.IDColumn(),
|
|
}
|
|
}
|
|
|
|
func (p project) InstanceIDColumn() database.Column {
|
|
return database.NewColumn(p.unqualifiedTableName(), "instance_id")
|
|
}
|
|
|
|
func (p project) OrganizationIDColumn() database.Column {
|
|
return database.NewColumn(p.unqualifiedTableName(), "organization_id")
|
|
}
|
|
|
|
func (p project) GrantedOrganizationIDColumn() database.Column {
|
|
return database.NewColumn(p.unqualifiedTableName(), "granted_organization_id")
|
|
}
|
|
|
|
func (p project) IDColumn() database.Column {
|
|
return database.NewColumn(p.unqualifiedTableName(), "id")
|
|
}
|
|
|
|
func (p project) CreatedAtColumn() database.Column {
|
|
return database.NewColumn(p.unqualifiedTableName(), "created_at")
|
|
}
|
|
|
|
func (p project) UpdatedAtColumn() database.Column {
|
|
return database.NewColumn(p.unqualifiedTableName(), "updated_at")
|
|
}
|
|
|
|
func (p project) NameColumn() database.Column {
|
|
return database.NewColumn(p.unqualifiedTableName(), "name")
|
|
}
|
|
|
|
func (p project) StateColumn() database.Column {
|
|
return database.NewColumn(p.unqualifiedTableName(), "state")
|
|
}
|
|
|
|
func (p project) ShouldAssertRoleColumn() database.Column {
|
|
return database.NewColumn(p.unqualifiedTableName(), "should_assert_role")
|
|
}
|
|
|
|
func (p project) IsAuthorizationRequiredColumn() database.Column {
|
|
return database.NewColumn(p.unqualifiedTableName(), "is_authorization_required")
|
|
}
|
|
|
|
func (p project) IsProjectAccessRequiredColumn() database.Column {
|
|
return database.NewColumn(p.unqualifiedTableName(), "is_project_access_required")
|
|
}
|
|
|
|
func (p project) UsedLabelingSettingOwnerColumn() database.Column {
|
|
return database.NewColumn(p.unqualifiedTableName(), "used_labeling_setting_owner")
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
// helpers
|
|
// -------------------------------------------------------------
|
|
|
|
const queryProjectStmt = `SELECT
|
|
projects.instance_id,
|
|
projects.organization_id,
|
|
projects.id,
|
|
projects.created_at,
|
|
projects.updated_at,
|
|
projects.name,
|
|
projects.state,
|
|
projects.should_assert_role,
|
|
projects.is_authorization_required,
|
|
projects.is_project_access_required,
|
|
projects.used_labeling_setting_owner,
|
|
NULL::TEXT AS grant_id,
|
|
NULL::TEXT AS granted_organization_id,
|
|
NULL::zitadel.project_grant_state AS grant_state
|
|
FROM zitadel.projects`
|
|
|
|
func (p project) prepareGetQuery(opts []database.QueryOption) (*database.StatementBuilder, error) {
|
|
options := new(database.QueryOpts)
|
|
for _, opt := range opts {
|
|
opt(options)
|
|
}
|
|
if err := checkRestrictingColumns(options.Condition, p.InstanceIDColumn()); err != nil {
|
|
return nil, err
|
|
}
|
|
builder := database.NewStatementBuilder(queryProjectStmt)
|
|
options.Write(builder)
|
|
|
|
return builder, nil
|
|
}
|
|
|
|
const queryGrantedProjectStmt = `SELECT
|
|
projects.instance_id,
|
|
projects.organization_id,
|
|
projects.id as id,
|
|
grants.created_at,
|
|
grants.updated_at,
|
|
projects.name,
|
|
projects.state,
|
|
projects.should_assert_role,
|
|
projects.is_authorization_required,
|
|
projects.is_project_access_required,
|
|
projects.used_labeling_setting_owner,
|
|
grants.id AS grant_id,
|
|
grants.granted_organization_id,
|
|
grants.state as grant_state
|
|
FROM zitadel.projects
|
|
INNER JOIN zitadel.project_grants as grants
|
|
ON grants.instance_id = projects.instance_id
|
|
AND grants.granting_organization_id = projects.organization_id
|
|
AND grants.project_id = projects.id`
|
|
|
|
const queryProjectsUnionStmt = `SELECT
|
|
projects.instance_id,
|
|
projects.organization_id,
|
|
projects.id as id,
|
|
projects.created_at,
|
|
projects.updated_at,
|
|
projects.name,
|
|
projects.state,
|
|
projects.should_assert_role,
|
|
projects.is_authorization_required,
|
|
projects.is_project_access_required,
|
|
projects.used_labeling_setting_owner,
|
|
projects.grant_id,
|
|
projects.granted_organization_id,
|
|
projects.grant_state
|
|
FROM (` +
|
|
queryProjectStmt +
|
|
` UNION ALL ` +
|
|
queryGrantedProjectStmt +
|
|
`) AS projects`
|
|
|
|
func (p project) prepareListQuery(opts []database.QueryOption) (*database.StatementBuilder, error) {
|
|
options := new(database.QueryOpts)
|
|
for _, opt := range opts {
|
|
opt(options)
|
|
}
|
|
if err := checkRestrictingColumns(options.Condition, p.InstanceIDColumn()); err != nil {
|
|
return nil, err
|
|
}
|
|
builder := database.NewStatementBuilder(
|
|
queryProjectsUnionStmt,
|
|
)
|
|
options.Write(builder)
|
|
|
|
return builder, nil
|
|
}
|