mirror of
https://github.com/zitadel/zitadel.git
synced 2025-01-10 12:24:03 +00:00
feat: SetPassword endpoint
This commit is contained in:
parent
aa13e451e3
commit
1e9d58c924
@ -0,0 +1,234 @@
|
||||
//go:build integration
|
||||
|
||||
package user_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
resource_object "github.com/zitadel/zitadel/pkg/grpc/resources/object/v3alpha"
|
||||
user "github.com/zitadel/zitadel/pkg/grpc/resources/user/v3alpha"
|
||||
)
|
||||
|
||||
func TestServer_SetPassword(t *testing.T) {
|
||||
t.Parallel()
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
schema := []byte(`{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}`)
|
||||
schemaResp := instance.CreateUserSchema(isolatedIAMOwnerCTX, schema)
|
||||
orgResp := instance.CreateOrganization(isolatedIAMOwnerCTX, gofakeit.Name(), gofakeit.Email())
|
||||
|
||||
type res struct {
|
||||
want *resource_object.Details
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
dep func(req *user.SetPasswordRequest) error
|
||||
req *user.SetPasswordRequest
|
||||
res res
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "password set, no context",
|
||||
ctx: context.Background(),
|
||||
dep: func(req *user.SetPasswordRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetPasswordRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
NewPassword: &user.SetPassword{
|
||||
Type: &user.SetPassword_Password{
|
||||
Password: gofakeit.Password(true, true, true, true, false, 12),
|
||||
},
|
||||
ChangeRequired: false,
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "password set, no permission",
|
||||
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
|
||||
dep: func(req *user.SetPasswordRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetPasswordRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
NewPassword: &user.SetPassword{
|
||||
Type: &user.SetPassword_Password{
|
||||
Password: gofakeit.Password(true, true, true, true, false, 12),
|
||||
},
|
||||
ChangeRequired: false,
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "password set, password empty",
|
||||
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
|
||||
dep: func(req *user.SetPasswordRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetPasswordRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
NewPassword: &user.SetPassword{
|
||||
Type: &user.SetPassword_Password{
|
||||
Password: "",
|
||||
},
|
||||
ChangeRequired: false,
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "password set, user not existing in org",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetPasswordRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetPasswordRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: "notexisting",
|
||||
},
|
||||
},
|
||||
NewPassword: &user.SetPassword{
|
||||
Type: &user.SetPassword_Password{
|
||||
Password: gofakeit.Password(true, true, true, true, false, 12),
|
||||
},
|
||||
ChangeRequired: false,
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "username add, user not existing",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
|
||||
req: &user.SetPasswordRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: "notexisting",
|
||||
},
|
||||
},
|
||||
Id: "not existing",
|
||||
NewPassword: &user.SetPassword{
|
||||
Type: &user.SetPassword_Password{
|
||||
Password: gofakeit.Password(true, true, true, true, false, 12),
|
||||
},
|
||||
ChangeRequired: false,
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "password set, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetPasswordRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetPasswordRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
NewPassword: &user.SetPassword{
|
||||
Type: &user.SetPassword_Password{
|
||||
Password: gofakeit.Password(true, true, true, true, false, 12),
|
||||
},
|
||||
ChangeRequired: false,
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "password set, no org, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetPasswordRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetPasswordRequest{
|
||||
NewPassword: &user.SetPassword{
|
||||
Type: &user.SetPassword_Password{
|
||||
Password: gofakeit.Password(true, true, true, true, false, 12),
|
||||
},
|
||||
ChangeRequired: false,
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.dep != nil {
|
||||
err := tt.dep(tt.req)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
got, err := instance.Client.UserV3Alpha.SetPassword(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
integration.AssertResourceDetails(t, tt.res.want, got.Details)
|
||||
})
|
||||
}
|
||||
}
|
33
internal/api/grpc/resources/user/v3alpha/password.go
Normal file
33
internal/api/grpc/resources/user/v3alpha/password.go
Normal file
@ -0,0 +1,33 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
resource_object "github.com/zitadel/zitadel/internal/api/grpc/resources/object/v3alpha"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
user "github.com/zitadel/zitadel/pkg/grpc/resources/user/v3alpha"
|
||||
)
|
||||
|
||||
func (s *Server) SetPassword(ctx context.Context, req *user.SetPasswordRequest) (_ *user.SetPasswordResponse, err error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
details, err := s.command.SetSchemaUserPassword(ctx, setPasswordRequestToSetSchemaUserPassword(req))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.SetPasswordResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func setPasswordRequestToSetSchemaUserPassword(req *user.SetPasswordRequest) *command.SetSchemaUserPassword {
|
||||
return &command.SetSchemaUserPassword{
|
||||
ResourceOwner: organizationToUpdateResourceOwner(req.Organization),
|
||||
UserID: req.GetId(),
|
||||
Password: req.GetNewPassword().GetPassword(),
|
||||
EncodedPasswordHash: req.GetNewPassword().GetHash(),
|
||||
ChangeRequired: req.GetNewPassword().GetChangeRequired(),
|
||||
}
|
||||
}
|
123
internal/command/user_v3_password.go
Normal file
123
internal/command/user_v3_password.go
Normal file
@ -0,0 +1,123 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/repository/user/authenticator"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
type SetSchemaUserPassword struct {
|
||||
ResourceOwner string
|
||||
UserID string
|
||||
|
||||
Password string
|
||||
EncodedPasswordHash string
|
||||
ChangeRequired bool
|
||||
}
|
||||
|
||||
func (p *SetSchemaUserPassword) Validate(hasher *crypto.Hasher) (err error) {
|
||||
if p.UserID == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-aS3Vz5t6BS", "Errors.IDMissing")
|
||||
}
|
||||
|
||||
if p.EncodedPasswordHash != "" {
|
||||
if !hasher.EncodingSupported(p.EncodedPasswordHash) {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-oz74onzvqr", "Errors.User.Password.NotSupported")
|
||||
}
|
||||
}
|
||||
if p.Password == "" && p.EncodedPasswordHash == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-3klek4sbns", "Errors.User.Password.Empty")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) SetSchemaUserPassword(ctx context.Context, username *SetSchemaUserPassword) (*domain.ObjectDetails, error) {
|
||||
if err := username.Validate(c.userPasswordHasher); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
existing, err := c.getPasswordExistsWithVerification(ctx, username.ResourceOwner, username.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resourceOwner := existing.ResourceOwner
|
||||
if existing.EncodedHash == "" {
|
||||
existingUser, err := c.getSchemaUserExists(ctx, username.ResourceOwner, username.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !existingUser.Exists() {
|
||||
return nil, zerrors.ThrowNotFound(nil, "TODO", "TODO")
|
||||
}
|
||||
resourceOwner = existingUser.ResourceOwner
|
||||
}
|
||||
|
||||
// If password is provided, let's check if is compliant with the policy.
|
||||
// If only a encodedPassword is passed, we can skip this.
|
||||
if username.Password != "" {
|
||||
if err = c.checkPasswordComplexity(ctx, username.Password, resourceOwner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
encodedPassword := username.EncodedPasswordHash
|
||||
if username.Password != "" {
|
||||
encodedPassword, err = c.userPasswordHasher.Hash(username.Password)
|
||||
if err = convertPasswapErr(err); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
events, err := c.eventstore.Push(ctx,
|
||||
authenticator.NewPasswordCreatedEvent(ctx,
|
||||
&authenticator.NewAggregate(username.UserID, resourceOwner).Aggregate,
|
||||
existing.UserID,
|
||||
encodedPassword,
|
||||
username.ChangeRequired,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pushedEventsToObjectDetails(events), nil
|
||||
}
|
||||
|
||||
func (c *Commands) DeleteSchemaUserPassword(ctx context.Context, resourceOwner, id string) (_ *domain.ObjectDetails, err error) {
|
||||
existing, err := c.getPasswordExistsWithVerification(ctx, resourceOwner, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existing.EncodedHash == "" {
|
||||
return nil, zerrors.ThrowNotFound(nil, "TODO", "TODO")
|
||||
}
|
||||
|
||||
events, err := c.eventstore.Push(ctx,
|
||||
authenticator.NewPasswordDeletedEvent(ctx,
|
||||
&authenticator.NewAggregate(id, existing.ResourceOwner).Aggregate,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pushedEventsToObjectDetails(events), nil
|
||||
}
|
||||
|
||||
func (c *Commands) getPasswordExistsWithVerification(ctx context.Context, resourceOwner, id string) (*PasswordV3WriteModel, error) {
|
||||
if id == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-PoSU5BOZCi", "Errors.IDMissing")
|
||||
}
|
||||
writeModel := NewPasswordV3WriteModel(resourceOwner, id)
|
||||
if err := c.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO permission through old password and password code
|
||||
if err := c.checkPermissionUpdateUser(ctx, writeModel.ResourceOwner, writeModel.UserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModel, nil
|
||||
}
|
53
internal/command/user_v3_password_model.go
Normal file
53
internal/command/user_v3_password_model.go
Normal file
@ -0,0 +1,53 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/user/authenticator"
|
||||
"github.com/zitadel/zitadel/internal/repository/user/schemauser"
|
||||
)
|
||||
|
||||
type PasswordV3WriteModel struct {
|
||||
eventstore.WriteModel
|
||||
UserID string
|
||||
|
||||
EncodedHash string
|
||||
ChangeRequired bool
|
||||
}
|
||||
|
||||
func NewPasswordV3WriteModel(resourceOwner, id string) *PasswordV3WriteModel {
|
||||
return &PasswordV3WriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: id,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
UserID: id,
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *PasswordV3WriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *authenticator.PasswordCreatedEvent:
|
||||
wm.UserID = e.UserID
|
||||
wm.EncodedHash = e.EncodedHash
|
||||
wm.ChangeRequired = e.ChangeRequired
|
||||
case *authenticator.PasswordDeletedEvent:
|
||||
wm.UserID = ""
|
||||
wm.EncodedHash = ""
|
||||
wm.ChangeRequired = false
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *PasswordV3WriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
AddQuery().
|
||||
AggregateTypes(schemauser.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
EventTypes(
|
||||
authenticator.PasswordCreatedType,
|
||||
authenticator.PasswordDeletedType,
|
||||
).Builder()
|
||||
}
|
416
internal/command/user_v3_password_test.go
Normal file
416
internal/command/user_v3_password_test.go
Normal file
@ -0,0 +1,416 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"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/authenticator"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func filterSchemaUserPasswordExisting() expect {
|
||||
return expectFilter(
|
||||
eventFromEventPusher(
|
||||
authenticator.NewPasswordCreatedEvent(
|
||||
context.Background(),
|
||||
&authenticator.NewAggregate("user1", "org1").Aggregate,
|
||||
"user1",
|
||||
"encoded",
|
||||
false,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func filterPasswordComplexityPolicyExisting() expect {
|
||||
return expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate,
|
||||
1,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func TestCommands_SetSchemaUserPassword(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
userPasswordHasher *crypto.Hasher
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
user *SetSchemaUserPassword
|
||||
}
|
||||
type res struct {
|
||||
details *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"no userID, error",
|
||||
fields{
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
user: &SetSchemaUserPassword{},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "COMMAND-aS3Vz5t6BS", "Errors.IDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"no password, error",
|
||||
fields{
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
user: &SetSchemaUserPassword{
|
||||
UserID: "user1",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "COMMAND-3klek4sbns", "Errors.User.Password.Empty"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"user not existing, error",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
user: &SetSchemaUserPassword{
|
||||
UserID: "notexisting",
|
||||
Password: "password",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, zerrors.ThrowNotFound(nil, "TODO", "TODO"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"no permission, error",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
user: &SetSchemaUserPassword{
|
||||
UserID: "user1",
|
||||
Password: "password",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, zerrors.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"password added, ok",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
filterSchemaUserExisting(),
|
||||
filterPasswordComplexityPolicyExisting(),
|
||||
expectPush(
|
||||
authenticator.NewPasswordCreatedEvent(
|
||||
context.Background(),
|
||||
&authenticator.NewAggregate("user1", "org1").Aggregate,
|
||||
"user1",
|
||||
"$plain$x$password",
|
||||
false,
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
userPasswordHasher: mockPasswordHasher("x"),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
user: &SetSchemaUserPassword{
|
||||
UserID: "user1",
|
||||
Password: "password",
|
||||
ChangeRequired: false,
|
||||
},
|
||||
},
|
||||
res{
|
||||
details: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"password set, ok",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
filterSchemaUserPasswordExisting(),
|
||||
filterPasswordComplexityPolicyExisting(),
|
||||
expectPush(
|
||||
authenticator.NewPasswordCreatedEvent(
|
||||
context.Background(),
|
||||
&authenticator.NewAggregate("user1", "org1").Aggregate,
|
||||
"user1",
|
||||
"$plain$x$password",
|
||||
false,
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
userPasswordHasher: mockPasswordHasher("x"),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
user: &SetSchemaUserPassword{
|
||||
UserID: "user1",
|
||||
Password: "password",
|
||||
ChangeRequired: false,
|
||||
},
|
||||
},
|
||||
res{
|
||||
details: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"password set, changeRequired, ok",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
filterSchemaUserPasswordExisting(),
|
||||
filterPasswordComplexityPolicyExisting(),
|
||||
expectPush(
|
||||
authenticator.NewPasswordCreatedEvent(
|
||||
context.Background(),
|
||||
&authenticator.NewAggregate("user1", "org1").Aggregate,
|
||||
"user1",
|
||||
"$plain$x$password",
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
userPasswordHasher: mockPasswordHasher("x"),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
user: &SetSchemaUserPassword{
|
||||
UserID: "user1",
|
||||
Password: "password",
|
||||
ChangeRequired: true,
|
||||
},
|
||||
},
|
||||
res{
|
||||
details: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
userPasswordHasher: tt.fields.userPasswordHasher,
|
||||
}
|
||||
details, err := c.SetSchemaUserPassword(tt.args.ctx, tt.args.user)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
if tt.res.err != nil && !tt.res.err(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assertObjectDetails(t, tt.res.details, details)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommands_DeleteSchemaUserPassword(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
resourceOwner string
|
||||
id string
|
||||
}
|
||||
type res struct {
|
||||
details *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"no ID, error",
|
||||
fields{
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
id: "",
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "COMMAND-PoSU5BOZCi", "Errors.IDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"password not existing, error",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
id: "notexisting",
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, zerrors.ThrowNotFound(nil, "TODO", "TODO"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"password already removed, error",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
authenticator.NewPasswordCreatedEvent(
|
||||
context.Background(),
|
||||
&authenticator.NewAggregate("user1", "org1").Aggregate,
|
||||
"id1",
|
||||
"hash",
|
||||
false,
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
authenticator.NewPasswordDeletedEvent(
|
||||
context.Background(),
|
||||
&authenticator.NewAggregate("user1", "org1").Aggregate,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
id: "user1",
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, zerrors.ThrowNotFound(nil, "TODO", "TODO"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"no permission, error",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
filterSchemaUserPasswordExisting(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
id: "user1",
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, zerrors.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"password removed, ok",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
filterSchemaUserPasswordExisting(),
|
||||
expectPush(
|
||||
authenticator.NewPasswordDeletedEvent(
|
||||
context.Background(),
|
||||
&authenticator.NewAggregate("user1", "org1").Aggregate,
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
id: "user1",
|
||||
},
|
||||
res{
|
||||
details: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
details, err := c.DeleteSchemaUserPassword(tt.args.ctx, tt.args.resourceOwner, tt.args.id)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
if tt.res.err != nil && !tt.res.err(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assertObjectDetails(t, tt.res.details, details)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -75,6 +75,34 @@ func (c *Commands) getSchemaUsernameExistsWithPermission(ctx context.Context, re
|
||||
return writeModel, nil
|
||||
}
|
||||
|
||||
func existingSchemaUser(ctx context.Context, c *Commands, resourceOwner, userID string) (*UserV3WriteModel, error) {
|
||||
if userID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-aS3Vz5t6BS", "Errors.IDMissing")
|
||||
}
|
||||
existingUser, err := c.getSchemaUserExists(ctx, resourceOwner, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !existingUser.Exists() {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-6T2xrOHxTx", "Errors.User.NotFound")
|
||||
}
|
||||
|
||||
if err := c.checkPermissionUpdateUser(ctx, existingUser.ResourceOwner, existingUser.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
existingSchema, err := c.getSchemaWriteModelByID(ctx, "", existingUser.SchemaID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !existingSchema.Exists() {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-6T2xrOHxTx", "TODO")
|
||||
}
|
||||
|
||||
//TODO possible authenticators check
|
||||
return existingUser, nil
|
||||
}
|
||||
|
||||
func existingSchemaUserWithPermission(ctx context.Context, c *Commands, resourceOwner, userID string) (*UserV3WriteModel, error) {
|
||||
if userID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-aS3Vz5t6BS", "Errors.IDMissing")
|
||||
|
@ -391,7 +391,7 @@ func TestCommands_DeleteUsername(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
"username added, isOrgSpecific, ok",
|
||||
"username removed, isOrgSpecific, ok",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
filterUsernameExisting(true),
|
||||
|
Loading…
x
Reference in New Issue
Block a user