zitadel/internal/command/project.go
Silvan f3e6f3b23b
feat: remove org (#4148)
* feat(command): remove org

* refactor: imports, unused code, error handling

* reduce org removed in action

* add org deletion to projections

* add org removal to projections

* add org removal to projections

* org removed projection

* lint import

* projections

* fix: table names in tests

* fix: table names in tests

* logging

* add org state

* fix(domain): add Owner removed to object details

* feat(ListQuery): add with owner removed

* fix(org-delete): add bool to functions to select with owner removed

* fix(org-delete): add bools to user grants with events to determine if dependencies lost owner

* fix(org-delete): add unit tests for owner removed and org removed events

* fix(org-delete): add handling of org remove for grants and members

* fix(org-delete): correction of unit tests for owner removed

* fix(org-delete): update projections, unit tests and get functions

* fix(org-delete): add change date to authnkeys and owner removed to org metadata

* fix(org-delete): include owner removed for login names

* fix(org-delete): some column fixes in projections and build for queries with owner removed

* indexes

* fix(org-delete): include review changes

* fix(org-delete): change user projection name after merge

* fix(org-delete): include review changes for project grant where no project owner is necessary

* fix(org-delete): include auth and adminapi tables with owner removed information

* fix(org-delete): cleanup username and orgdomain uniqueconstraints when org is removed

* fix(org-delete): add permissions for org.remove

* remove unnecessary unique constraints

* fix column order in primary keys

* fix(org-delete): include review changes

* fix(org-delete): add owner removed indexes and chang setup step to create tables

* fix(org-delete): move PK order of instance_id and change added user_grant from review

* fix(org-delete): no params for prepareUserQuery

* change to step 6

* merge main

* fix(org-delete): OldUserName rename to private

* fix linting

* cleanup

* fix: remove org test

* create prerelease

* chore: delete org-delete as prerelease

Co-authored-by: Stefan Benz <stefan@caos.ch>
Co-authored-by: Livio Spring <livio.a@gmail.com>
Co-authored-by: Fabi <38692350+hifabienne@users.noreply.github.com>
Co-authored-by: Stefan Benz <46600784+stebenz@users.noreply.github.com>
2022-11-30 17:01:17 +01:00

332 lines
12 KiB
Go

package command
import (
"context"
"strings"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/command/preparation"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/project"
)
func (c *Commands) AddProjectWithID(ctx context.Context, project *domain.Project, resourceOwner, projectID string) (_ *domain.Project, err error) {
existingProject, err := c.getProjectWriteModelByID(ctx, projectID, resourceOwner)
if err != nil {
return nil, err
}
if existingProject.State != domain.ProjectStateUnspecified {
return nil, errors.ThrowInvalidArgument(nil, "COMMAND-opamwu", "Errors.Project.AlreadyExisting")
}
return c.addProjectWithID(ctx, project, resourceOwner, projectID)
}
func (c *Commands) AddProject(ctx context.Context, project *domain.Project, resourceOwner, ownerUserID string) (_ *domain.Project, err error) {
if !project.IsValid() {
return nil, errors.ThrowInvalidArgument(nil, "PROJECT-IOVCC", "Errors.Project.Invalid")
}
projectID, err := c.idGenerator.Next()
if err != nil {
return nil, err
}
return c.addProjectWithIDWithOwner(ctx, project, resourceOwner, ownerUserID, projectID)
}
func (c *Commands) addProjectWithID(ctx context.Context, projectAdd *domain.Project, resourceOwner, projectID string) (_ *domain.Project, err error) {
projectAdd.AggregateID = projectID
addedProject := NewProjectWriteModel(projectAdd.AggregateID, resourceOwner)
projectAgg := ProjectAggregateFromWriteModel(&addedProject.WriteModel)
events := []eventstore.Command{
project.NewProjectAddedEvent(
ctx,
projectAgg,
projectAdd.Name,
projectAdd.ProjectRoleAssertion,
projectAdd.ProjectRoleCheck,
projectAdd.HasProjectCheck,
projectAdd.PrivateLabelingSetting),
}
pushedEvents, err := c.eventstore.Push(ctx, events...)
if err != nil {
return nil, err
}
err = AppendAndReduce(addedProject, pushedEvents...)
if err != nil {
return nil, err
}
return projectWriteModelToProject(addedProject), nil
}
func (c *Commands) addProjectWithIDWithOwner(ctx context.Context, projectAdd *domain.Project, resourceOwner, ownerUserID, projectID string) (_ *domain.Project, err error) {
if !projectAdd.IsValid() {
return nil, errors.ThrowInvalidArgument(nil, "PROJECT-IOVCC", "Errors.Project.Invalid")
}
projectAdd.AggregateID = projectID
addedProject := NewProjectWriteModel(projectAdd.AggregateID, resourceOwner)
projectAgg := ProjectAggregateFromWriteModel(&addedProject.WriteModel)
projectRole := domain.RoleProjectOwner
events := []eventstore.Command{
project.NewProjectAddedEvent(
ctx,
projectAgg,
projectAdd.Name,
projectAdd.ProjectRoleAssertion,
projectAdd.ProjectRoleCheck,
projectAdd.HasProjectCheck,
projectAdd.PrivateLabelingSetting),
project.NewProjectMemberAddedEvent(ctx, projectAgg, ownerUserID, projectRole),
}
pushedEvents, err := c.eventstore.Push(ctx, events...)
if err != nil {
return nil, err
}
err = AppendAndReduce(addedProject, pushedEvents...)
if err != nil {
return nil, err
}
return projectWriteModelToProject(addedProject), nil
}
func AddProjectCommand(
a *project.Aggregate,
name string,
owner string,
projectRoleAssertion bool,
projectRoleCheck bool,
hasProjectCheck bool,
privateLabelingSetting domain.PrivateLabelingSetting,
) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if name = strings.TrimSpace(name); name == "" {
return nil, errors.ThrowInvalidArgument(nil, "PROJE-C01yo", "Errors.Invalid.Argument")
}
if !privateLabelingSetting.Valid() {
return nil, errors.ThrowInvalidArgument(nil, "PROJE-AO52V", "Errors.Invalid.Argument")
}
if owner == "" {
return nil, errors.ThrowPreconditionFailed(nil, "PROJE-hzxwo", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
return []eventstore.Command{
project.NewProjectAddedEvent(ctx, &a.Aggregate,
name,
projectRoleAssertion,
projectRoleCheck,
hasProjectCheck,
privateLabelingSetting,
),
project.NewProjectMemberAddedEvent(ctx, &a.Aggregate,
owner,
domain.RoleProjectOwner),
}, nil
}, nil
}
}
func projectWriteModel(ctx context.Context, filter preparation.FilterToQueryReducer, projectID, resourceOwner string) (project *ProjectWriteModel, err error) {
project = NewProjectWriteModel(projectID, resourceOwner)
events, err := filter(ctx, project.Query())
if err != nil {
return nil, err
}
project.AppendEvents(events...)
if err := project.Reduce(); err != nil {
return nil, err
}
return project, nil
}
func (c *Commands) getProjectByID(ctx context.Context, projectID, resourceOwner string) (*domain.Project, error) {
projectWriteModel, err := c.getProjectWriteModelByID(ctx, projectID, resourceOwner)
if err != nil {
return nil, err
}
if projectWriteModel.State == domain.ProjectStateUnspecified || projectWriteModel.State == domain.ProjectStateRemoved {
return nil, errors.ThrowNotFound(nil, "PROJECT-Gd2hh", "Errors.Project.NotFound")
}
return projectWriteModelToProject(projectWriteModel), nil
}
func (c *Commands) checkProjectExists(ctx context.Context, projectID, resourceOwner string) error {
projectWriteModel, err := c.getProjectWriteModelByID(ctx, projectID, resourceOwner)
if err != nil {
return err
}
if projectWriteModel.State == domain.ProjectStateUnspecified || projectWriteModel.State == domain.ProjectStateRemoved {
return errors.ThrowPreconditionFailed(nil, "COMMAND-EbFMN", "Errors.Project.NotFound")
}
return nil
}
func (c *Commands) ChangeProject(ctx context.Context, projectChange *domain.Project, resourceOwner string) (*domain.Project, error) {
if !projectChange.IsValid() || projectChange.AggregateID == "" {
return nil, errors.ThrowInvalidArgument(nil, "COMMAND-4m9vS", "Errors.Project.Invalid")
}
existingProject, err := c.getProjectWriteModelByID(ctx, projectChange.AggregateID, resourceOwner)
if err != nil {
return nil, err
}
if existingProject.State == domain.ProjectStateUnspecified || existingProject.State == domain.ProjectStateRemoved {
return nil, errors.ThrowNotFound(nil, "COMMAND-3M9sd", "Errors.Project.NotFound")
}
projectAgg := ProjectAggregateFromWriteModel(&existingProject.WriteModel)
changedEvent, hasChanged, err := existingProject.NewChangedEvent(
ctx,
projectAgg,
projectChange.Name,
projectChange.ProjectRoleAssertion,
projectChange.ProjectRoleCheck,
projectChange.HasProjectCheck,
projectChange.PrivateLabelingSetting)
if err != nil {
return nil, err
}
if !hasChanged {
return nil, errors.ThrowPreconditionFailed(nil, "COMMAND-2M0fs", "Errors.NoChangesFound")
}
pushedEvents, err := c.eventstore.Push(ctx, changedEvent)
if err != nil {
return nil, err
}
err = AppendAndReduce(existingProject, pushedEvents...)
if err != nil {
return nil, err
}
return projectWriteModelToProject(existingProject), nil
}
func (c *Commands) DeactivateProject(ctx context.Context, projectID string, resourceOwner string) (*domain.ObjectDetails, error) {
if projectID == "" || resourceOwner == "" {
return nil, errors.ThrowInvalidArgument(nil, "COMMAND-88iF0", "Errors.Project.ProjectIDMissing")
}
existingProject, err := c.getProjectWriteModelByID(ctx, projectID, resourceOwner)
if err != nil {
return nil, err
}
if existingProject.State == domain.ProjectStateUnspecified || existingProject.State == domain.ProjectStateRemoved {
return nil, errors.ThrowNotFound(nil, "COMMAND-112M9", "Errors.Project.NotFound")
}
if existingProject.State != domain.ProjectStateActive {
return nil, errors.ThrowPreconditionFailed(nil, "COMMAND-mki55", "Errors.Project.NotActive")
}
projectAgg := ProjectAggregateFromWriteModel(&existingProject.WriteModel)
pushedEvents, err := c.eventstore.Push(ctx, project.NewProjectDeactivatedEvent(ctx, projectAgg))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingProject, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingProject.WriteModel), nil
}
func (c *Commands) ReactivateProject(ctx context.Context, projectID string, resourceOwner string) (*domain.ObjectDetails, error) {
if projectID == "" || resourceOwner == "" {
return nil, errors.ThrowInvalidArgument(nil, "COMMAND-3ihsF", "Errors.Project.ProjectIDMissing")
}
existingProject, err := c.getProjectWriteModelByID(ctx, projectID, resourceOwner)
if err != nil {
return nil, err
}
if existingProject.State == domain.ProjectStateUnspecified || existingProject.State == domain.ProjectStateRemoved {
return nil, errors.ThrowNotFound(nil, "COMMAND-3M9sd", "Errors.Project.NotFound")
}
if existingProject.State != domain.ProjectStateInactive {
return nil, errors.ThrowPreconditionFailed(nil, "COMMAND-5M9bs", "Errors.Project.NotInactive")
}
projectAgg := ProjectAggregateFromWriteModel(&existingProject.WriteModel)
pushedEvents, err := c.eventstore.Push(ctx, project.NewProjectReactivatedEvent(ctx, projectAgg))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingProject, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingProject.WriteModel), nil
}
func (c *Commands) RemoveProject(ctx context.Context, projectID, resourceOwner string, cascadingUserGrantIDs ...string) (*domain.ObjectDetails, error) {
if projectID == "" || resourceOwner == "" {
return nil, errors.ThrowInvalidArgument(nil, "COMMAND-66hM9", "Errors.Project.ProjectIDMissing")
}
existingProject, err := c.getProjectWriteModelByID(ctx, projectID, resourceOwner)
if err != nil {
return nil, err
}
if existingProject.State == domain.ProjectStateUnspecified || existingProject.State == domain.ProjectStateRemoved {
return nil, errors.ThrowNotFound(nil, "COMMAND-3M9sd", "Errors.Project.NotFound")
}
samlEntityIDsAgg, err := c.getSAMLEntityIdsWriteModelByProjectID(ctx, projectID, resourceOwner)
if err != nil {
return nil, err
}
uniqueConstraints := make([]*eventstore.EventUniqueConstraint, len(samlEntityIDsAgg.EntityIDs))
for i, entityID := range samlEntityIDsAgg.EntityIDs {
uniqueConstraints[i] = project.NewRemoveSAMLConfigEntityIDUniqueConstraint(entityID.EntityID)
}
projectAgg := ProjectAggregateFromWriteModel(&existingProject.WriteModel)
events := []eventstore.Command{
project.NewProjectRemovedEvent(ctx, projectAgg, 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")
continue
}
events = append(events, event)
}
pushedEvents, err := c.eventstore.Push(ctx, events...)
if err != nil {
return nil, err
}
err = AppendAndReduce(existingProject, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingProject.WriteModel), nil
}
func (c *Commands) getProjectWriteModelByID(ctx context.Context, projectID, resourceOwner string) (*ProjectWriteModel, error) {
projectWriteModel := NewProjectWriteModel(projectID, resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, projectWriteModel)
if err != nil {
return nil, err
}
return projectWriteModel, nil
}
func (c *Commands) getSAMLEntityIdsWriteModelByProjectID(ctx context.Context, projectID, resourceOwner string) (*SAMLEntityIDsWriteModel, error) {
samlEntityIDsAgg := NewSAMLEntityIDsWriteModel(projectID, resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, samlEntityIDsAgg)
if err != nil {
return nil, err
}
return samlEntityIDsAgg, nil
}