mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 20:57:31 +00:00
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>
This commit is contained in:
@@ -8,9 +8,9 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/command/preparation"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
caos_errs "github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/org"
|
||||
"github.com/zitadel/zitadel/internal/repository/project"
|
||||
user_repo "github.com/zitadel/zitadel/internal/repository/user"
|
||||
)
|
||||
|
||||
@@ -105,8 +105,8 @@ func (c *Commands) getOrg(ctx context.Context, orgID string) (*domain.Org, error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if writeModel.State == domain.OrgStateUnspecified || writeModel.State == domain.OrgStateRemoved {
|
||||
return nil, caos_errs.ThrowInternal(err, "COMMAND-4M9sf", "Errors.Org.NotFound")
|
||||
if !isOrgStateExists(writeModel.State) {
|
||||
return nil, errors.ThrowInternal(err, "COMMAND-4M9sf", "Errors.Org.NotFound")
|
||||
}
|
||||
return orgWriteModelToOrg(writeModel), nil
|
||||
}
|
||||
@@ -116,8 +116,8 @@ func (c *Commands) checkOrgExists(ctx context.Context, orgID string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if orgWriteModel.State == domain.OrgStateUnspecified || orgWriteModel.State == domain.OrgStateRemoved {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-QXPGs", "Errors.Org.NotFound")
|
||||
if !isOrgStateExists(orgWriteModel.State) {
|
||||
return errors.ThrowPreconditionFailed(nil, "COMMAND-QXPGs", "Errors.Org.NotFound")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -128,7 +128,7 @@ func (c *Commands) AddOrgWithID(ctx context.Context, name, userID, resourceOwner
|
||||
return nil, err
|
||||
}
|
||||
if existingOrg.State != domain.OrgStateUnspecified {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "ORG-lapo2m", "Errors.Org.AlreadyExisting")
|
||||
return nil, errors.ThrowNotFound(nil, "ORG-lapo2m", "Errors.Org.AlreadyExisting")
|
||||
}
|
||||
|
||||
return c.addOrgWithIDAndMember(ctx, name, userID, resourceOwner, orgID, claimedUserIDs)
|
||||
@@ -136,12 +136,12 @@ func (c *Commands) AddOrgWithID(ctx context.Context, name, userID, resourceOwner
|
||||
|
||||
func (c *Commands) AddOrg(ctx context.Context, name, userID, resourceOwner string, claimedUserIDs []string) (*domain.Org, error) {
|
||||
if name = strings.TrimSpace(name); name == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "EVENT-Mf9sd", "Errors.Org.Invalid")
|
||||
return nil, errors.ThrowInvalidArgument(nil, "EVENT-Mf9sd", "Errors.Org.Invalid")
|
||||
}
|
||||
|
||||
orgID, err := c.idGenerator.Next()
|
||||
if err != nil {
|
||||
return nil, caos_errs.ThrowInternal(err, "COMMA-OwciI", "Errors.Internal")
|
||||
return nil, errors.ThrowInternal(err, "COMMA-OwciI", "Errors.Internal")
|
||||
}
|
||||
|
||||
return c.addOrgWithIDAndMember(ctx, name, userID, resourceOwner, orgID, claimedUserIDs)
|
||||
@@ -176,18 +176,18 @@ func (c *Commands) addOrgWithIDAndMember(ctx context.Context, name, userID, reso
|
||||
func (c *Commands) ChangeOrg(ctx context.Context, orgID, name string) (*domain.ObjectDetails, error) {
|
||||
name = strings.TrimSpace(name)
|
||||
if orgID == "" || name == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "EVENT-Mf9sd", "Errors.Org.Invalid")
|
||||
return nil, errors.ThrowInvalidArgument(nil, "EVENT-Mf9sd", "Errors.Org.Invalid")
|
||||
}
|
||||
|
||||
orgWriteModel, err := c.getOrgWriteModelByID(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if orgWriteModel.State == domain.OrgStateUnspecified || orgWriteModel.State == domain.OrgStateRemoved {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "ORG-1MRds", "Errors.Org.NotFound")
|
||||
if !isOrgStateExists(orgWriteModel.State) {
|
||||
return nil, errors.ThrowNotFound(nil, "ORG-1MRds", "Errors.Org.NotFound")
|
||||
}
|
||||
if orgWriteModel.Name == name {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "ORG-4VSdf", "Errors.Org.NotChanged")
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "ORG-4VSdf", "Errors.Org.NotChanged")
|
||||
}
|
||||
orgAgg := OrgAggregateFromWriteModel(&orgWriteModel.WriteModel)
|
||||
events := make([]eventstore.Command, 0)
|
||||
@@ -215,11 +215,11 @@ func (c *Commands) DeactivateOrg(ctx context.Context, orgID string) (*domain.Obj
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if orgWriteModel.State == domain.OrgStateUnspecified || orgWriteModel.State == domain.OrgStateRemoved {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "ORG-oL9nT", "Errors.Org.NotFound")
|
||||
if !isOrgStateExists(orgWriteModel.State) {
|
||||
return nil, errors.ThrowNotFound(nil, "ORG-oL9nT", "Errors.Org.NotFound")
|
||||
}
|
||||
if orgWriteModel.State == domain.OrgStateInactive {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-Dbs2g", "Errors.Org.AlreadyDeactivated")
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-Dbs2g", "Errors.Org.AlreadyDeactivated")
|
||||
}
|
||||
orgAgg := OrgAggregateFromWriteModel(&orgWriteModel.WriteModel)
|
||||
pushedEvents, err := c.eventstore.Push(ctx, org.NewOrgDeactivatedEvent(ctx, orgAgg))
|
||||
@@ -238,11 +238,11 @@ func (c *Commands) ReactivateOrg(ctx context.Context, orgID string) (*domain.Obj
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if orgWriteModel.State == domain.OrgStateUnspecified || orgWriteModel.State == domain.OrgStateRemoved {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "ORG-Dgf3g", "Errors.Org.NotFound")
|
||||
if !isOrgStateExists(orgWriteModel.State) {
|
||||
return nil, errors.ThrowNotFound(nil, "ORG-Dgf3g", "Errors.Org.NotFound")
|
||||
}
|
||||
if orgWriteModel.State == domain.OrgStateActive {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-bfnrh", "Errors.Org.AlreadyActive")
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-bfnrh", "Errors.Org.AlreadyActive")
|
||||
}
|
||||
orgAgg := OrgAggregateFromWriteModel(&orgWriteModel.WriteModel)
|
||||
pushedEvents, err := c.eventstore.Push(ctx, org.NewOrgReactivatedEvent(ctx, orgAgg))
|
||||
@@ -256,8 +256,251 @@ func (c *Commands) ReactivateOrg(ctx context.Context, orgID string) (*domain.Obj
|
||||
return writeModelToObjectDetails(&orgWriteModel.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) RemoveOrg(ctx context.Context, id string) (*domain.ObjectDetails, error) {
|
||||
orgAgg := org.NewAggregate(id)
|
||||
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareRemoveOrg(orgAgg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &domain.ObjectDetails{
|
||||
Sequence: events[len(events)-1].Sequence(),
|
||||
EventDate: events[len(events)-1].CreationDate(),
|
||||
ResourceOwner: events[len(events)-1].Aggregate().InstanceID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Commands) prepareRemoveOrg(a *org.Aggregate) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
writeModel, err := c.getOrgWriteModelByID(ctx, a.ID)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowPreconditionFailed(err, "COMMA-wG9p1", "Errors.Org.NotFound")
|
||||
}
|
||||
if !isOrgStateExists(writeModel.State) {
|
||||
return nil, errors.ThrowNotFound(nil, "COMMA-aps2n", "Errors.Org.NotFound")
|
||||
}
|
||||
|
||||
domainPolicy, err := c.getOrgDomainPolicy(ctx, a.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
usernames, err := OrgUsers(ctx, filter, a.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
domains, err := OrgDomains(ctx, filter, a.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
links, err := OrgUserIDPLinks(ctx, filter, a.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entityIds, err := OrgSamlEntityIDs(ctx, filter, a.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []eventstore.Command{org.NewOrgRemovedEvent(ctx, &a.Aggregate, writeModel.Name, usernames, domainPolicy.UserLoginMustBeDomain, domains, links, entityIds)}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func OrgUserIDPLinks(ctx context.Context, filter preparation.FilterToQueryReducer, orgID string) ([]*domain.UserIDPLink, error) {
|
||||
events, err := filter(ctx, eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(orgID).
|
||||
OrderAsc().
|
||||
AddQuery().
|
||||
AggregateTypes(user_repo.AggregateType).
|
||||
EventTypes(
|
||||
user_repo.UserIDPLinkAddedType, user_repo.UserIDPLinkRemovedType, user_repo.UserIDPLinkCascadeRemovedType,
|
||||
).Builder())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
links := make([]*domain.UserIDPLink, 0)
|
||||
for _, event := range events {
|
||||
switch eventTyped := event.(type) {
|
||||
case *user_repo.UserIDPLinkAddedEvent:
|
||||
links = append(links, &domain.UserIDPLink{
|
||||
IDPConfigID: eventTyped.IDPConfigID,
|
||||
ExternalUserID: eventTyped.ExternalUserID,
|
||||
DisplayName: eventTyped.DisplayName,
|
||||
})
|
||||
case *user_repo.UserIDPLinkRemovedEvent:
|
||||
for i := range links {
|
||||
if links[i].ExternalUserID == eventTyped.ExternalUserID &&
|
||||
links[i].IDPConfigID == eventTyped.IDPConfigID {
|
||||
links[i] = links[len(links)-1]
|
||||
links[len(links)-1] = nil
|
||||
links = links[:len(links)-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
case *user_repo.UserIDPLinkCascadeRemovedEvent:
|
||||
for i := range links {
|
||||
if links[i].ExternalUserID == eventTyped.ExternalUserID &&
|
||||
links[i].IDPConfigID == eventTyped.IDPConfigID {
|
||||
links[i] = links[len(links)-1]
|
||||
links[len(links)-1] = nil
|
||||
links = links[:len(links)-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return links, nil
|
||||
}
|
||||
|
||||
type samlEntityID struct {
|
||||
appID string
|
||||
entityID string
|
||||
}
|
||||
|
||||
func OrgSamlEntityIDs(ctx context.Context, filter preparation.FilterToQueryReducer, orgID string) ([]string, error) {
|
||||
events, err := filter(ctx, eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(orgID).
|
||||
OrderAsc().
|
||||
AddQuery().
|
||||
AggregateTypes(project.AggregateType).
|
||||
EventTypes(
|
||||
project.SAMLConfigAddedType, project.SAMLConfigChangedType, project.ApplicationRemovedType,
|
||||
).Builder())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entityIDs := make([]samlEntityID, 0)
|
||||
for _, event := range events {
|
||||
switch eventTyped := event.(type) {
|
||||
case *project.SAMLConfigAddedEvent:
|
||||
entityIDs = append(entityIDs, samlEntityID{appID: eventTyped.AppID, entityID: eventTyped.EntityID})
|
||||
case *project.SAMLConfigChangedEvent:
|
||||
for i := range entityIDs {
|
||||
if entityIDs[i].appID == eventTyped.AppID {
|
||||
entityIDs[i].entityID = eventTyped.EntityID
|
||||
break
|
||||
}
|
||||
}
|
||||
case *project.ApplicationRemovedEvent:
|
||||
for i := range entityIDs {
|
||||
if entityIDs[i].appID == eventTyped.AppID {
|
||||
entityIDs[i] = entityIDs[len(entityIDs)-1]
|
||||
entityIDs = entityIDs[:len(entityIDs)-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ids := make([]string, len(entityIDs))
|
||||
for i := range entityIDs {
|
||||
ids[i] = entityIDs[i].entityID
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func OrgDomains(ctx context.Context, filter preparation.FilterToQueryReducer, orgID string) ([]string, error) {
|
||||
events, err := filter(ctx, eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(orgID).
|
||||
OrderAsc().
|
||||
AddQuery().
|
||||
AggregateTypes(org.AggregateType).
|
||||
EventTypes(
|
||||
org.OrgDomainVerifiedEventType,
|
||||
org.OrgDomainRemovedEventType,
|
||||
).Builder())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
names := make([]string, 0)
|
||||
for _, event := range events {
|
||||
switch eventTyped := event.(type) {
|
||||
case *org.DomainVerifiedEvent:
|
||||
names = append(names, eventTyped.Domain)
|
||||
case *org.DomainRemovedEvent:
|
||||
for i := range names {
|
||||
if names[i] == eventTyped.Domain {
|
||||
names[i] = names[len(names)-1]
|
||||
names = names[:len(names)-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return names, nil
|
||||
}
|
||||
|
||||
type userIDName struct {
|
||||
name string
|
||||
id string
|
||||
}
|
||||
|
||||
func OrgUsers(ctx context.Context, filter preparation.FilterToQueryReducer, orgID string) ([]string, error) {
|
||||
events, err := filter(ctx, eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
InstanceID(authz.GetInstance(ctx).InstanceID()).
|
||||
ResourceOwner(orgID).
|
||||
OrderAsc().
|
||||
AddQuery().
|
||||
AggregateTypes(user_repo.AggregateType).
|
||||
EventTypes(
|
||||
user_repo.HumanAddedType,
|
||||
user_repo.MachineAddedEventType,
|
||||
user_repo.HumanRegisteredType,
|
||||
user_repo.UserDomainClaimedType,
|
||||
user_repo.UserUserNameChangedType,
|
||||
user_repo.UserRemovedType,
|
||||
).Builder())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
users := make([]userIDName, 0)
|
||||
for _, event := range events {
|
||||
switch eventTyped := event.(type) {
|
||||
case *user_repo.HumanAddedEvent:
|
||||
users = append(users, userIDName{eventTyped.UserName, eventTyped.Aggregate().ID})
|
||||
case *user_repo.MachineAddedEvent:
|
||||
users = append(users, userIDName{eventTyped.UserName, eventTyped.Aggregate().ID})
|
||||
case *user_repo.HumanRegisteredEvent:
|
||||
users = append(users, userIDName{eventTyped.UserName, eventTyped.Aggregate().ID})
|
||||
case *user_repo.DomainClaimedEvent:
|
||||
for i := range users {
|
||||
if users[i].id == eventTyped.Aggregate().ID {
|
||||
users[i].name = eventTyped.UserName
|
||||
}
|
||||
}
|
||||
case *user_repo.UsernameChangedEvent:
|
||||
for i := range users {
|
||||
if users[i].id == eventTyped.Aggregate().ID {
|
||||
users[i].name = eventTyped.UserName
|
||||
}
|
||||
}
|
||||
case *user_repo.UserRemovedEvent:
|
||||
for i := range users {
|
||||
if users[i].id == eventTyped.Aggregate().ID {
|
||||
users[i] = users[len(users)-1]
|
||||
users = users[:len(users)-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
names := make([]string, len(users))
|
||||
for i := range users {
|
||||
names[i] = users[i].name
|
||||
}
|
||||
return names, nil
|
||||
}
|
||||
|
||||
func ExistsOrg(ctx context.Context, filter preparation.FilterToQueryReducer, id string) (exists bool, err error) {
|
||||
events, err := filter(ctx, eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
InstanceID(authz.GetInstance(ctx).InstanceID()).
|
||||
ResourceOwner(id).
|
||||
OrderAsc().
|
||||
AddQuery().
|
||||
@@ -287,7 +530,7 @@ func ExistsOrg(ctx context.Context, filter preparation.FilterToQueryReducer, id
|
||||
|
||||
func (c *Commands) addOrgWithID(ctx context.Context, organisation *domain.Org, orgID string, claimedUserIDs []string) (_ *eventstore.Aggregate, _ *OrgWriteModel, _ []eventstore.Command, err error) {
|
||||
if !organisation.IsValid() {
|
||||
return nil, nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMM-deLSk", "Errors.Org.Invalid")
|
||||
return nil, nil, nil, errors.ThrowInvalidArgument(nil, "COMM-deLSk", "Errors.Org.Invalid")
|
||||
}
|
||||
|
||||
organisation.AggregateID = orgID
|
||||
@@ -316,3 +559,16 @@ func (c *Commands) getOrgWriteModelByID(ctx context.Context, orgID string) (*Org
|
||||
}
|
||||
return orgWriteModel, nil
|
||||
}
|
||||
|
||||
func isOrgStateExists(state domain.OrgState) bool {
|
||||
return !hasOrgState(state, domain.OrgStateRemoved, domain.OrgStateUnspecified)
|
||||
}
|
||||
|
||||
func hasOrgState(check domain.OrgState, states ...domain.OrgState) bool {
|
||||
for _, state := range states {
|
||||
if check == state {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
Reference in New Issue
Block a user