mirror of
https://github.com/zitadel/zitadel.git
synced 2025-07-15 14:58:36 +00:00

# Which Problems Are Solved The commands for the resource based v2beta AuthorizationService API are added. Authorizations, previously knows as user grants, give a user in a specific organization and project context roles. The project can be owned or granted. The given roles can be used to restrict access within the projects applications. The commands for the resource based v2beta InteralPermissionService API are added. Administrators, previously knows as memberships, give a user in a specific organization and project context roles. The project can be owned or granted. The give roles give the user permissions to manage different resources in Zitadel. API definitions from https://github.com/zitadel/zitadel/issues/9165 are implemented. Contains endpoints for user metadata. # How the Problems Are Solved ### New Methods - CreateAuthorization - UpdateAuthorization - DeleteAuthorization - ActivateAuthorization - DeactivateAuthorization - ListAuthorizations - CreateAdministrator - UpdateAdministrator - DeleteAdministrator - ListAdministrators - SetUserMetadata to set metadata on a user - DeleteUserMetadata to delete metadata on a user - ListUserMetadata to query for metadata of a user ## Deprecated Methods ### v1.ManagementService - GetUserGrantByID - ListUserGrants - AddUserGrant - UpdateUserGrant - DeactivateUserGrant - ReactivateUserGrant - RemoveUserGrant - BulkRemoveUserGrant ### v1.AuthService - ListMyUserGrants - ListMyProjectPermissions # Additional Changes - Permission checks for metadata functionality on query and command side - correct existence checks for resources, for example you can only be an administrator on an existing project - combined all member tables to singular query for the administrators - add permission checks for command an query side functionality - combined functions on command side where necessary for easier maintainability # Additional Context Closes #9165 --------- Co-authored-by: Elio Bischof <elio@zitadel.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Livio Spring <livio.a@gmail.com>
803 lines
24 KiB
Go
803 lines
24 KiB
Go
package command
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"github.com/zitadel/zitadel/internal/api/authz"
|
|
http_util "github.com/zitadel/zitadel/internal/api/http"
|
|
"github.com/zitadel/zitadel/internal/command/preparation"
|
|
"github.com/zitadel/zitadel/internal/domain"
|
|
"github.com/zitadel/zitadel/internal/eventstore"
|
|
"github.com/zitadel/zitadel/internal/repository/org"
|
|
"github.com/zitadel/zitadel/internal/repository/project"
|
|
"github.com/zitadel/zitadel/internal/repository/user"
|
|
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
|
"github.com/zitadel/zitadel/internal/zerrors"
|
|
)
|
|
|
|
// InstanceOrgSetup is used for the first organisation in the instance setup.
|
|
// It used to be called OrgSetup, which now allows multiple Users, but it's used in the config.yaml and therefore
|
|
// a breaking change was not possible.
|
|
type InstanceOrgSetup struct {
|
|
Name string
|
|
CustomDomain string
|
|
Human *AddHuman
|
|
Machine *AddMachine
|
|
LoginClient *AddLoginClient
|
|
Roles []string
|
|
}
|
|
|
|
type AddLoginClient struct {
|
|
Machine *Machine
|
|
Pat *AddPat
|
|
}
|
|
|
|
type OrgSetup struct {
|
|
Name string
|
|
CustomDomain string
|
|
Admins []*OrgSetupAdmin
|
|
OrgID string
|
|
}
|
|
|
|
// OrgSetupAdmin describes a user to be created (Human / Machine) or an existing (ID) to be used for an org setup.
|
|
type OrgSetupAdmin struct {
|
|
ID string
|
|
Human *AddHuman
|
|
Machine *AddMachine
|
|
Roles []string
|
|
}
|
|
|
|
type orgSetupCommands struct {
|
|
validations []preparation.Validation
|
|
aggregate *org.Aggregate
|
|
commands *Commands
|
|
|
|
admins []*OrgSetupAdmin
|
|
pats []*PersonalAccessToken
|
|
machineKeys []*MachineKey
|
|
}
|
|
|
|
type CreatedOrg struct {
|
|
ObjectDetails *domain.ObjectDetails
|
|
OrgAdmins []OrgAdmin
|
|
}
|
|
|
|
type OrgAdmin interface {
|
|
GetID() string
|
|
}
|
|
|
|
type CreatedOrgAdmin struct {
|
|
ID string
|
|
EmailCode *string
|
|
PhoneCode *string
|
|
PAT *PersonalAccessToken
|
|
MachineKey *MachineKey
|
|
}
|
|
|
|
func (a *CreatedOrgAdmin) GetID() string {
|
|
return a.ID
|
|
}
|
|
|
|
type AssignedOrgAdmin struct {
|
|
ID string
|
|
}
|
|
|
|
func (a *AssignedOrgAdmin) GetID() string {
|
|
return a.ID
|
|
}
|
|
|
|
func (o *OrgSetup) Validate() (err error) {
|
|
if o.OrgID != "" && strings.TrimSpace(o.OrgID) == "" {
|
|
return zerrors.ThrowInvalidArgument(nil, "ORG-4ABd3", "Errors.Invalid.Argument")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Commands) setUpOrgWithIDs(ctx context.Context, o *OrgSetup, orgID string, allowInitialMail bool, userIDs ...string) (_ *CreatedOrg, err error) {
|
|
cmds := c.newOrgSetupCommands(ctx, orgID, o)
|
|
for _, admin := range o.Admins {
|
|
if err = cmds.setupOrgAdmin(admin, allowInitialMail); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if err = cmds.addCustomDomain(o.CustomDomain, userIDs); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return cmds.push(ctx)
|
|
}
|
|
|
|
func (c *Commands) newOrgSetupCommands(ctx context.Context, orgID string, orgSetup *OrgSetup) *orgSetupCommands {
|
|
orgAgg := org.NewAggregate(orgID)
|
|
validations := []preparation.Validation{
|
|
AddOrgCommand(ctx, orgAgg, orgSetup.Name),
|
|
}
|
|
return &orgSetupCommands{
|
|
validations: validations,
|
|
aggregate: orgAgg,
|
|
commands: c,
|
|
admins: orgSetup.Admins,
|
|
}
|
|
}
|
|
|
|
func (c *orgSetupCommands) setupOrgAdmin(admin *OrgSetupAdmin, allowInitialMail bool) error {
|
|
if admin.ID != "" {
|
|
c.validations = append(c.validations, c.commands.AddOrgMemberCommand(&AddOrgMember{OrgID: c.aggregate.ID, UserID: admin.ID, Roles: orgAdminRoles(admin.Roles)}))
|
|
return nil
|
|
}
|
|
|
|
var userID string
|
|
if admin.Human != nil && admin.Human.ID != "" {
|
|
userID = admin.Human.ID
|
|
} else {
|
|
var err error
|
|
userID, err = c.commands.idGenerator.Next()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if admin.Human != nil {
|
|
admin.Human.ID = userID
|
|
c.validations = append(c.validations, c.commands.AddHumanCommand(admin.Human, c.aggregate.ID, c.commands.userPasswordHasher, c.commands.userEncryption, allowInitialMail))
|
|
} else if admin.Machine != nil {
|
|
admin.Machine.Machine.AggregateID = userID
|
|
if err := c.setupOrgAdminMachine(c.aggregate, admin.Machine); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
c.validations = append(c.validations, c.commands.AddOrgMemberCommand(&AddOrgMember{OrgID: c.aggregate.ID, UserID: userID, Roles: orgAdminRoles(admin.Roles)}))
|
|
return nil
|
|
}
|
|
|
|
func (c *orgSetupCommands) setupOrgAdminMachine(orgAgg *org.Aggregate, machine *AddMachine) error {
|
|
userAgg := user.NewAggregate(machine.Machine.AggregateID, orgAgg.ID)
|
|
c.validations = append(c.validations, AddMachineCommand(userAgg, machine.Machine))
|
|
var pat *PersonalAccessToken
|
|
var machineKey *MachineKey
|
|
if machine.Pat != nil {
|
|
pat = NewPersonalAccessToken(orgAgg.ID, machine.Machine.AggregateID, machine.Pat.ExpirationDate, machine.Pat.Scopes, domain.UserTypeMachine)
|
|
tokenID, err := c.commands.idGenerator.Next()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pat.TokenID = tokenID
|
|
c.pats = append(c.pats, pat)
|
|
c.validations = append(c.validations, prepareAddPersonalAccessToken(pat, c.commands.keyAlgorithm))
|
|
}
|
|
if machine.MachineKey != nil {
|
|
machineKey = NewMachineKey(orgAgg.ID, machine.Machine.AggregateID, machine.MachineKey.ExpirationDate, machine.MachineKey.Type)
|
|
keyID, err := c.commands.idGenerator.Next()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
machineKey.KeyID = keyID
|
|
c.machineKeys = append(c.machineKeys, machineKey)
|
|
c.validations = append(c.validations, prepareAddUserMachineKey(machineKey, c.commands.keySize))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *orgSetupCommands) addCustomDomain(domain string, userIDs []string) error {
|
|
if domain != "" {
|
|
c.validations = append(c.validations, c.commands.prepareAddOrgDomain(c.aggregate, domain, userIDs))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func orgAdminRoles(roles []string) []string {
|
|
if len(roles) > 0 {
|
|
return roles
|
|
}
|
|
return []string{domain.RoleOrgOwner}
|
|
}
|
|
|
|
func (c *orgSetupCommands) push(ctx context.Context) (_ *CreatedOrg, err error) {
|
|
cmds, err := preparation.PrepareCommands(ctx, c.commands.eventstore.Filter, c.validations...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
events, err := c.commands.eventstore.Push(ctx, cmds...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &CreatedOrg{
|
|
ObjectDetails: &domain.ObjectDetails{
|
|
Sequence: events[len(events)-1].Sequence(),
|
|
EventDate: events[len(events)-1].CreatedAt(),
|
|
ResourceOwner: c.aggregate.ID,
|
|
},
|
|
OrgAdmins: c.createdAdmins(),
|
|
}, nil
|
|
}
|
|
|
|
func (c *orgSetupCommands) createdAdmins() []OrgAdmin {
|
|
users := make([]OrgAdmin, 0, len(c.admins))
|
|
for _, admin := range c.admins {
|
|
if admin.ID != "" && admin.Human == nil {
|
|
users = append(users, &AssignedOrgAdmin{ID: admin.ID})
|
|
continue
|
|
}
|
|
if admin.Human != nil {
|
|
users = append(users, c.createdHumanAdmin(admin))
|
|
continue
|
|
}
|
|
if admin.Machine != nil {
|
|
users = append(users, c.createdMachineAdmin(admin))
|
|
}
|
|
}
|
|
return users
|
|
}
|
|
|
|
func (c *orgSetupCommands) createdHumanAdmin(admin *OrgSetupAdmin) *CreatedOrgAdmin {
|
|
createdAdmin := &CreatedOrgAdmin{
|
|
ID: admin.Human.ID,
|
|
}
|
|
if admin.Human.EmailCode != nil {
|
|
createdAdmin.EmailCode = admin.Human.EmailCode
|
|
}
|
|
return createdAdmin
|
|
}
|
|
|
|
func (c *orgSetupCommands) createdMachineAdmin(admin *OrgSetupAdmin) *CreatedOrgAdmin {
|
|
createdAdmin := &CreatedOrgAdmin{
|
|
ID: admin.Machine.Machine.AggregateID,
|
|
}
|
|
if admin.Machine.Pat != nil {
|
|
for _, pat := range c.pats {
|
|
if pat.AggregateID == createdAdmin.ID {
|
|
createdAdmin.PAT = pat
|
|
}
|
|
}
|
|
}
|
|
if admin.Machine.MachineKey != nil {
|
|
for _, key := range c.machineKeys {
|
|
if key.AggregateID == createdAdmin.ID {
|
|
createdAdmin.MachineKey = key
|
|
}
|
|
}
|
|
}
|
|
return createdAdmin
|
|
}
|
|
|
|
func (c *Commands) SetUpOrg(ctx context.Context, o *OrgSetup, allowInitialMail bool, userIDs ...string) (*CreatedOrg, error) {
|
|
if err := o.Validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if o.OrgID == "" {
|
|
var err error
|
|
o.OrgID, err = c.idGenerator.Next()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return c.setUpOrgWithIDs(ctx, o, o.OrgID, allowInitialMail, userIDs...)
|
|
}
|
|
|
|
// AddOrgCommand defines the commands to create a new org,
|
|
// this includes the verified default domain
|
|
func AddOrgCommand(ctx context.Context, a *org.Aggregate, name string) preparation.Validation {
|
|
return func() (preparation.CreateCommands, error) {
|
|
if name = strings.TrimSpace(name); name == "" {
|
|
return nil, zerrors.ThrowInvalidArgument(nil, "ORG-mruNY", "Errors.Invalid.Argument")
|
|
}
|
|
defaultDomain, err := domain.NewIAMDomainName(name, http_util.DomainContext(ctx).RequestedDomain())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
|
return []eventstore.Command{
|
|
org.NewOrgAddedEvent(ctx, &a.Aggregate, name),
|
|
org.NewDomainAddedEvent(ctx, &a.Aggregate, defaultDomain),
|
|
org.NewDomainVerifiedEvent(ctx, &a.Aggregate, defaultDomain),
|
|
org.NewDomainPrimarySetEvent(ctx, &a.Aggregate, defaultDomain),
|
|
}, nil
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
func (c *Commands) getOrg(ctx context.Context, orgID string) (*domain.Org, error) {
|
|
writeModel, err := c.getOrgWriteModelByID(ctx, orgID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !isOrgStateExists(writeModel.State) {
|
|
return nil, zerrors.ThrowInternal(err, "COMMAND-4M9sf", "Errors.Org.NotFound")
|
|
}
|
|
return orgWriteModelToOrg(writeModel), nil
|
|
}
|
|
|
|
func (c *Commands) checkOrgExists(ctx context.Context, orgID string) error {
|
|
orgWriteModel, err := c.getOrgWriteModelByID(ctx, orgID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !isOrgStateExists(orgWriteModel.State) {
|
|
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-QXPGs", "Errors.Org.NotFound")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Commands) AddOrgWithID(ctx context.Context, name, userID, resourceOwner, orgID string, setOrgInactive bool, claimedUserIDs []string) (_ *domain.Org, err error) {
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
defer func() { span.EndWithError(err) }()
|
|
|
|
existingOrg, err := c.getOrgWriteModelByID(ctx, orgID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if existingOrg.State != domain.OrgStateUnspecified {
|
|
return nil, zerrors.ThrowNotFound(nil, "ORG-lapo2m", "Errors.Org.AlreadyExisting")
|
|
}
|
|
|
|
return c.addOrgWithIDAndMember(ctx, name, userID, resourceOwner, orgID, setOrgInactive, claimedUserIDs)
|
|
}
|
|
|
|
func (c *Commands) AddOrg(ctx context.Context, name, userID, resourceOwner string, claimedUserIDs []string) (*domain.Org, error) {
|
|
if name = strings.TrimSpace(name); name == "" {
|
|
return nil, zerrors.ThrowInvalidArgument(nil, "EVENT-Mf9sd", "Errors.Org.Invalid")
|
|
}
|
|
|
|
orgID, err := c.idGenerator.Next()
|
|
if err != nil {
|
|
return nil, zerrors.ThrowInternal(err, "COMMA-OwciI", "Errors.Internal")
|
|
}
|
|
|
|
return c.addOrgWithIDAndMember(ctx, name, userID, resourceOwner, orgID, false, claimedUserIDs)
|
|
}
|
|
|
|
func (c *Commands) addOrgWithIDAndMember(ctx context.Context, name, userID, resourceOwner, orgID string, setOrgInactive bool, claimedUserIDs []string) (_ *domain.Org, err error) {
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
defer func() { span.EndWithError(err) }()
|
|
|
|
orgAgg, addedOrg, events, err := c.addOrgWithID(ctx, &domain.Org{Name: name}, orgID, claimedUserIDs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
_, err = c.checkUserExists(ctx, userID, resourceOwner)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
addMember := &AddOrgMember{OrgID: orgAgg.ID, UserID: userID, Roles: []string{domain.RoleOrgOwner}}
|
|
if err := addMember.IsValid(c.zitadelRoles); err != nil {
|
|
return nil, err
|
|
}
|
|
events = append(events, org.NewMemberAddedEvent(ctx, orgAgg, addMember.UserID, addMember.Roles...))
|
|
if setOrgInactive {
|
|
deactivateOrgEvent := org.NewOrgDeactivatedEvent(ctx, orgAgg)
|
|
events = append(events, deactivateOrgEvent)
|
|
}
|
|
pushedEvents, err := c.eventstore.Push(ctx, events...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = AppendAndReduce(addedOrg, pushedEvents...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return orgWriteModelToOrg(addedOrg), nil
|
|
}
|
|
|
|
func (c *Commands) ChangeOrg(ctx context.Context, orgID, name string) (*domain.ObjectDetails, error) {
|
|
name = strings.TrimSpace(name)
|
|
if orgID == "" || name == "" {
|
|
return nil, zerrors.ThrowInvalidArgument(nil, "EVENT-Mf9sd", "Errors.Org.Invalid")
|
|
}
|
|
|
|
orgWriteModel, err := c.getOrgWriteModelByID(ctx, orgID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !isOrgStateExists(orgWriteModel.State) {
|
|
return nil, zerrors.ThrowNotFound(nil, "ORG-1MRds", "Errors.Org.NotFound")
|
|
}
|
|
if orgWriteModel.Name == name {
|
|
return nil, zerrors.ThrowPreconditionFailed(nil, "ORG-4VSdf", "Errors.Org.NotChanged")
|
|
}
|
|
orgAgg := OrgAggregateFromWriteModel(&orgWriteModel.WriteModel)
|
|
events := make([]eventstore.Command, 0)
|
|
events = append(events, org.NewOrgChangedEvent(ctx, orgAgg, orgWriteModel.Name, name))
|
|
changeDomainEvents, err := c.changeDefaultDomain(ctx, orgID, name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(changeDomainEvents) > 0 {
|
|
events = append(events, changeDomainEvents...)
|
|
}
|
|
pushedEvents, err := c.eventstore.Push(ctx, events...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = AppendAndReduce(orgWriteModel, pushedEvents...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return writeModelToObjectDetails(&orgWriteModel.WriteModel), nil
|
|
}
|
|
|
|
func (c *Commands) DeactivateOrg(ctx context.Context, orgID string) (*domain.ObjectDetails, error) {
|
|
orgWriteModel, err := c.getOrgWriteModelByID(ctx, orgID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !isOrgStateExists(orgWriteModel.State) {
|
|
return nil, zerrors.ThrowNotFound(nil, "ORG-oL9nT", "Errors.Org.NotFound")
|
|
}
|
|
if orgWriteModel.State == domain.OrgStateInactive {
|
|
return nil, zerrors.ThrowPreconditionFailed(nil, "EVENT-Dbs2g", "Errors.Org.AlreadyDeactivated")
|
|
}
|
|
orgAgg := OrgAggregateFromWriteModel(&orgWriteModel.WriteModel)
|
|
pushedEvents, err := c.eventstore.Push(ctx, org.NewOrgDeactivatedEvent(ctx, orgAgg))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = AppendAndReduce(orgWriteModel, pushedEvents...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return writeModelToObjectDetails(&orgWriteModel.WriteModel), nil
|
|
}
|
|
|
|
func (c *Commands) ReactivateOrg(ctx context.Context, orgID string) (*domain.ObjectDetails, error) {
|
|
orgWriteModel, err := c.getOrgWriteModelByID(ctx, orgID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !isOrgStateExists(orgWriteModel.State) {
|
|
return nil, zerrors.ThrowNotFound(nil, "ORG-Dgf3g", "Errors.Org.NotFound")
|
|
}
|
|
if orgWriteModel.State == domain.OrgStateActive {
|
|
return nil, zerrors.ThrowPreconditionFailed(nil, "EVENT-bfnrh", "Errors.Org.AlreadyActive")
|
|
}
|
|
orgAgg := OrgAggregateFromWriteModel(&orgWriteModel.WriteModel)
|
|
pushedEvents, err := c.eventstore.Push(ctx, org.NewOrgReactivatedEvent(ctx, orgAgg))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = AppendAndReduce(orgWriteModel, pushedEvents...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
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].CreatedAt(),
|
|
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) {
|
|
instance := authz.GetInstance(ctx)
|
|
if a.ID == instance.DefaultOrganisationID() {
|
|
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMA-wG9p1", "Errors.Org.DefaultOrgNotDeletable")
|
|
}
|
|
|
|
_, 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")
|
|
}
|
|
// "precondition failed" error means the project does not exist, return other errors
|
|
if !zerrors.IsPreconditionFailed(err) {
|
|
return nil, err
|
|
}
|
|
writeModel, err := c.getOrgWriteModelByID(ctx, a.ID)
|
|
if err != nil {
|
|
return nil, zerrors.ThrowPreconditionFailed(err, "COMMA-wG9p1", "Errors.Org.NotFound")
|
|
}
|
|
if !isOrgStateExists(writeModel.State) {
|
|
return nil, zerrors.ThrowNotFound(nil, "COMMA-aps2n", "Errors.Org.NotFound")
|
|
}
|
|
|
|
domainPolicy, err := c.domainPolicyWriteModel(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.AggregateType).
|
|
EventTypes(
|
|
user.UserIDPLinkAddedType, user.UserIDPLinkRemovedType, user.UserIDPLinkCascadeRemovedType,
|
|
).Builder())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
links := make([]*domain.UserIDPLink, 0)
|
|
for _, event := range events {
|
|
switch eventTyped := event.(type) {
|
|
case *user.UserIDPLinkAddedEvent:
|
|
links = append(links, &domain.UserIDPLink{
|
|
IDPConfigID: eventTyped.IDPConfigID,
|
|
ExternalUserID: eventTyped.ExternalUserID,
|
|
DisplayName: eventTyped.DisplayName,
|
|
})
|
|
case *user.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.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.AggregateType).
|
|
EventTypes(
|
|
user.HumanAddedType,
|
|
user.MachineAddedEventType,
|
|
user.HumanRegisteredType,
|
|
user.UserDomainClaimedType,
|
|
user.UserUserNameChangedType,
|
|
user.UserRemovedType,
|
|
).Builder())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
users := make([]userIDName, 0)
|
|
for _, event := range events {
|
|
switch eventTyped := event.(type) {
|
|
case *user.HumanAddedEvent:
|
|
users = append(users, userIDName{eventTyped.UserName, eventTyped.Aggregate().ID})
|
|
case *user.MachineAddedEvent:
|
|
users = append(users, userIDName{eventTyped.UserName, eventTyped.Aggregate().ID})
|
|
case *user.HumanRegisteredEvent:
|
|
users = append(users, userIDName{eventTyped.UserName, eventTyped.Aggregate().ID})
|
|
case *user.DomainClaimedEvent:
|
|
for i := range users {
|
|
if users[i].id == eventTyped.Aggregate().ID {
|
|
users[i].name = eventTyped.UserName
|
|
}
|
|
}
|
|
case *user.UsernameChangedEvent:
|
|
for i := range users {
|
|
if users[i].id == eventTyped.Aggregate().ID {
|
|
users[i].name = eventTyped.UserName
|
|
}
|
|
}
|
|
case *user.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().
|
|
AggregateTypes(org.AggregateType).
|
|
AggregateIDs(id).
|
|
EventTypes(
|
|
org.OrgAddedEventType,
|
|
org.OrgDeactivatedEventType,
|
|
org.OrgReactivatedEventType,
|
|
org.OrgRemovedEventType,
|
|
).Builder())
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
for _, event := range events {
|
|
switch event.(type) {
|
|
case *org.OrgAddedEvent, *org.OrgReactivatedEvent:
|
|
exists = true
|
|
case *org.OrgDeactivatedEvent, *org.OrgRemovedEvent:
|
|
exists = false
|
|
}
|
|
}
|
|
|
|
return exists, nil
|
|
}
|
|
|
|
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, zerrors.ThrowInvalidArgument(nil, "COMM-deLSk", "Errors.Org.Invalid")
|
|
}
|
|
|
|
organisation.AggregateID = orgID
|
|
organisation.AddIAMDomain(http_util.DomainContext(ctx).RequestedDomain())
|
|
addedOrg := NewOrgWriteModel(organisation.AggregateID)
|
|
|
|
orgAgg := OrgAggregateFromWriteModel(&addedOrg.WriteModel)
|
|
events := []eventstore.Command{
|
|
org.NewOrgAddedEvent(ctx, orgAgg, organisation.Name),
|
|
}
|
|
for _, orgDomain := range organisation.Domains {
|
|
orgDomainEvents, err := c.addOrgDomain(ctx, orgAgg, NewOrgDomainWriteModel(orgAgg.ID, orgDomain.Domain), orgDomain, claimedUserIDs)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
events = append(events, orgDomainEvents...)
|
|
}
|
|
return orgAgg, addedOrg, events, nil
|
|
}
|
|
|
|
func (c *Commands) getOrgWriteModelByID(ctx context.Context, orgID string) (_ *OrgWriteModel, err error) {
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
defer func() { span.EndWithError(err) }()
|
|
|
|
orgWriteModel := NewOrgWriteModel(orgID)
|
|
err = c.eventstore.FilterToQueryReducer(ctx, orgWriteModel)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
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
|
|
}
|