mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 23:57:31 +00:00
feat: user api requests to resource API (#9794)
# Which Problems Are Solved This pull request addresses a significant gap in the user service v2 API, which currently lacks methods for managing machine users. # How the Problems Are Solved This PR adds new API endpoints to the user service v2 to manage machine users including their secret, keys and personal access tokens. Additionally, there's now a CreateUser and UpdateUser endpoints which allow to create either a human or machine user and update them. The existing `CreateHumanUser` endpoint has been deprecated along the corresponding management service endpoints. For details check the additional context section. # Additional Context - Closes https://github.com/zitadel/zitadel/issues/9349 ## More details - API changes: https://github.com/zitadel/zitadel/pull/9680 - Implementation: https://github.com/zitadel/zitadel/pull/9763 - Tests: https://github.com/zitadel/zitadel/pull/9771 ## Follow-ups - Metadata: support managing user metadata using resource API https://github.com/zitadel/zitadel/pull/10005 - Machine token type: support managing the machine token type (migrate to new enum with zero value unspecified?) --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
@@ -22,7 +22,7 @@ func (c *Commands) AddInstanceMemberCommand(a *instance.Aggregate, userID string
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "INSTANCE-4m0fS", "Errors.IAM.MemberInvalid")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
if exists, err := ExistsUser(ctx, filter, userID, ""); err != nil || !exists {
|
||||
if exists, err := ExistsUser(ctx, filter, userID, "", false); err != nil || !exists {
|
||||
return nil, zerrors.ThrowPreconditionFailed(err, "INSTA-GSXOn", "Errors.User.NotFound")
|
||||
}
|
||||
if isMember, err := IsInstanceMember(ctx, filter, a.ID, userID); err != nil || isMember {
|
||||
|
@@ -28,7 +28,7 @@ func (c *Commands) AddOrgMemberCommand(a *org.Aggregate, userID string, roles ..
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
if exists, err := ExistsUser(ctx, filter, userID, ""); err != nil || !exists {
|
||||
if exists, err := ExistsUser(ctx, filter, userID, "", false); err != nil || !exists {
|
||||
return nil, zerrors.ThrowPreconditionFailed(err, "ORG-GoXOn", "Errors.User.NotFound")
|
||||
}
|
||||
if isMember, err := IsOrgMember(ctx, filter, a.ID, userID); err != nil || isMember {
|
||||
|
@@ -353,21 +353,27 @@ func (c *Commands) userWriteModelByID(ctx context.Context, userID, resourceOwner
|
||||
return writeModel, nil
|
||||
}
|
||||
|
||||
func ExistsUser(ctx context.Context, filter preparation.FilterToQueryReducer, id, resourceOwner string) (exists bool, err error) {
|
||||
func ExistsUser(ctx context.Context, filter preparation.FilterToQueryReducer, id, resourceOwner string, machineOnly bool) (exists bool, err error) {
|
||||
eventTypes := []eventstore.EventType{
|
||||
user.MachineAddedEventType,
|
||||
user.UserRemovedType,
|
||||
}
|
||||
if !machineOnly {
|
||||
eventTypes = append(eventTypes,
|
||||
user.HumanRegisteredType,
|
||||
user.UserV1RegisteredType,
|
||||
user.HumanAddedType,
|
||||
user.UserV1AddedType,
|
||||
)
|
||||
}
|
||||
events, err := filter(ctx, eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(resourceOwner).
|
||||
OrderAsc().
|
||||
AddQuery().
|
||||
AggregateTypes(user.AggregateType).
|
||||
AggregateIDs(id).
|
||||
EventTypes(
|
||||
user.HumanRegisteredType,
|
||||
user.UserV1RegisteredType,
|
||||
user.HumanAddedType,
|
||||
user.UserV1AddedType,
|
||||
user.MachineAddedEventType,
|
||||
user.UserRemovedType,
|
||||
).Builder())
|
||||
EventTypes(eventTypes...).
|
||||
Builder())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
@@ -25,6 +25,7 @@ type Machine struct {
|
||||
Name string
|
||||
Description string
|
||||
AccessTokenType domain.OIDCTokenType
|
||||
PermissionCheck PermissionCheck
|
||||
}
|
||||
|
||||
func (m *Machine) IsZero() bool {
|
||||
@@ -33,8 +34,8 @@ func (m *Machine) IsZero() bool {
|
||||
|
||||
func AddMachineCommand(a *user.Aggregate, machine *Machine) preparation.Validation {
|
||||
return func() (_ preparation.CreateCommands, err error) {
|
||||
if a.ResourceOwner == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-xiown2", "Errors.ResourceOwnerMissing")
|
||||
if a.ResourceOwner == "" && machine.PermissionCheck == nil {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-xiown3", "Errors.ResourceOwnerMissing")
|
||||
}
|
||||
if a.ID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-p0p2mi", "Errors.User.UserIDMissing")
|
||||
@@ -49,7 +50,7 @@ func AddMachineCommand(a *user.Aggregate, machine *Machine) preparation.Validati
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
writeModel, err := getMachineWriteModel(ctx, a.ID, a.ResourceOwner, filter)
|
||||
writeModel, err := getMachineWriteModel(ctx, a.ID, a.ResourceOwner, filter, machine.PermissionCheck)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -67,7 +68,18 @@ func AddMachineCommand(a *user.Aggregate, machine *Machine) preparation.Validati
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) AddMachine(ctx context.Context, machine *Machine) (_ *domain.ObjectDetails, err error) {
|
||||
type addMachineOption func(context.Context, *Machine) error
|
||||
|
||||
func AddMachineWithUsernameToIDFallback() addMachineOption {
|
||||
return func(ctx context.Context, m *Machine) error {
|
||||
if m.Username == "" {
|
||||
m.Username = m.AggregateID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) AddMachine(ctx context.Context, machine *Machine, check PermissionCheck, options ...addMachineOption) (_ *domain.ObjectDetails, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
@@ -80,6 +92,16 @@ func (c *Commands) AddMachine(ctx context.Context, machine *Machine) (_ *domain.
|
||||
}
|
||||
|
||||
agg := user.NewAggregate(machine.AggregateID, machine.ResourceOwner)
|
||||
for _, option := range options {
|
||||
if err = option(ctx, machine); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if check != nil {
|
||||
if err = check(machine.ResourceOwner, machine.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, AddMachineCommand(agg, machine))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -97,6 +119,7 @@ func (c *Commands) AddMachine(ctx context.Context, machine *Machine) (_ *domain.
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Deprecated: use ChangeUserMachine instead
|
||||
func (c *Commands) ChangeMachine(ctx context.Context, machine *Machine) (*domain.ObjectDetails, error) {
|
||||
agg := user.NewAggregate(machine.AggregateID, machine.ResourceOwner)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, changeMachineCommand(agg, machine))
|
||||
@@ -118,24 +141,21 @@ func (c *Commands) ChangeMachine(ctx context.Context, machine *Machine) (*domain
|
||||
|
||||
func changeMachineCommand(a *user.Aggregate, machine *Machine) preparation.Validation {
|
||||
return func() (_ preparation.CreateCommands, err error) {
|
||||
if a.ResourceOwner == "" {
|
||||
if a.ResourceOwner == "" && machine.PermissionCheck == nil {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-xiown3", "Errors.ResourceOwnerMissing")
|
||||
}
|
||||
if a.ID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-p0p3mi", "Errors.User.UserIDMissing")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
writeModel, err := getMachineWriteModel(ctx, a.ID, a.ResourceOwner, filter)
|
||||
writeModel, err := getMachineWriteModel(ctx, a.ID, a.ResourceOwner, filter, machine.PermissionCheck)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isUserStateExists(writeModel.UserState) {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-5M0od", "Errors.User.NotFound")
|
||||
}
|
||||
changedEvent, hasChanged, err := writeModel.NewChangedEvent(ctx, &a.Aggregate, machine.Name, machine.Description, machine.AccessTokenType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
changedEvent, hasChanged := writeModel.NewChangedEvent(ctx, &a.Aggregate, machine.Name, machine.Description, machine.AccessTokenType)
|
||||
if !hasChanged {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-2n8vs", "Errors.User.NotChanged")
|
||||
}
|
||||
@@ -147,10 +167,9 @@ func changeMachineCommand(a *user.Aggregate, machine *Machine) preparation.Valid
|
||||
}
|
||||
}
|
||||
|
||||
func getMachineWriteModel(ctx context.Context, userID, resourceOwner string, filter preparation.FilterToQueryReducer) (_ *MachineWriteModel, err error) {
|
||||
func getMachineWriteModel(ctx context.Context, userID, resourceOwner string, filter preparation.FilterToQueryReducer, permissionCheck PermissionCheck) (_ *MachineWriteModel, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
writeModel := NewMachineWriteModel(userID, resourceOwner)
|
||||
events, err := filter(ctx, writeModel.Query())
|
||||
if err != nil {
|
||||
@@ -161,5 +180,10 @@ func getMachineWriteModel(ctx context.Context, userID, resourceOwner string, fil
|
||||
}
|
||||
writeModel.AppendEvents(events...)
|
||||
err = writeModel.Reduce()
|
||||
if permissionCheck != nil {
|
||||
if err := permissionCheck(writeModel.ResourceOwner, writeModel.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return writeModel, err
|
||||
}
|
||||
|
@@ -15,12 +15,14 @@ import (
|
||||
)
|
||||
|
||||
type AddMachineKey struct {
|
||||
Type domain.AuthNKeyType
|
||||
ExpirationDate time.Time
|
||||
Type domain.AuthNKeyType
|
||||
ExpirationDate time.Time
|
||||
PermissionCheck PermissionCheck
|
||||
}
|
||||
|
||||
type MachineKey struct {
|
||||
models.ObjectRoot
|
||||
PermissionCheck PermissionCheck
|
||||
|
||||
KeyID string
|
||||
Type domain.AuthNKeyType
|
||||
@@ -64,7 +66,7 @@ func (key *MachineKey) Detail() ([]byte, error) {
|
||||
}
|
||||
|
||||
func (key *MachineKey) content() error {
|
||||
if key.ResourceOwner == "" {
|
||||
if key.PermissionCheck == nil && key.ResourceOwner == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-kqpoix", "Errors.ResourceOwnerMissing")
|
||||
}
|
||||
if key.AggregateID == "" {
|
||||
@@ -91,7 +93,7 @@ func (key *MachineKey) valid() (err error) {
|
||||
}
|
||||
|
||||
func (key *MachineKey) checkAggregate(ctx context.Context, filter preparation.FilterToQueryReducer) error {
|
||||
if exists, err := ExistsUser(ctx, filter, key.AggregateID, key.ResourceOwner); err != nil || !exists {
|
||||
if exists, err := ExistsUser(ctx, filter, key.AggregateID, key.ResourceOwner, true); err != nil || !exists {
|
||||
return zerrors.ThrowPreconditionFailed(err, "COMMAND-bnipwm1", "Errors.User.NotFound")
|
||||
}
|
||||
return nil
|
||||
@@ -142,7 +144,7 @@ func prepareAddUserMachineKey(machineKey *MachineKey, keySize int) preparation.V
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
writeModel, err := getMachineKeyWriteModelByID(ctx, filter, machineKey.AggregateID, machineKey.KeyID, machineKey.ResourceOwner)
|
||||
writeModel, err := getMachineKeyWriteModelByID(ctx, filter, machineKey.AggregateID, machineKey.KeyID, machineKey.ResourceOwner, machineKey.PermissionCheck)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -186,7 +188,7 @@ func prepareRemoveUserMachineKey(machineKey *MachineKey) preparation.Validation
|
||||
return nil, err
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
writeModel, err := getMachineKeyWriteModelByID(ctx, filter, machineKey.AggregateID, machineKey.KeyID, machineKey.ResourceOwner)
|
||||
writeModel, err := getMachineKeyWriteModelByID(ctx, filter, machineKey.AggregateID, machineKey.KeyID, machineKey.ResourceOwner, machineKey.PermissionCheck)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -204,16 +206,18 @@ func prepareRemoveUserMachineKey(machineKey *MachineKey) preparation.Validation
|
||||
}
|
||||
}
|
||||
|
||||
func getMachineKeyWriteModelByID(ctx context.Context, filter preparation.FilterToQueryReducer, userID, keyID, resourceOwner string) (_ *MachineKeyWriteModel, err error) {
|
||||
func getMachineKeyWriteModelByID(ctx context.Context, filter preparation.FilterToQueryReducer, userID, keyID, resourceOwner string, permissionCheck PermissionCheck) (_ *MachineKeyWriteModel, err error) {
|
||||
writeModel := NewMachineKeyWriteModel(userID, keyID, resourceOwner)
|
||||
events, err := filter(ctx, writeModel.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(events) == 0 {
|
||||
return writeModel, nil
|
||||
}
|
||||
writeModel.AppendEvents(events...)
|
||||
err = writeModel.Reduce()
|
||||
if permissionCheck != nil {
|
||||
if err := permissionCheck(writeModel.ResourceOwner, writeModel.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return writeModel, err
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
@@ -106,9 +105,8 @@ func (wm *MachineWriteModel) NewChangedEvent(
|
||||
name,
|
||||
description string,
|
||||
accessTokenType domain.OIDCTokenType,
|
||||
) (*user.MachineChangedEvent, bool, error) {
|
||||
) (*user.MachineChangedEvent, bool) {
|
||||
changes := make([]user.MachineChanges, 0)
|
||||
var err error
|
||||
|
||||
if wm.Name != name {
|
||||
changes = append(changes, user.ChangeName(name))
|
||||
@@ -120,11 +118,8 @@ func (wm *MachineWriteModel) NewChangedEvent(
|
||||
changes = append(changes, user.ChangeAccessTokenType(accessTokenType))
|
||||
}
|
||||
if len(changes) == 0 {
|
||||
return nil, false, nil
|
||||
return nil, false
|
||||
}
|
||||
changeEvent, err := user.NewMachineChangedEvent(ctx, aggregate, changes)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return changeEvent, true, nil
|
||||
changeEvent := user.NewMachineChangedEvent(ctx, aggregate, changes)
|
||||
return changeEvent, true
|
||||
}
|
||||
|
@@ -11,7 +11,8 @@ import (
|
||||
)
|
||||
|
||||
type GenerateMachineSecret struct {
|
||||
ClientSecret string
|
||||
PermissionCheck PermissionCheck
|
||||
ClientSecret string
|
||||
}
|
||||
|
||||
func (c *Commands) GenerateMachineSecret(ctx context.Context, userID string, resourceOwner string, set *GenerateMachineSecret) (*domain.ObjectDetails, error) {
|
||||
@@ -35,14 +36,14 @@ func (c *Commands) GenerateMachineSecret(ctx context.Context, userID string, res
|
||||
|
||||
func (c *Commands) prepareGenerateMachineSecret(a *user.Aggregate, set *GenerateMachineSecret) preparation.Validation {
|
||||
return func() (_ preparation.CreateCommands, err error) {
|
||||
if a.ResourceOwner == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-x0992n", "Errors.ResourceOwnerMissing")
|
||||
if a.ResourceOwner == "" && set.PermissionCheck == nil {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-0qp2hus", "Errors.ResourceOwnerMissing")
|
||||
}
|
||||
if a.ID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-bzoqjs", "Errors.User.UserIDMissing")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
writeModel, err := getMachineWriteModel(ctx, a.ID, a.ResourceOwner, filter)
|
||||
writeModel, err := getMachineWriteModel(ctx, a.ID, a.ResourceOwner, filter, set.PermissionCheck)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -62,9 +63,10 @@ func (c *Commands) prepareGenerateMachineSecret(a *user.Aggregate, set *Generate
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) RemoveMachineSecret(ctx context.Context, userID string, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||
func (c *Commands) RemoveMachineSecret(ctx context.Context, userID string, resourceOwner string, permissionCheck PermissionCheck) (*domain.ObjectDetails, error) {
|
||||
agg := user.NewAggregate(userID, resourceOwner)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, prepareRemoveMachineSecret(agg))
|
||||
//nolint:staticcheck
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, prepareRemoveMachineSecret(agg, permissionCheck))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -81,16 +83,16 @@ func (c *Commands) RemoveMachineSecret(ctx context.Context, userID string, resou
|
||||
}, nil
|
||||
}
|
||||
|
||||
func prepareRemoveMachineSecret(a *user.Aggregate) preparation.Validation {
|
||||
func prepareRemoveMachineSecret(a *user.Aggregate, check PermissionCheck) preparation.Validation {
|
||||
return func() (_ preparation.CreateCommands, err error) {
|
||||
if a.ResourceOwner == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-0qp2hus", "Errors.ResourceOwnerMissing")
|
||||
if a.ResourceOwner == "" && check == nil {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-x0992n", "Errors.ResourceOwnerMissing")
|
||||
}
|
||||
if a.ID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-bzosjs", "Errors.User.UserIDMissing")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
writeModel, err := getMachineWriteModel(ctx, a.ID, a.ResourceOwner, filter)
|
||||
writeModel, err := getMachineWriteModel(ctx, a.ID, a.ResourceOwner, filter, check)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -44,7 +44,7 @@ func TestCommandSide_GenerateMachineSecret(t *testing.T) {
|
||||
ctx: context.Background(),
|
||||
userID: "",
|
||||
resourceOwner: "org1",
|
||||
set: nil,
|
||||
set: new(GenerateMachineSecret),
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
@@ -59,7 +59,7 @@ func TestCommandSide_GenerateMachineSecret(t *testing.T) {
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
resourceOwner: "",
|
||||
set: nil,
|
||||
set: new(GenerateMachineSecret),
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
@@ -76,7 +76,7 @@ func TestCommandSide_GenerateMachineSecret(t *testing.T) {
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
set: nil,
|
||||
set: new(GenerateMachineSecret),
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPreconditionFailed,
|
||||
@@ -289,7 +289,7 @@ func TestCommandSide_RemoveMachineSecret(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
}
|
||||
got, err := r.RemoveMachineSecret(tt.args.ctx, tt.args.userID, tt.args.resourceOwner)
|
||||
got, err := r.RemoveMachineSecret(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, nil)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
@@ -24,6 +24,8 @@ func TestCommandSide_AddMachine(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
machine *Machine
|
||||
check PermissionCheck
|
||||
options func(*Commands) []addMachineOption
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
@@ -194,14 +196,242 @@ func TestCommandSide_AddMachine(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with username fallback to given username",
|
||||
fields: fields{
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "aggregateID"),
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewDomainPolicyAddedEvent(context.Background(),
|
||||
&user.NewAggregate("aggregateID", "org1").Aggregate,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
user.NewMachineAddedEvent(context.Background(),
|
||||
&user.NewAggregate("aggregateID", "org1").Aggregate,
|
||||
"username",
|
||||
"name",
|
||||
"",
|
||||
true,
|
||||
domain.OIDCTokenTypeBearer,
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
machine: &Machine{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
Name: "name",
|
||||
Username: "username",
|
||||
},
|
||||
options: func(commands *Commands) []addMachineOption {
|
||||
return []addMachineOption{
|
||||
AddMachineWithUsernameToIDFallback(),
|
||||
}
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with username fallback to generated id",
|
||||
fields: fields{
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "aggregateID"),
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewDomainPolicyAddedEvent(context.Background(),
|
||||
&user.NewAggregate("aggregateID", "org1").Aggregate,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
user.NewMachineAddedEvent(context.Background(),
|
||||
&user.NewAggregate("aggregateID", "org1").Aggregate,
|
||||
"aggregateID",
|
||||
"name",
|
||||
"",
|
||||
true,
|
||||
domain.OIDCTokenTypeBearer,
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
machine: &Machine{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
Name: "name",
|
||||
},
|
||||
options: func(commands *Commands) []addMachineOption {
|
||||
return []addMachineOption{
|
||||
AddMachineWithUsernameToIDFallback(),
|
||||
}
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with username fallback to given id",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewDomainPolicyAddedEvent(context.Background(),
|
||||
&user.NewAggregate("aggregateID", "org1").Aggregate,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
user.NewMachineAddedEvent(context.Background(),
|
||||
&user.NewAggregate("aggregateID", "org1").Aggregate,
|
||||
"aggregateID",
|
||||
"name",
|
||||
"",
|
||||
true,
|
||||
domain.OIDCTokenTypeBearer,
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
machine: &Machine{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
ResourceOwner: "org1",
|
||||
AggregateID: "aggregateID",
|
||||
},
|
||||
Name: "name",
|
||||
},
|
||||
options: func(commands *Commands) []addMachineOption {
|
||||
return []addMachineOption{
|
||||
AddMachineWithUsernameToIDFallback(),
|
||||
}
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with succeeding permission check, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewDomainPolicyAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
user.NewMachineAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"username",
|
||||
"name",
|
||||
"description",
|
||||
true,
|
||||
domain.OIDCTokenTypeBearer,
|
||||
),
|
||||
),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
machine: &Machine{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
Description: "description",
|
||||
Name: "name",
|
||||
Username: "username",
|
||||
},
|
||||
check: func(resourceOwner, aggregateID string) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with failing permission check, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
machine: &Machine{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
Description: "description",
|
||||
Name: "name",
|
||||
Username: "username",
|
||||
},
|
||||
check: func(resourceOwner, aggregateID string) error {
|
||||
return zerrors.ThrowPermissionDenied(nil, "", "")
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
idGenerator: tt.fields.idGenerator,
|
||||
eventstore: tt.fields.eventstore,
|
||||
idGenerator: tt.fields.idGenerator,
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
}
|
||||
got, err := r.AddMachine(tt.args.ctx, tt.args.machine)
|
||||
var options []addMachineOption
|
||||
if tt.args.options != nil {
|
||||
options = tt.args.options(r)
|
||||
}
|
||||
got, err := r.AddMachine(tt.args.ctx, tt.args.machine, tt.args.check, options...)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -391,7 +621,7 @@ func TestCommandSide_ChangeMachine(t *testing.T) {
|
||||
}
|
||||
|
||||
func newMachineChangedEvent(ctx context.Context, userID, resourceOwner, name, description string) *user.MachineChangedEvent {
|
||||
event, _ := user.NewMachineChangedEvent(ctx,
|
||||
event := user.NewMachineChangedEvent(ctx,
|
||||
&user.NewAggregate(userID, resourceOwner).Aggregate,
|
||||
[]user.MachineChanges{
|
||||
user.ChangeName(name),
|
||||
|
@@ -21,6 +21,7 @@ type AddPat struct {
|
||||
|
||||
type PersonalAccessToken struct {
|
||||
models.ObjectRoot
|
||||
PermissionCheck PermissionCheck
|
||||
|
||||
ExpirationDate time.Time
|
||||
Scopes []string
|
||||
@@ -43,7 +44,7 @@ func NewPersonalAccessToken(resourceOwner string, userID string, expirationDate
|
||||
}
|
||||
|
||||
func (pat *PersonalAccessToken) content() error {
|
||||
if pat.ResourceOwner == "" {
|
||||
if pat.ResourceOwner == "" && pat.PermissionCheck == nil {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-xs0k2n", "Errors.ResourceOwnerMissing")
|
||||
}
|
||||
if pat.AggregateID == "" {
|
||||
@@ -109,11 +110,10 @@ func prepareAddPersonalAccessToken(pat *PersonalAccessToken, algorithm crypto.En
|
||||
if err := pat.checkAggregate(ctx, filter); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
writeModel, err := getPersonalAccessTokenWriteModelByID(ctx, filter, pat.AggregateID, pat.TokenID, pat.ResourceOwner)
|
||||
writeModel, err := getPersonalAccessTokenWriteModelByID(ctx, filter, pat.AggregateID, pat.TokenID, pat.ResourceOwner, pat.PermissionCheck)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pat.Token, err = createToken(algorithm, writeModel.TokenID, writeModel.AggregateID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -155,7 +155,7 @@ func prepareRemovePersonalAccessToken(pat *PersonalAccessToken) preparation.Vali
|
||||
return nil, err
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) (_ []eventstore.Command, err error) {
|
||||
writeModel, err := getPersonalAccessTokenWriteModelByID(ctx, filter, pat.AggregateID, pat.TokenID, pat.ResourceOwner)
|
||||
writeModel, err := getPersonalAccessTokenWriteModelByID(ctx, filter, pat.AggregateID, pat.TokenID, pat.ResourceOwner, pat.PermissionCheck)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -181,16 +181,18 @@ func createToken(algorithm crypto.EncryptionAlgorithm, tokenID, userID string) (
|
||||
return base64.RawURLEncoding.EncodeToString(encrypted), nil
|
||||
}
|
||||
|
||||
func getPersonalAccessTokenWriteModelByID(ctx context.Context, filter preparation.FilterToQueryReducer, userID, tokenID, resourceOwner string) (_ *PersonalAccessTokenWriteModel, err error) {
|
||||
func getPersonalAccessTokenWriteModelByID(ctx context.Context, filter preparation.FilterToQueryReducer, userID, tokenID, resourceOwner string, check PermissionCheck) (_ *PersonalAccessTokenWriteModel, err error) {
|
||||
writeModel := NewPersonalAccessTokenWriteModel(userID, tokenID, resourceOwner)
|
||||
events, err := filter(ctx, writeModel.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(events) == 0 {
|
||||
return writeModel, nil
|
||||
}
|
||||
writeModel.AppendEvents(events...)
|
||||
err = writeModel.Reduce()
|
||||
if err = writeModel.Reduce(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if check != nil {
|
||||
err = check(writeModel.ResourceOwner, writeModel.AggregateID)
|
||||
}
|
||||
return writeModel, err
|
||||
}
|
||||
|
@@ -1813,7 +1813,7 @@ func TestExistsUser(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotExists, err := ExistsUser(context.Background(), tt.args.filter, tt.args.id, tt.args.resourceOwner)
|
||||
gotExists, err := ExistsUser(context.Background(), tt.args.filter, tt.args.id, tt.args.resourceOwner, false)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ExistsUser() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
|
@@ -132,7 +132,6 @@ func (c *Commands) RemoveUserV2(ctx context.Context, userID, resourceOwner strin
|
||||
if userID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-vaipl7s13l", "Errors.User.UserIDMissing")
|
||||
}
|
||||
|
||||
existingUser, err := c.userRemoveWriteModel(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -143,7 +142,6 @@ func (c *Commands) RemoveUserV2(ctx context.Context, userID, resourceOwner strin
|
||||
if err := c.checkPermissionDeleteUser(ctx, existingUser.ResourceOwner, existingUser.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
domainPolicy, err := c.domainPolicyWriteModel(ctx, existingUser.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-l40ykb3xh2", "Errors.Org.DomainPolicy.NotExisting")
|
||||
|
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/command/preparation"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
@@ -121,7 +122,10 @@ func (c *Commands) AddUserHuman(ctx context.Context, resourceOwner string, human
|
||||
if resourceOwner == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMA-095xh8fll1", "Errors.Internal")
|
||||
}
|
||||
|
||||
if human.Details == nil {
|
||||
human.Details = &domain.ObjectDetails{}
|
||||
}
|
||||
human.Details.ResourceOwner = resourceOwner
|
||||
if err := human.Validate(c.userPasswordHasher); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -132,7 +136,12 @@ func (c *Commands) AddUserHuman(ctx context.Context, resourceOwner string, human
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// check for permission to create user on resourceOwner
|
||||
if !human.Register {
|
||||
if err := c.checkPermissionUpdateUser(ctx, resourceOwner, human.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// only check if user is already existing
|
||||
existingHuman, err := c.userExistsWriteModel(
|
||||
ctx,
|
||||
@@ -144,12 +153,6 @@ func (c *Commands) AddUserHuman(ctx context.Context, resourceOwner string, human
|
||||
if isUserStateExists(existingHuman.UserState) {
|
||||
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-7yiox1isql", "Errors.User.AlreadyExisting")
|
||||
}
|
||||
// check for permission to create user on resourceOwner
|
||||
if !human.Register {
|
||||
if err := c.checkPermission(ctx, domain.PermissionUserWrite, resourceOwner, human.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// add resourceowner for the events with the aggregate
|
||||
existingHuman.ResourceOwner = resourceOwner
|
||||
|
||||
@@ -161,6 +164,7 @@ func (c *Commands) AddUserHuman(ctx context.Context, resourceOwner string, human
|
||||
if err = c.userValidateDomain(ctx, resourceOwner, human.Username, domainPolicy.UserLoginMustBeDomain); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var createCmd humanCreationCommand
|
||||
if human.Register {
|
||||
createCmd = user.NewHumanRegisteredEvent(
|
||||
@@ -203,17 +207,33 @@ func (c *Commands) AddUserHuman(ctx context.Context, resourceOwner string, human
|
||||
return err
|
||||
}
|
||||
|
||||
cmds := make([]eventstore.Command, 0, 3)
|
||||
cmds = append(cmds, createCmd)
|
||||
|
||||
cmds, err = c.addHumanCommandEmail(ctx, filter, cmds, existingHuman.Aggregate(), human, alg, allowInitMail)
|
||||
cmds, err := c.addUserHumanCommands(ctx, filter, existingHuman, human, allowInitMail, alg, createCmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(cmds) == 0 {
|
||||
human.Details = writeModelToObjectDetails(&existingHuman.WriteModel)
|
||||
return nil
|
||||
}
|
||||
err = c.pushAppendAndReduce(ctx, existingHuman, cmds...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
human.Details = writeModelToObjectDetails(&existingHuman.WriteModel)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) addUserHumanCommands(ctx context.Context, filter preparation.FilterToQueryReducer, existingHuman *UserV2WriteModel, human *AddHuman, allowInitMail bool, alg crypto.EncryptionAlgorithm, addUserCommand eventstore.Command) ([]eventstore.Command, error) {
|
||||
cmds := []eventstore.Command{addUserCommand}
|
||||
var err error
|
||||
cmds, err = c.addHumanCommandEmail(ctx, filter, cmds, existingHuman.Aggregate(), human, alg, allowInitMail)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmds, err = c.addHumanCommandPhone(ctx, filter, cmds, existingHuman.Aggregate(), human, alg)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, metadataEntry := range human.Metadata {
|
||||
@@ -227,7 +247,7 @@ func (c *Commands) AddUserHuman(ctx context.Context, resourceOwner string, human
|
||||
for _, link := range human.Links {
|
||||
cmd, err := addLink(ctx, filter, existingHuman.Aggregate(), link)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
@@ -235,7 +255,7 @@ func (c *Commands) AddUserHuman(ctx context.Context, resourceOwner string, human
|
||||
if human.TOTPSecret != "" {
|
||||
encryptedSecret, err := crypto.Encrypt([]byte(human.TOTPSecret), c.multifactors.OTP.CryptoMFA)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
cmds = append(cmds,
|
||||
user.NewHumanOTPAddedEvent(ctx, &existingHuman.Aggregate().Aggregate, encryptedSecret),
|
||||
@@ -246,18 +266,7 @@ func (c *Commands) AddUserHuman(ctx context.Context, resourceOwner string, human
|
||||
if human.SetInactive {
|
||||
cmds = append(cmds, user.NewUserDeactivatedEvent(ctx, &existingHuman.Aggregate().Aggregate))
|
||||
}
|
||||
|
||||
if len(cmds) == 0 {
|
||||
human.Details = writeModelToObjectDetails(&existingHuman.WriteModel)
|
||||
return nil
|
||||
}
|
||||
|
||||
err = c.pushAppendAndReduce(ctx, existingHuman, cmds...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
human.Details = writeModelToObjectDetails(&existingHuman.WriteModel)
|
||||
return nil
|
||||
return cmds, nil
|
||||
}
|
||||
|
||||
func (c *Commands) ChangeUserHuman(ctx context.Context, human *ChangeHuman, alg crypto.EncryptionAlgorithm) (err error) {
|
||||
@@ -341,7 +350,6 @@ func (c *Commands) ChangeUserHuman(ctx context.Context, human *ChangeHuman, alg
|
||||
if human.State != nil {
|
||||
// only allow toggling between active and inactive
|
||||
// any other target state is not supported
|
||||
// the existing human's state has to be the
|
||||
switch {
|
||||
case isUserStateActive(*human.State):
|
||||
if isUserStateActive(existingHuman.UserState) {
|
||||
|
@@ -302,9 +302,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
|
||||
{
|
||||
name: "add human (with initial code), no permission",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
|
||||
newCode: mockEncryptedCode("userinit", time.Hour),
|
||||
@@ -326,9 +324,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
|
||||
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
res: res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, zerrors.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied"))
|
||||
},
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@@ -352,6 +352,7 @@ func TestCommands_ResendInviteCode(t *testing.T) {
|
||||
"user does not exist",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
// The write model doesn't query any events
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
|
94
internal/command/user_v2_machine.go
Normal file
94
internal/command/user_v2_machine.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
type ChangeMachine struct {
|
||||
ID string
|
||||
ResourceOwner string
|
||||
Username *string
|
||||
Name *string
|
||||
Description *string
|
||||
|
||||
// Details are set after a successful execution of the command
|
||||
Details *domain.ObjectDetails
|
||||
}
|
||||
|
||||
func (h *ChangeMachine) Changed() bool {
|
||||
if h.Username != nil {
|
||||
return true
|
||||
}
|
||||
if h.Name != nil {
|
||||
return true
|
||||
}
|
||||
if h.Description != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *Commands) ChangeUserMachine(ctx context.Context, machine *ChangeMachine) (err error) {
|
||||
existingMachine, err := c.UserMachineWriteModel(
|
||||
ctx,
|
||||
machine.ID,
|
||||
machine.ResourceOwner,
|
||||
false,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if machine.Changed() {
|
||||
if err := c.checkPermissionUpdateUser(ctx, existingMachine.ResourceOwner, existingMachine.AggregateID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
cmds := make([]eventstore.Command, 0)
|
||||
if machine.Username != nil {
|
||||
cmds, err = c.changeUsername(ctx, cmds, existingMachine, *machine.Username)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var machineChanges []user.MachineChanges
|
||||
if machine.Name != nil && *machine.Name != existingMachine.Name {
|
||||
machineChanges = append(machineChanges, user.ChangeName(*machine.Name))
|
||||
}
|
||||
if machine.Description != nil && *machine.Description != existingMachine.Description {
|
||||
machineChanges = append(machineChanges, user.ChangeDescription(*machine.Description))
|
||||
}
|
||||
if len(machineChanges) > 0 {
|
||||
cmds = append(cmds, user.NewMachineChangedEvent(ctx, &existingMachine.Aggregate().Aggregate, machineChanges))
|
||||
}
|
||||
if len(cmds) == 0 {
|
||||
machine.Details = writeModelToObjectDetails(&existingMachine.WriteModel)
|
||||
return nil
|
||||
}
|
||||
err = c.pushAppendAndReduce(ctx, existingMachine, cmds...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
machine.Details = writeModelToObjectDetails(&existingMachine.WriteModel)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) UserMachineWriteModel(ctx context.Context, userID, resourceOwner string, metadataWM bool) (writeModel *UserV2WriteModel, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
writeModel = NewUserMachineWriteModel(userID, resourceOwner, metadataWM)
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isUserStateExists(writeModel.UserState) {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-ugjs0upun6", "Errors.User.NotFound")
|
||||
}
|
||||
return writeModel, nil
|
||||
}
|
260
internal/command/user_v2_machine_test.go
Normal file
260
internal/command/user_v2_machine_test.go
Normal file
@@ -0,0 +1,260 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"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/user"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func TestCommandSide_ChangeUserMachine(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
orgID string
|
||||
machine *ChangeMachine
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
|
||||
userAgg := user.NewAggregate("user1", "org1")
|
||||
userAddedEvent := user.NewMachineAddedEvent(context.Background(),
|
||||
&userAgg.Aggregate,
|
||||
"username",
|
||||
"name",
|
||||
"description",
|
||||
true,
|
||||
domain.OIDCTokenTypeBearer,
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "change machine username, no permission",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(userAddedEvent),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
machine: &ChangeMachine{
|
||||
Username: gu.Ptr("changed"),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, zerrors.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change machine username, not found",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
machine: &ChangeMachine{
|
||||
Username: gu.Ptr("changed"),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-ugjs0upun6", "Errors.User.NotFound"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change machine username, ok",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(userAddedEvent),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewDomainPolicyAddedEvent(context.Background(),
|
||||
&userAgg.Aggregate,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
user.NewUsernameChangedEvent(context.Background(),
|
||||
&userAgg.Aggregate,
|
||||
"username",
|
||||
"changed",
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
machine: &ChangeMachine{
|
||||
Username: gu.Ptr("changed"),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{
|
||||
Sequence: 0,
|
||||
EventDate: time.Time{},
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change machine username, no change",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(userAddedEvent),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
machine: &ChangeMachine{
|
||||
Username: gu.Ptr("username"),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{
|
||||
Sequence: 0,
|
||||
EventDate: time.Time{},
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change machine description, no permission",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(userAddedEvent),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
machine: &ChangeMachine{
|
||||
Description: gu.Ptr("changed"),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, zerrors.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change machine description, ok",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(userAddedEvent),
|
||||
),
|
||||
expectPush(
|
||||
user.NewMachineChangedEvent(context.Background(),
|
||||
&userAgg.Aggregate,
|
||||
[]user.MachineChanges{
|
||||
user.ChangeDescription("changed"),
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
machine: &ChangeMachine{
|
||||
Description: gu.Ptr("changed"),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change machine description, no change",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(userAddedEvent),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
machine: &ChangeMachine{
|
||||
Description: gu.Ptr("description"),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
err := r.ChangeUserMachine(tt.args.ctx, tt.args.machine)
|
||||
if tt.res.err == nil {
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
} else if !tt.res.err(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
return
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assertObjectDetails(t, tt.res.want, tt.args.machine.Details)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -118,6 +118,14 @@ func NewUserHumanWriteModel(userID, resourceOwner string, profileWM, emailWM, ph
|
||||
return newUserV2WriteModel(userID, resourceOwner, opts...)
|
||||
}
|
||||
|
||||
func NewUserMachineWriteModel(userID, resourceOwner string, metadataListWM bool) *UserV2WriteModel {
|
||||
opts := []UserV2WMOption{WithMachine(), WithState()}
|
||||
if metadataListWM {
|
||||
opts = append(opts, WithMetadata())
|
||||
}
|
||||
return newUserV2WriteModel(userID, resourceOwner, opts...)
|
||||
}
|
||||
|
||||
func newUserV2WriteModel(userID, resourceOwner string, opts ...UserV2WMOption) *UserV2WriteModel {
|
||||
wm := &UserV2WriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
|
Reference in New Issue
Block a user