mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-24 03:16:48 +00:00
# Which Problems Are Solved 1. Adding users to user groups and removing users from user groups. 2. Searching for users in user groups by group IDs or user IDs # How the Problems Are Solved By adding: 1. The API definitions to manage users in users groups 3. The command-layer implementation of adding users/removing users to/from user groups. 4. The projection table group_users1 5. Query-side implementation to search for users in user groups # Additional Changes 1. Remove debug statements from unit tests. 2. Fix removal of groups when orgs are removed 3. Add unit tests for groups projection # Additional Context * Related to #9702 * Follow-up for PRs * https://github.com/zitadel/zitadel/pull/10455 * https://github.com/zitadel/zitadel/pull/10758 * https://github.com/zitadel/zitadel/pull/10853
182 lines
5.1 KiB
Go
182 lines
5.1 KiB
Go
package command
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"github.com/zitadel/zitadel/internal/domain"
|
|
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
|
repo "github.com/zitadel/zitadel/internal/repository/group"
|
|
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
|
"github.com/zitadel/zitadel/internal/zerrors"
|
|
)
|
|
|
|
type CreateGroup struct {
|
|
models.ObjectRoot
|
|
|
|
Name string
|
|
Description string
|
|
}
|
|
|
|
func (g *CreateGroup) IsValid() error {
|
|
if strings.TrimSpace(g.Name) == "" {
|
|
return zerrors.ThrowInvalidArgument(nil, "GROUP-m177lN", "Errors.Group.InvalidName")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CreateGroup creates a new user group in an organization
|
|
func (c *Commands) CreateGroup(ctx context.Context, group *CreateGroup) (details *domain.ObjectDetails, err error) {
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
defer func() { span.EndWithError(err) }()
|
|
|
|
// create a unique group ID if not provided
|
|
if group.AggregateID == "" {
|
|
group.AggregateID, err = c.idGenerator.Next()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if err = group.IsValid(); err != nil {
|
|
return nil, err
|
|
}
|
|
if group.ResourceOwner == "" {
|
|
return nil, zerrors.ThrowInvalidArgument(nil, "CMDGRP-msc0Tt", "Errors.Group.MissingOrganizationID")
|
|
}
|
|
|
|
if err = c.checkPermissionCreateGroup(ctx, group.ResourceOwner, group.AggregateID); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// check whether the organization where the group should be created exists
|
|
err = c.checkOrgExists(ctx, group.ResourceOwner)
|
|
if err != nil {
|
|
return nil, zerrors.ThrowPreconditionFailed(nil, "CMDGRP-j1mH8l", "Errors.Org.NotFound")
|
|
}
|
|
|
|
// check if a group with the same ID already exists
|
|
groupWriteModel, err := c.getGroupWriteModelByID(ctx, group.AggregateID, group.ResourceOwner, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if groupWriteModel.State.Exists() {
|
|
return nil, zerrors.ThrowAlreadyExists(nil, "CMDGRP-shRut3", "Errors.Group.AlreadyExists")
|
|
}
|
|
|
|
err = c.pushAppendAndReduce(ctx,
|
|
groupWriteModel,
|
|
repo.NewGroupAddedEvent(ctx,
|
|
GroupAggregateFromWriteModel(ctx, &groupWriteModel.WriteModel),
|
|
group.Name,
|
|
group.Description,
|
|
))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return writeModelToObjectDetails(&groupWriteModel.WriteModel), nil
|
|
}
|
|
|
|
type UpdateGroup struct {
|
|
models.ObjectRoot
|
|
|
|
Name *string
|
|
Description *string
|
|
}
|
|
|
|
func (g *UpdateGroup) IsValid() error {
|
|
if g.Name != nil && strings.TrimSpace(*g.Name) == "" {
|
|
return zerrors.ThrowInvalidArgument(nil, "GROUP-dUNd3r", "Errors.Group.InvalidName")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UpdateGroup updates a user group in an organization
|
|
func (c *Commands) UpdateGroup(ctx context.Context, groupUpdate *UpdateGroup) (details *domain.ObjectDetails, err error) {
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
defer func() { span.EndWithError(err) }()
|
|
|
|
if err = groupUpdate.IsValid(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
existingGroup, err := c.getGroupWriteModelByID(ctx, groupUpdate.AggregateID, groupUpdate.ResourceOwner, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !existingGroup.State.Exists() {
|
|
return nil, zerrors.ThrowNotFound(nil, "CMDGRP-b33zly", "Errors.Group.NotFound")
|
|
}
|
|
|
|
if err = c.checkPermissionUpdateGroup(ctx, existingGroup.ResourceOwner, existingGroup.AggregateID); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
changedEvent := existingGroup.NewChangedEvent(
|
|
ctx,
|
|
GroupAggregateFromWriteModel(ctx, &existingGroup.WriteModel),
|
|
groupUpdate.Name,
|
|
groupUpdate.Description,
|
|
)
|
|
if changedEvent == nil {
|
|
return writeModelToObjectDetails(&existingGroup.WriteModel), nil
|
|
}
|
|
|
|
err = c.pushAppendAndReduce(ctx,
|
|
existingGroup,
|
|
changedEvent)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return writeModelToObjectDetails(&existingGroup.WriteModel), nil
|
|
}
|
|
|
|
// DeleteGroup deletes a user group from an organization
|
|
func (c *Commands) DeleteGroup(ctx context.Context, groupID string) (details *domain.ObjectDetails, err error) {
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
defer func() { span.EndWithError(err) }()
|
|
|
|
existingGroup, err := c.getGroupWriteModelByID(ctx, groupID, "", nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !existingGroup.State.Exists() {
|
|
return writeModelToObjectDetails(&existingGroup.WriteModel), nil
|
|
}
|
|
|
|
if err = c.checkPermissionDeleteGroup(ctx, existingGroup.ResourceOwner, existingGroup.AggregateID); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = c.pushAppendAndReduce(ctx,
|
|
existingGroup,
|
|
repo.NewGroupRemovedEvent(ctx,
|
|
GroupAggregateFromWriteModel(ctx, &existingGroup.WriteModel),
|
|
existingGroup.Name,
|
|
))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return writeModelToObjectDetails(&existingGroup.WriteModel), nil
|
|
}
|
|
|
|
func (c *Commands) getGroupWriteModelByID(ctx context.Context, groupID, orgID string, userIDs []string) (*GroupWriteModel, error) {
|
|
groupWriteModel := NewGroupWriteModel(groupID, orgID, userIDs)
|
|
err := c.eventstore.FilterToQueryReducer(ctx, groupWriteModel)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return groupWriteModel, nil
|
|
}
|
|
|
|
func (c *Commands) checkGroupExists(ctx context.Context, groupID string, userIDs []string) (*GroupWriteModel, error) {
|
|
group, err := c.getGroupWriteModelByID(ctx, groupID, "", userIDs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !group.State.Exists() {
|
|
return nil, zerrors.ThrowPreconditionFailed(nil, "CMDGRP-eQfeur", "Errors.Group.NotFound")
|
|
}
|
|
return group, nil
|
|
}
|