fix: add authenticators to user v3 endpoints

This commit is contained in:
Stefan Benz 2024-09-27 17:52:18 +02:00
parent ee5de6563a
commit 418771b466
No known key found for this signature in database
GPG Key ID: 071AA751ED4F9D31
15 changed files with 590 additions and 229 deletions

View File

@ -24,14 +24,38 @@ func (s *Server) SetPassword(ctx context.Context, req *user.SetPasswordRequest)
} }
func setPasswordRequestToSetSchemaUserPassword(req *user.SetPasswordRequest) *command.SetSchemaUserPassword { func setPasswordRequestToSetSchemaUserPassword(req *user.SetPasswordRequest) *command.SetSchemaUserPassword {
pw, verification := setPasswordToSetSchemaUserPassword(req.GetNewPassword())
return &command.SetSchemaUserPassword{ return &command.SetSchemaUserPassword{
ResourceOwner: organizationToUpdateResourceOwner(req.Organization), ResourceOwner: organizationToUpdateResourceOwner(req.Organization),
UserID: req.GetId(), UserID: req.GetId(),
Password: req.GetNewPassword().GetPassword(), Password: pw,
EncodedPasswordHash: req.GetNewPassword().GetHash(), Verification: verification,
ChangeRequired: req.GetNewPassword().GetChangeRequired(), }
VerificationCode: req.GetNewPassword().GetVerificationCode(), }
CurrentPassword: req.GetNewPassword().GetCurrentPassword(),
func setPasswordToSetSchemaUserPassword(req *user.SetPassword) (*command.SchemaUserPassword, *command.SchemaUserPasswordVerification) {
return setPasswordToSchemaUserPassword(req.GetPassword(), req.GetHash(), req.GetChangeRequired()),
setPasswordToSchemaUserPasswordVerification(req.GetCurrentPassword(), req.GetVerificationCode())
}
func setPasswordToSchemaUserPassword(pw string, hash string, changeRequired bool) *command.SchemaUserPassword {
if pw == "" && hash == "" {
return nil
}
return &command.SchemaUserPassword{
Password: pw,
EncodedPasswordHash: hash,
ChangeRequired: changeRequired,
}
}
func setPasswordToSchemaUserPasswordVerification(pw string, code string) *command.SchemaUserPasswordVerification {
if pw == "" && code == "" {
return nil
}
return &command.SchemaUserPasswordVerification{
CurrentPassword: pw,
Code: code,
} }
} }

View File

@ -25,21 +25,32 @@ func (s *Server) AddPersonalAccessToken(ctx context.Context, req *user.AddPerson
return &user.AddPersonalAccessTokenResponse{ return &user.AddPersonalAccessTokenResponse{
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner), Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner),
PersonalAccessTokenId: details.ID, PersonalAccessTokenId: details.ID,
PersonalAccessToken: pat.Token, PersonalAccessToken: pat.PAT.Token,
}, nil }, nil
} }
func addPersonalAccessTokenRequestToAddPAT(req *user.AddPersonalAccessTokenRequest) *command.AddPAT { func addPersonalAccessTokenRequestToAddPAT(req *user.AddPersonalAccessTokenRequest) *command.AddPAT {
expDate := time.Time{} if req == nil {
if req.GetPersonalAccessToken().GetExpirationDate() != nil { return nil
expDate = req.GetPersonalAccessToken().GetExpirationDate().AsTime()
} }
return &command.AddPAT{ return &command.AddPAT{
ResourceOwner: organizationToUpdateResourceOwner(req.Organization), ResourceOwner: organizationToUpdateResourceOwner(req.Organization),
UserID: req.GetId(), UserID: req.GetId(),
PAT: setPersonalAccessTokenToAddPAT(req.GetPersonalAccessToken()),
}
}
func setPersonalAccessTokenToAddPAT(set *user.SetPersonalAccessToken) *command.PAT {
if set == nil {
return nil
}
expDate := time.Time{}
if set.GetExpirationDate() != nil {
expDate = set.GetExpirationDate().AsTime()
}
return &command.PAT{
ExpirationDate: expDate, ExpirationDate: expDate,
Scope: []string{oidc.ScopeOpenID, oidc.ScopeProfile, z_oidc.ScopeUserMetaData, z_oidc.ScopeResourceOwner}, Scopes: []string{oidc.ScopeOpenID, oidc.ScopeProfile, z_oidc.ScopeUserMetaData, z_oidc.ScopeResourceOwner},
} }
} }

View File

@ -22,19 +22,31 @@ func (s *Server) AddPublicKey(ctx context.Context, req *user.AddPublicKeyRequest
return &user.AddPublicKeyResponse{ return &user.AddPublicKeyResponse{
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner), Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner),
PublicKeyId: details.ID, PublicKeyId: details.ID,
PrivateKey: pk.PrivateKey, PrivateKey: pk.GetPrivateKey(),
}, nil }, nil
} }
func addPublicKeyRequestToAddPublicKey(req *user.AddPublicKeyRequest) *command.AddPublicKey { func addPublicKeyRequestToAddPublicKey(req *user.AddPublicKeyRequest) *command.AddPublicKey {
expDate := time.Time{} if req == nil {
if req.GetPublicKey().GetExpirationDate() != nil { return nil
expDate = req.GetPublicKey().GetExpirationDate().AsTime()
} }
return &command.AddPublicKey{ return &command.AddPublicKey{
ResourceOwner: organizationToUpdateResourceOwner(req.Organization), ResourceOwner: organizationToUpdateResourceOwner(req.Organization),
UserID: req.GetId(), UserID: req.GetId(),
PublicKey: req.GetPublicKey().GetPublicKey().GetPublicKey(), PublicKey: setPublicKeyToAddPublicKey(req.GetPublicKey()),
}
}
func setPublicKeyToAddPublicKey(req *user.SetPublicKey) *command.PublicKey {
if req == nil {
return nil
}
expDate := time.Time{}
if req.GetExpirationDate() != nil {
expDate = req.GetExpirationDate().AsTime()
}
return &command.PublicKey{
PublicKey: req.GetPublicKey().GetPublicKey(),
ExpirationDate: expDate, ExpirationDate: expDate,
} }
} }

View File

@ -30,20 +30,54 @@ func (s *Server) CreateUser(ctx context.Context, req *user.CreateUserRequest) (_
}, nil }, nil
} }
type authenticators struct {
Usernames []*command.Username
Password *command.SchemaUserPassword
PublicKeys []*command.PublicKey
PATs []*command.PAT
}
func setAuthenticatorsToAuthenticators(set *user.SetAuthenticators) *authenticators {
if set == nil {
return nil
}
auths := &authenticators{}
for _, u := range set.GetUsernames() {
auths.Usernames = append(auths.Usernames, setUsernameToAddUsername(u))
}
if set.GetPassword() != nil {
auths.Password = setPasswordToSchemaUserPassword(set.GetPassword().GetPassword(), set.GetPassword().GetHash(), set.GetPassword().GetChangeRequired())
}
for _, p := range set.GetPublicKey() {
auths.PublicKeys = append(auths.PublicKeys, setPublicKeyToAddPublicKey(p))
}
for _, p := range set.GetPersonalAccessToken() {
auths.PATs = append(auths.PATs, setPersonalAccessTokenToAddPAT(p))
}
return auths
}
func createUserRequestToCreateSchemaUser(ctx context.Context, req *user.CreateUserRequest) (*command.CreateSchemaUser, error) { func createUserRequestToCreateSchemaUser(ctx context.Context, req *user.CreateUserRequest) (*command.CreateSchemaUser, error) {
data, err := req.GetUser().GetData().MarshalJSON() data, err := req.GetUser().GetData().MarshalJSON()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &command.CreateSchemaUser{ user := &command.CreateSchemaUser{
ResourceOwner: organizationToCreateResourceOwner(ctx, req.Organization), ResourceOwner: organizationToCreateResourceOwner(ctx, req.Organization),
SchemaID: req.GetUser().GetSchemaId(), SchemaID: req.GetUser().GetSchemaId(),
ID: req.GetUser().GetUserId(), ID: req.GetUser().GetUserId(),
Data: data, Data: data,
Email: setEmailToEmail(req.GetUser().GetContact().GetEmail()), Email: setEmailToEmail(req.GetUser().GetContact().GetEmail()),
Phone: setPhoneToPhone(req.GetUser().GetContact().GetPhone()), Phone: setPhoneToPhone(req.GetUser().GetContact().GetPhone()),
}, nil }
if auths := setAuthenticatorsToAuthenticators(req.GetUser().Authenticators); auths != nil {
user.Usernames = auths.Usernames
user.Password = auths.Password
user.PublicKeys = auths.PublicKeys
user.PATs = auths.PATs
}
return user, nil
} }
func organizationToCreateResourceOwner(ctx context.Context, org *object.Organization) string { func organizationToCreateResourceOwner(ctx context.Context, org *object.Organization) string {

View File

@ -24,11 +24,23 @@ func (s *Server) AddUsername(ctx context.Context, req *user.AddUsernameRequest)
} }
func addUsernameRequestToAddUsername(req *user.AddUsernameRequest) *command.AddUsername { func addUsernameRequestToAddUsername(req *user.AddUsernameRequest) *command.AddUsername {
if req == nil {
return nil
}
return &command.AddUsername{ return &command.AddUsername{
ResourceOwner: organizationToUpdateResourceOwner(req.Organization), ResourceOwner: organizationToUpdateResourceOwner(req.Organization),
UserID: req.GetId(), UserID: req.GetId(),
Username: req.GetUsername().GetUsername(), Username: setUsernameToAddUsername(req.GetUsername()),
IsOrgSpecific: req.GetUsername().GetIsOrganizationSpecific(), }
}
func setUsernameToAddUsername(req *user.SetUsername) *command.Username {
if req == nil {
return nil
}
return &command.Username{
Username: req.GetUsername(),
IsOrgSpecific: req.GetIsOrganizationSpecific(),
} }
} }

View File

@ -19,6 +19,11 @@ type CreateSchemaUser struct {
ReturnCodeEmail *string ReturnCodeEmail *string
Phone *Phone Phone *Phone
ReturnCodePhone *string ReturnCodePhone *string
Usernames []*Username
Password *SchemaUserPassword
PublicKeys []*PublicKey
PATs []*PAT
} }
func (s *CreateSchemaUser) Valid() (err error) { func (s *CreateSchemaUser) Valid() (err error) {
@ -84,6 +89,34 @@ func (c *Commands) CreateSchemaUser(ctx context.Context, user *CreateSchemaUser)
if codePhone != "" { if codePhone != "" {
user.ReturnCodePhone = &codePhone user.ReturnCodePhone = &codePhone
} }
for i := range user.Usernames {
_, usernameEvents, err := c.addUsername(ctx, writeModel.ResourceOwner, writeModel.AggregateID, user.Usernames[i])
if err != nil {
return nil, err
}
events = append(events, usernameEvents...)
}
if user.Password != nil {
_, pwEvents, err := c.setSchemaUserPassword(ctx, writeModel.ResourceOwner, writeModel.AggregateID, nil, user.Password)
if err != nil {
return nil, err
}
events = append(events, pwEvents...)
}
for i := range user.PublicKeys {
_, pkEvents, err := c.addPublicKey(ctx, writeModel.ResourceOwner, writeModel.AggregateID, user.PublicKeys[i])
if err != nil {
return nil, err
}
events = append(events, pkEvents...)
}
for i := range user.PATs {
_, patEvents, err := c.addPAT(ctx, writeModel.ResourceOwner, writeModel.AggregateID, user.PATs[i])
if err != nil {
return nil, err
}
events = append(events, patEvents...)
}
return c.pushAppendAndReduceDetails(ctx, writeModel, events...) return c.pushAppendAndReduceDetails(ctx, writeModel, events...)
} }

View File

@ -6,6 +6,7 @@ import (
"github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/telemetry/tracing"
"github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/internal/zerrors"
) )
@ -14,19 +15,22 @@ type SetSchemaUserPassword struct {
ResourceOwner string ResourceOwner string
UserID string UserID string
Verification *SchemaUserPasswordVerification
Password *SchemaUserPassword
}
type SchemaUserPasswordVerification struct {
CurrentPassword string
Code string
}
type SchemaUserPassword struct {
Password string Password string
EncodedPasswordHash string EncodedPasswordHash string
ChangeRequired bool ChangeRequired bool
CurrentPassword string
VerificationCode string
} }
func (p *SetSchemaUserPassword) Validate(hasher *crypto.Hasher) (err error) { func (p *SchemaUserPassword) Validate(hasher *crypto.Hasher) (err error) {
if p.UserID == "" {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-aS3Vz5t6BS", "Errors.IDMissing")
}
if p.EncodedPasswordHash != "" { if p.EncodedPasswordHash != "" {
if !hasher.EncodingSupported(p.EncodedPasswordHash) { if !hasher.EncodingSupported(p.EncodedPasswordHash) {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-oz74onzvqr", "Errors.User.Password.NotSupported") return zerrors.ThrowInvalidArgument(nil, "COMMAND-oz74onzvqr", "Errors.User.Password.NotSupported")
@ -35,56 +39,75 @@ func (p *SetSchemaUserPassword) Validate(hasher *crypto.Hasher) (err error) {
if p.Password == "" && p.EncodedPasswordHash == "" { if p.Password == "" && p.EncodedPasswordHash == "" {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-3klek4sbns", "Errors.User.Password.Empty") return zerrors.ThrowInvalidArgument(nil, "COMMAND-3klek4sbns", "Errors.User.Password.Empty")
} }
return nil return nil
} }
func (c *Commands) SetSchemaUserPassword(ctx context.Context, user *SetSchemaUserPassword) (*domain.ObjectDetails, error) { func (c *Commands) SetSchemaUserPassword(ctx context.Context, set *SetSchemaUserPassword) (*domain.ObjectDetails, error) {
if err := user.Validate(c.userPasswordHasher); err != nil { if set.UserID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-aS3Vz5t6BS", "Errors.IDMissing")
}
if err := set.Password.Validate(c.userPasswordHasher); err != nil {
return nil, err return nil, err
} }
schemauser, err := existingSchemaUser(ctx, c, set.ResourceOwner, set.UserID)
schemaUser := &schemaUserPassword{
Create: true,
ResourceOwner: user.ResourceOwner,
UserID: user.UserID,
VerificationCode: user.VerificationCode,
CurrentPassword: user.CurrentPassword,
Password: user.Password,
EncodedPasswordHash: user.EncodedPasswordHash,
}
writeModel, err := c.getSchemaUserPasswordWithVerification(ctx, schemaUser)
if err != nil { if err != nil {
return nil, err return nil, err
} }
set.ResourceOwner = schemauser.ResourceOwner
// If password is provided, let's check if is compliant with the policy. _, err = existingSchema(ctx, c, "", schemauser.SchemaID)
// If only a encodedPassword is passed, we can skip this. if err != nil {
if user.Password != "" { return nil, err
if err = c.checkPasswordComplexity(ctx, user.Password, writeModel.ResourceOwner); err != nil {
return nil, err
}
} }
// TODO check for possible authenticators
encodedPassword := schemaUser.EncodedPasswordHash writeModel, events, err := c.setSchemaUserPassword(ctx, set.ResourceOwner, set.UserID, set.Verification, set.Password)
if encodedPassword == "" && user.Password != "" {
encodedPassword, err = c.userPasswordHasher.Hash(user.Password)
if err = convertPasswapErr(err); err != nil {
return nil, err
}
}
events, err := writeModel.NewCreate(ctx,
encodedPassword,
user.ChangeRequired,
)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return c.pushAppendAndReduceDetails(ctx, writeModel, events...) return c.pushAppendAndReduceDetails(ctx, writeModel, events...)
} }
func (c *Commands) setSchemaUserPassword(ctx context.Context, resourceOwner, userID string, verification *SchemaUserPasswordVerification, set *SchemaUserPassword) (*PasswordV3WriteModel, []eventstore.Command, error) {
if set == nil {
return nil, nil, nil
}
schemaUser := &schemaUserPassword{
Set: true,
ResourceOwner: resourceOwner,
UserID: userID,
Verification: verification,
NewPassword: set,
}
writeModel, err := c.getSchemaUserPasswordWithVerification(ctx, schemaUser)
if err != nil {
return nil, nil, err
}
// If password is provided, let's check if is compliant with the policy.
// If only a encodedPassword is passed, we can skip this.
if set.Password != "" {
if err = c.checkPasswordComplexity(ctx, set.Password, writeModel.ResourceOwner); err != nil {
return nil, nil, err
}
}
encodedPassword := schemaUser.NewPassword.EncodedPasswordHash
if encodedPassword == "" && set.Password != "" {
encodedPassword, err = c.userPasswordHasher.Hash(set.Password)
if err = convertPasswapErr(err); err != nil {
return nil, nil, err
}
}
events, err := writeModel.NewCreate(ctx,
encodedPassword,
set.ChangeRequired,
)
if err != nil {
return nil, nil, err
}
return writeModel, events, nil
}
type RequestSchemaUserPasswordReset struct { type RequestSchemaUserPasswordReset struct {
ResourceOwner string ResourceOwner string
UserID string UserID string
@ -132,13 +155,11 @@ func (c *Commands) DeleteSchemaUserPassword(ctx context.Context, resourceOwner,
} }
type schemaUserPassword struct { type schemaUserPassword struct {
Create bool Set bool
ResourceOwner string ResourceOwner string
UserID string UserID string
VerificationCode string Verification *SchemaUserPasswordVerification
CurrentPassword string NewPassword *SchemaUserPassword
Password string
EncodedPasswordHash string
} }
func (c *Commands) getSchemaUserPasswordWM(ctx context.Context, resourceOwner, id string) (*PasswordV3WriteModel, error) { func (c *Commands) getSchemaUserPasswordWM(ctx context.Context, resourceOwner, id string) (*PasswordV3WriteModel, error) {
@ -165,23 +186,21 @@ func (c *Commands) getSchemaUserPasswordWithVerification(ctx context.Context, us
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := writeModel.Exists(); user.Create && err != nil { if err := writeModel.Exists(); !user.Set && err != nil {
schemauser, err := existingSchemaUser(ctx, c, user.ResourceOwner, user.UserID) return nil, err
if err != nil {
return nil, err
}
writeModel.ResourceOwner = schemauser.ResourceOwner
} }
// if no verification is set, the user must have the permission to change the password // if no verification is set, the user must have the permission to change the password
verification := c.setSchemaUserPasswordWithPermission(writeModel.UserID, writeModel.ResourceOwner) verification := c.setSchemaUserPasswordWithPermission(writeModel.UserID, writeModel.ResourceOwner)
// otherwise check the password code... if user.Verification != nil {
if user.VerificationCode != "" { // otherwise check the password code...
verification = c.setSchemaUserPasswordWithVerifyCode(writeModel.CodeCreationDate, writeModel.CodeExpiry, writeModel.Code, user.VerificationCode) if user.Verification.Code != "" {
} verification = c.setSchemaUserPasswordWithVerifyCode(writeModel.CodeCreationDate, writeModel.CodeExpiry, writeModel.Code, user.Verification.Code)
// ...or old password }
if user.CurrentPassword != "" { // ...or old password
verification = c.checkSchemaUserCurrentPassword(user.Password, user.EncodedPasswordHash, user.CurrentPassword, writeModel.EncodedHash) if user.Verification.CurrentPassword != "" {
verification = c.checkSchemaUserCurrentPassword(user.NewPassword.Password, user.NewPassword.EncodedPasswordHash, user.Verification.CurrentPassword, writeModel.EncodedHash)
}
} }
if verification != nil { if verification != nil {
@ -191,7 +210,7 @@ func (c *Commands) getSchemaUserPasswordWithVerification(ctx context.Context, us
} }
// use the new hash from the verification in case there is one (e.g. existing pw check) // use the new hash from the verification in case there is one (e.g. existing pw check)
if newEncodedPassword != "" { if newEncodedPassword != "" {
user.EncodedPasswordHash = newEncodedPassword user.NewPassword.EncodedPasswordHash = newEncodedPassword
} }
} }
return writeModel, nil return writeModel, nil

View File

@ -94,7 +94,8 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
args{ args{
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &SetSchemaUserPassword{ user: &SetSchemaUserPassword{
UserID: "user1", UserID: "user1",
Password: &SchemaUserPassword{},
}, },
}, },
res{ res{
@ -108,7 +109,6 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
fields{ fields{
eventstore: expectEventstore( eventstore: expectEventstore(
expectFilter(), expectFilter(),
expectFilter(),
), ),
checkPermission: newMockPermissionCheckAllowed(), checkPermission: newMockPermissionCheckAllowed(),
}, },
@ -116,7 +116,7 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &SetSchemaUserPassword{ user: &SetSchemaUserPassword{
UserID: "notexisting", UserID: "notexisting",
Password: "password", Password: &SchemaUserPassword{Password: "password"},
}, },
}, },
res{ res{
@ -129,8 +129,9 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
"no permission, error", "no permission, error",
fields{ fields{
eventstore: expectEventstore( eventstore: expectEventstore(
expectFilter(),
filterSchemaUserExisting(), filterSchemaUserExisting(),
filterSchemaExisting(),
expectFilter(),
), ),
checkPermission: newMockPermissionCheckNotAllowed(), checkPermission: newMockPermissionCheckNotAllowed(),
}, },
@ -138,7 +139,7 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &SetSchemaUserPassword{ user: &SetSchemaUserPassword{
UserID: "user1", UserID: "user1",
Password: "password", Password: &SchemaUserPassword{Password: "password"},
}, },
}, },
res{ res{
@ -151,8 +152,9 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
"password added, ok", "password added, ok",
fields{ fields{
eventstore: expectEventstore( eventstore: expectEventstore(
expectFilter(),
filterSchemaUserExisting(), filterSchemaUserExisting(),
filterSchemaExisting(),
expectFilter(),
filterPasswordComplexityPolicyExisting(), filterPasswordComplexityPolicyExisting(),
expectPush( expectPush(
authenticator.NewPasswordCreatedEvent( authenticator.NewPasswordCreatedEvent(
@ -170,9 +172,11 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
args{ args{
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &SetSchemaUserPassword{ user: &SetSchemaUserPassword{
UserID: "user1", UserID: "user1",
Password: "password", Password: &SchemaUserPassword{
ChangeRequired: false, Password: "password",
ChangeRequired: false,
},
}, },
}, },
res{ res{
@ -185,7 +189,9 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
"password set, complexity failed", "password set, complexity failed",
fields{ fields{
eventstore: expectEventstore( eventstore: expectEventstore(
filterSchemaUserPasswordExisting(), filterSchemaUserExisting(),
filterSchemaExisting(),
expectFilter(),
filterPasswordComplexityPolicyExisting(), filterPasswordComplexityPolicyExisting(),
expectPush( expectPush(
authenticator.NewPasswordCreatedEvent( authenticator.NewPasswordCreatedEvent(
@ -203,9 +209,11 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
args{ args{
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &SetSchemaUserPassword{ user: &SetSchemaUserPassword{
UserID: "user1", UserID: "user1",
Password: "password", Password: &SchemaUserPassword{
ChangeRequired: false, Password: "password",
ChangeRequired: false,
},
}, },
}, },
res{ res{
@ -218,7 +226,9 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
"password set, ok", "password set, ok",
fields{ fields{
eventstore: expectEventstore( eventstore: expectEventstore(
filterSchemaUserPasswordExisting(), filterSchemaUserExisting(),
filterSchemaExisting(),
expectFilter(),
filterPasswordComplexityPolicyExisting(), filterPasswordComplexityPolicyExisting(),
expectPush( expectPush(
authenticator.NewPasswordCreatedEvent( authenticator.NewPasswordCreatedEvent(
@ -236,9 +246,11 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
args{ args{
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &SetSchemaUserPassword{ user: &SetSchemaUserPassword{
UserID: "user1", UserID: "user1",
Password: "password", Password: &SchemaUserPassword{
ChangeRequired: false, Password: "password",
ChangeRequired: false,
},
}, },
}, },
res{ res{
@ -251,7 +263,9 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
"password set, changeRequired, ok", "password set, changeRequired, ok",
fields{ fields{
eventstore: expectEventstore( eventstore: expectEventstore(
filterSchemaUserPasswordExisting(), filterSchemaUserExisting(),
filterSchemaExisting(),
expectFilter(),
filterPasswordComplexityPolicyExisting(), filterPasswordComplexityPolicyExisting(),
expectPush( expectPush(
authenticator.NewPasswordCreatedEvent( authenticator.NewPasswordCreatedEvent(
@ -269,9 +283,11 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
args{ args{
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &SetSchemaUserPassword{ user: &SetSchemaUserPassword{
UserID: "user1", UserID: "user1",
Password: "password", Password: &SchemaUserPassword{
ChangeRequired: true, Password: "password",
ChangeRequired: true,
},
}, },
}, },
res{ res{
@ -284,7 +300,9 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
"password set, encoded, ok", "password set, encoded, ok",
fields{ fields{
eventstore: expectEventstore( eventstore: expectEventstore(
filterSchemaUserPasswordExisting(), filterSchemaUserExisting(),
filterSchemaExisting(),
expectFilter(),
filterPasswordComplexityPolicyExisting(), filterPasswordComplexityPolicyExisting(),
expectPush( expectPush(
authenticator.NewPasswordCreatedEvent( authenticator.NewPasswordCreatedEvent(
@ -302,10 +320,12 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
args{ args{
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &SetSchemaUserPassword{ user: &SetSchemaUserPassword{
UserID: "user1", UserID: "user1",
Password: "passwordnotused", Password: &SchemaUserPassword{
EncodedPasswordHash: "$plain$x$password2", Password: "passwordnotused",
ChangeRequired: false, EncodedPasswordHash: "$plain$x$password2",
ChangeRequired: false,
},
}, },
}, },
res{ res{
@ -318,6 +338,8 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
"password set, current password, ok", "password set, current password, ok",
fields{ fields{
eventstore: expectEventstore( eventstore: expectEventstore(
filterSchemaUserExisting(),
filterSchemaExisting(),
filterSchemaUserPasswordExisting(), filterSchemaUserPasswordExisting(),
filterPasswordComplexityPolicyExisting(), filterPasswordComplexityPolicyExisting(),
expectPush( expectPush(
@ -335,10 +357,14 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
args{ args{
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &SetSchemaUserPassword{ user: &SetSchemaUserPassword{
UserID: "user1", UserID: "user1",
Password: "password2", Password: &SchemaUserPassword{
CurrentPassword: "password", Password: "password2",
ChangeRequired: false, ChangeRequired: false,
},
Verification: &SchemaUserPasswordVerification{
CurrentPassword: "password",
},
}, },
}, },
res{ res{
@ -351,6 +377,8 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
"password set, current password, ok", "password set, current password, ok",
fields{ fields{
eventstore: expectEventstore( eventstore: expectEventstore(
filterSchemaUserExisting(),
filterSchemaExisting(),
filterSchemaUserPasswordExisting(), filterSchemaUserPasswordExisting(),
filterPasswordComplexityPolicyExisting(), filterPasswordComplexityPolicyExisting(),
expectPush( expectPush(
@ -368,10 +396,14 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
args{ args{
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &SetSchemaUserPassword{ user: &SetSchemaUserPassword{
UserID: "user1", UserID: "user1",
Password: "password2", Password: &SchemaUserPassword{
CurrentPassword: "password", Password: "password2",
ChangeRequired: false, ChangeRequired: false,
},
Verification: &SchemaUserPasswordVerification{
CurrentPassword: "password",
},
}, },
}, },
res{ res{
@ -383,6 +415,8 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
"password set, current password, failed", "password set, current password, failed",
fields{ fields{
eventstore: expectEventstore( eventstore: expectEventstore(
filterSchemaUserExisting(),
filterSchemaExisting(),
filterSchemaUserPasswordExisting(), filterSchemaUserPasswordExisting(),
), ),
userPasswordHasher: mockPasswordHasher("x"), userPasswordHasher: mockPasswordHasher("x"),
@ -390,10 +424,14 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
args{ args{
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &SetSchemaUserPassword{ user: &SetSchemaUserPassword{
UserID: "user1", UserID: "user1",
Password: "password2", Password: &SchemaUserPassword{
CurrentPassword: "notreally", Password: "password2",
ChangeRequired: false, ChangeRequired: false,
},
Verification: &SchemaUserPasswordVerification{
CurrentPassword: "notreally",
},
}, },
}, },
res{ res{
@ -406,6 +444,8 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
"password set, code, ok", "password set, code, ok",
fields{ fields{
eventstore: expectEventstore( eventstore: expectEventstore(
filterSchemaUserExisting(),
filterSchemaExisting(),
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
authenticator.NewPasswordCreatedEvent( authenticator.NewPasswordCreatedEvent(
@ -449,10 +489,14 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
args{ args{
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &SetSchemaUserPassword{ user: &SetSchemaUserPassword{
UserID: "user1", UserID: "user1",
Password: "password2", Password: &SchemaUserPassword{
VerificationCode: "code", Password: "password2",
ChangeRequired: false, ChangeRequired: false,
},
Verification: &SchemaUserPasswordVerification{
Code: "code",
},
}, },
}, },
res{ res{
@ -465,6 +509,8 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
"password set, code, failed", "password set, code, failed",
fields{ fields{
eventstore: expectEventstore( eventstore: expectEventstore(
filterSchemaUserExisting(),
filterSchemaExisting(),
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
authenticator.NewPasswordCreatedEvent( authenticator.NewPasswordCreatedEvent(
@ -498,10 +544,14 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
args{ args{
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &SetSchemaUserPassword{ user: &SetSchemaUserPassword{
UserID: "user1", UserID: "user1",
Password: "password2", Password: &SchemaUserPassword{
VerificationCode: "notreally", Password: "password2",
ChangeRequired: false, ChangeRequired: false,
},
Verification: &SchemaUserPasswordVerification{
Code: "notreally",
},
}, },
}, },
res{ res{
@ -514,6 +564,8 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
"password set, code, no code", "password set, code, no code",
fields{ fields{
eventstore: expectEventstore( eventstore: expectEventstore(
filterSchemaUserExisting(),
filterSchemaExisting(),
filterSchemaUserPasswordExisting(), filterSchemaUserPasswordExisting(),
), ),
userPasswordHasher: mockPasswordHasher("x"), userPasswordHasher: mockPasswordHasher("x"),
@ -522,10 +574,14 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
args{ args{
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &SetSchemaUserPassword{ user: &SetSchemaUserPassword{
UserID: "user1", UserID: "user1",
Password: "password2", Password: &SchemaUserPassword{
VerificationCode: "notreally", Password: "password2",
ChangeRequired: false, ChangeRequired: false,
},
Verification: &SchemaUserPasswordVerification{
Code: "notreally",
},
}, },
}, },
res{ res{
@ -618,7 +674,7 @@ func TestCommands_RequestSchemaUserPasswordReset(t *testing.T) {
"no permission, error", "no permission, error",
fields{ fields{
eventstore: expectEventstore( eventstore: expectEventstore(
expectFilter(), filterSchemaUserPasswordExisting(),
), ),
checkPermission: newMockPermissionCheckNotAllowed(), checkPermission: newMockPermissionCheckNotAllowed(),
}, },

View File

@ -7,6 +7,7 @@ import (
"github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/internal/zerrors"
) )
@ -18,27 +19,24 @@ type AddPAT struct {
ResourceOwner string ResourceOwner string
UserID string UserID string
PAT *PAT
}
type PAT struct {
ExpirationDate time.Time ExpirationDate time.Time
Scope []string Scopes []string
Token string Token string
} }
func (wm *AddPAT) GetExpirationDate() time.Time { func (c *Commands) AddPAT(ctx context.Context, add *AddPAT) (*domain.ObjectDetails, error) {
return wm.ExpirationDate if add.UserID == "" {
}
func (wm *AddPAT) SetExpirationDate(date time.Time) {
wm.ExpirationDate = date
}
func (c *Commands) AddPAT(ctx context.Context, pat *AddPAT) (*domain.ObjectDetails, error) {
if pat.UserID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-14sGR7lTaj", "Errors.IDMissing") return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-14sGR7lTaj", "Errors.IDMissing")
} }
schemauser, err := existingSchemaUser(ctx, c, pat.ResourceOwner, pat.UserID) schemauser, err := existingSchemaUser(ctx, c, add.ResourceOwner, add.UserID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
add.ResourceOwner = schemauser.ResourceOwner
_, err = existingSchema(ctx, c, "", schemauser.SchemaID) _, err = existingSchema(ctx, c, "", schemauser.SchemaID)
if err != nil { if err != nil {
@ -46,26 +44,36 @@ func (c *Commands) AddPAT(ctx context.Context, pat *AddPAT) (*domain.ObjectDetai
} }
// TODO check for possible authenticators // TODO check for possible authenticators
id, err := c.idGenerator.Next() writeModel, events, err := c.addPAT(ctx, add.ResourceOwner, add.UserID, add.PAT)
if err != nil {
return nil, err
}
writeModel, err := c.getSchemaPATWM(ctx, schemauser.ResourceOwner, schemauser.AggregateID, id)
if err != nil {
return nil, err
}
events, err := writeModel.NewCreate(ctx, pat.ExpirationDate, pat.Scope)
if err != nil {
return nil, err
}
pat.Token, err = createSchemaUserPAT(c.keyAlgorithm, writeModel.AggregateID, pat.UserID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return c.pushAppendAndReduceDetails(ctx, writeModel, events...) return c.pushAppendAndReduceDetails(ctx, writeModel, events...)
} }
func (c *Commands) addPAT(ctx context.Context, resourceOwner, userID string, add *PAT) (*PATV3WriteModel, []eventstore.Command, error) {
if add == nil {
return nil, nil, nil
}
id, err := c.idGenerator.Next()
if err != nil {
return nil, nil, err
}
writeModel, err := c.getSchemaPATWM(ctx, resourceOwner, userID, id)
if err != nil {
return nil, nil, err
}
events, err := writeModel.NewCreate(ctx, add.ExpirationDate, add.Scopes)
if err != nil {
return nil, nil, err
}
add.Token, err = createSchemaUserPAT(c.keyAlgorithm, writeModel.AggregateID, writeModel.UserID)
if err != nil {
return nil, nil, err
}
return writeModel, events, nil
}
func (c *Commands) DeletePAT(ctx context.Context, resourceOwner, userID, id string) (*domain.ObjectDetails, error) { func (c *Commands) DeletePAT(ctx context.Context, resourceOwner, userID, id string) (*domain.ObjectDetails, error) {
if userID == "" { if userID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-hzqeAXW1qP", "Errors.IDMissing") return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-hzqeAXW1qP", "Errors.IDMissing")

View File

@ -62,8 +62,10 @@ func TestCommands_AddPAT(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(), checkPermission: newMockPermissionCheckAllowed(),
}, },
args{ args{
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &AddPAT{}, user: &AddPAT{
PAT: &PAT{},
},
}, },
res{ res{
err: func(err error) bool { err: func(err error) bool {
@ -83,6 +85,7 @@ func TestCommands_AddPAT(t *testing.T) {
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &AddPAT{ user: &AddPAT{
UserID: "notexisting", UserID: "notexisting",
PAT: &PAT{},
}, },
}, },
res{ res{
@ -106,6 +109,7 @@ func TestCommands_AddPAT(t *testing.T) {
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &AddPAT{ user: &AddPAT{
UserID: "user1", UserID: "user1",
PAT: &PAT{},
}, },
}, },
res{ res{
@ -127,6 +131,7 @@ func TestCommands_AddPAT(t *testing.T) {
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &AddPAT{ user: &AddPAT{
UserID: "user1", UserID: "user1",
PAT: &PAT{},
}, },
}, },
res{ res{
@ -160,7 +165,9 @@ func TestCommands_AddPAT(t *testing.T) {
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &AddPAT{ user: &AddPAT{
UserID: "user1", UserID: "user1",
Scope: []string{"first", "second", "third"}, PAT: &PAT{
Scopes: []string{"first", "second", "third"},
},
}, },
}, },
res{ res{
@ -194,9 +201,11 @@ func TestCommands_AddPAT(t *testing.T) {
args{ args{
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &AddPAT{ user: &AddPAT{
UserID: "user1", UserID: "user1",
ExpirationDate: time.Date(2024, time.January, 1, 1, 1, 1, 1, time.UTC), PAT: &PAT{
Scope: []string{"first", "second", "third"}, ExpirationDate: time.Date(2024, time.January, 1, 1, 1, 1, 1, time.UTC),
Scopes: []string{"first", "second", "third"},
},
}, },
}, },
res{ res{
@ -224,7 +233,7 @@ func TestCommands_AddPAT(t *testing.T) {
} }
if tt.res.err == nil { if tt.res.err == nil {
assertObjectDetails(t, tt.res.details, details) assertObjectDetails(t, tt.res.details, details)
assert.Equal(t, tt.res.token, tt.args.user.Token) assert.Equal(t, tt.res.token, tt.args.user.PAT.Token)
} }
}) })
} }

View File

@ -5,6 +5,7 @@ import (
"time" "time"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/internal/zerrors"
) )
@ -12,35 +13,47 @@ type AddPublicKey struct {
ResourceOwner string ResourceOwner string
UserID string UserID string
PublicKey *PublicKey
}
func (wm *AddPublicKey) GetPrivateKey() []byte {
if wm.PublicKey == nil {
return nil
}
return wm.PublicKey.PrivateKey
}
type PublicKey struct {
ExpirationDate time.Time ExpirationDate time.Time
PublicKey []byte PublicKey []byte
PrivateKey []byte PrivateKey []byte
} }
func (wm *AddPublicKey) GetExpirationDate() time.Time { func (wm *PublicKey) GetExpirationDate() time.Time {
return wm.ExpirationDate return wm.ExpirationDate
} }
func (wm *AddPublicKey) SetExpirationDate(date time.Time) { func (wm *PublicKey) SetExpirationDate(date time.Time) {
wm.ExpirationDate = date wm.ExpirationDate = date
} }
func (wm *AddPublicKey) SetPublicKey(data []byte) { func (wm *PublicKey) SetPublicKey(data []byte) {
wm.PublicKey = data wm.PublicKey = data
} }
func (wm *AddPublicKey) SetPrivateKey(data []byte) { func (wm *PublicKey) SetPrivateKey(data []byte) {
wm.PrivateKey = data wm.PrivateKey = data
} }
func (c *Commands) AddPublicKey(ctx context.Context, pk *AddPublicKey) (*domain.ObjectDetails, error) { func (c *Commands) AddPublicKey(ctx context.Context, add *AddPublicKey) (*domain.ObjectDetails, error) {
if pk.UserID == "" { if add.UserID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-14sGR7lTaj", "Errors.IDMissing") return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-14sGR7lTaj", "Errors.IDMissing")
} }
schemauser, err := existingSchemaUser(ctx, c, pk.ResourceOwner, pk.UserID) schemauser, err := existingSchemaUser(ctx, c, add.ResourceOwner, add.UserID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
add.ResourceOwner = schemauser.ResourceOwner
_, err = existingSchema(ctx, c, "", schemauser.SchemaID) _, err = existingSchema(ctx, c, "", schemauser.SchemaID)
if err != nil { if err != nil {
@ -48,28 +61,37 @@ func (c *Commands) AddPublicKey(ctx context.Context, pk *AddPublicKey) (*domain.
} }
// TODO check for possible authenticators // TODO check for possible authenticators
id, err := c.idGenerator.Next() writeModel, events, err := c.addPublicKey(ctx, add.ResourceOwner, add.UserID, add.PublicKey)
if err != nil {
return nil, err
}
writeModel, err := c.getSchemaPublicKeyWM(ctx, schemauser.ResourceOwner, schemauser.AggregateID, id)
if err != nil {
return nil, err
}
if len(pk.PublicKey) == 0 {
if err := domain.SetNewAuthNKeyPair(pk, c.machineKeySize); err != nil {
return nil, err
}
}
events, err := writeModel.NewCreate(ctx, pk.ExpirationDate, pk.PublicKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return c.pushAppendAndReduceDetails(ctx, writeModel, events...) return c.pushAppendAndReduceDetails(ctx, writeModel, events...)
} }
func (c *Commands) addPublicKey(ctx context.Context, resourceOwner, userID string, add *PublicKey) (*PublicKeyV3WriteModel, []eventstore.Command, error) {
if add == nil {
return nil, nil, nil
}
id, err := c.idGenerator.Next()
if err != nil {
return nil, nil, err
}
writeModel, err := c.getSchemaPublicKeyWM(ctx, resourceOwner, userID, id)
if err != nil {
return nil, nil, err
}
if len(add.PublicKey) == 0 {
if err := domain.SetNewAuthNKeyPair(add, c.machineKeySize); err != nil {
return nil, nil, err
}
}
events, err := writeModel.NewCreate(ctx, add.ExpirationDate, add.PublicKey)
if err != nil {
return nil, nil, err
}
return writeModel, events, nil
}
func (c *Commands) DeletePublicKey(ctx context.Context, resourceOwner, userID, id string) (*domain.ObjectDetails, error) { func (c *Commands) DeletePublicKey(ctx context.Context, resourceOwner, userID, id string) (*domain.ObjectDetails, error) {
if userID == "" { if userID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-hzqeAXW1qP", "Errors.IDMissing") return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-hzqeAXW1qP", "Errors.IDMissing")

View File

@ -101,8 +101,10 @@ func TestCommands_AddPublicKey(t *testing.T) {
args{ args{
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &AddPublicKey{ user: &AddPublicKey{
UserID: "user1", UserID: "user1",
PublicKey: []byte("something"), PublicKey: &PublicKey{
PublicKey: []byte("something"),
},
}, },
}, },
res{ res{
@ -155,8 +157,10 @@ func TestCommands_AddPublicKey(t *testing.T) {
args{ args{
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &AddPublicKey{ user: &AddPublicKey{
UserID: "user1", UserID: "user1",
PublicKey: []byte("something"), PublicKey: &PublicKey{
PublicKey: []byte("something"),
},
}, },
}, },
res{ res{
@ -188,9 +192,11 @@ func TestCommands_AddPublicKey(t *testing.T) {
args{ args{
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &AddPublicKey{ user: &AddPublicKey{
UserID: "user1", UserID: "user1",
PublicKey: []byte("something"), PublicKey: &PublicKey{
ExpirationDate: time.Date(2024, time.January, 1, 1, 1, 1, 1, time.UTC), PublicKey: []byte("something"),
ExpirationDate: time.Date(2024, time.January, 1, 1, 1, 1, 1, time.UTC),
},
}, },
}, },
res{ res{

View File

@ -16,6 +16,7 @@ import (
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/id" "github.com/zitadel/zitadel/internal/id"
"github.com/zitadel/zitadel/internal/id/mock" "github.com/zitadel/zitadel/internal/id/mock"
"github.com/zitadel/zitadel/internal/repository/user/authenticator"
"github.com/zitadel/zitadel/internal/repository/user/schema" "github.com/zitadel/zitadel/internal/repository/user/schema"
"github.com/zitadel/zitadel/internal/repository/user/schemauser" "github.com/zitadel/zitadel/internal/repository/user/schemauser"
"github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/internal/zerrors"
@ -23,10 +24,12 @@ import (
func TestCommands_CreateSchemaUser(t *testing.T) { func TestCommands_CreateSchemaUser(t *testing.T) {
type fields struct { type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore eventstore func(t *testing.T) *eventstore.Eventstore
idGenerator id.Generator idGenerator id.Generator
checkPermission domain.PermissionCheck checkPermission domain.PermissionCheck
newCode encrypedCodeFunc newCode encrypedCodeFunc
userPasswordHasher *crypto.Hasher
tokenAlg crypto.EncryptionAlgorithm
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
@ -698,15 +701,100 @@ func TestCommands_CreateSchemaUser(t *testing.T) {
}, },
}, },
}, },
{
"user created, full authenticators",
fields{
eventstore: expectEventstore(
expectFilter(),
filterSchemaExisting(),
expectFilter(),
expectFilter(),
filterPasswordComplexityPolicyExisting(),
expectFilter(),
expectFilter(),
expectPush(
schemauser.NewCreatedEvent(
context.Background(),
&schemauser.NewAggregate("id1", "org1").Aggregate,
"type",
1,
json.RawMessage(`{
"name": "user"
}`),
),
authenticator.NewUsernameCreatedEvent(
context.Background(),
&authenticator.NewAggregate("username1", "org1").Aggregate,
"id1",
true,
"username1",
),
authenticator.NewPasswordCreatedEvent(
context.Background(),
&authenticator.NewAggregate("id1", "org1").Aggregate,
"id1",
"$plain$x$password",
false,
),
authenticator.NewPublicKeyCreatedEvent(
context.Background(),
&authenticator.NewAggregate("pk1", "org1").Aggregate,
"id1",
time.Date(2024, time.January, 1, 1, 1, 1, 1, time.UTC),
[]byte("something"),
),
authenticator.NewPATCreatedEvent(
context.Background(),
&authenticator.NewAggregate("pat1", "org1").Aggregate,
"id1",
time.Date(2024, time.January, 1, 1, 1, 1, 1, time.UTC),
[]string{"first", "second", "third"},
),
),
),
idGenerator: mock.NewIDGeneratorExpectIDs(t, "id1", "username1", "pk1", "pat1"),
checkPermission: newMockPermissionCheckAllowed(),
userPasswordHasher: mockPasswordHasher("x"),
tokenAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args{
ctx: authz.NewMockContext("instanceID", "", ""),
user: &CreateSchemaUser{
ResourceOwner: "org1",
SchemaID: "type",
Data: json.RawMessage(`{
"name": "user"
}`),
Usernames: []*Username{
{Username: "username1", IsOrgSpecific: true},
},
Password: &SchemaUserPassword{Password: "password"},
PublicKeys: []*PublicKey{
{PublicKey: []byte("something"), ExpirationDate: time.Date(2024, time.January, 1, 1, 1, 1, 1, time.UTC)},
},
PATs: []*PAT{
{Scopes: []string{"first", "second", "third"}, ExpirationDate: time.Date(2024, time.January, 1, 1, 1, 1, 1, time.UTC)},
},
},
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "org1",
ID: "id1",
},
},
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
c := &Commands{ c := &Commands{
eventstore: tt.fields.eventstore(t), eventstore: tt.fields.eventstore(t),
idGenerator: tt.fields.idGenerator, idGenerator: tt.fields.idGenerator,
checkPermission: tt.fields.checkPermission, checkPermission: tt.fields.checkPermission,
newEncryptedCode: tt.fields.newCode, newEncryptedCode: tt.fields.newCode,
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
userPasswordHasher: tt.fields.userPasswordHasher,
keyAlgorithm: tt.fields.tokenAlg,
} }
details, err := c.CreateSchemaUser(tt.args.ctx, tt.args.user) details, err := c.CreateSchemaUser(tt.args.ctx, tt.args.user)
if tt.res.err == nil { if tt.res.err == nil {

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/internal/zerrors"
) )
@ -11,18 +12,23 @@ type AddUsername struct {
ResourceOwner string ResourceOwner string
UserID string UserID string
Username *Username
}
type Username struct {
Username string Username string
IsOrgSpecific bool IsOrgSpecific bool
} }
func (c *Commands) AddUsername(ctx context.Context, username *AddUsername) (*domain.ObjectDetails, error) { func (c *Commands) AddUsername(ctx context.Context, add *AddUsername) (*domain.ObjectDetails, error) {
if username.UserID == "" { if add.UserID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-aS3Vz5t6BS", "Errors.IDMissing") return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-aS3Vz5t6BS", "Errors.IDMissing")
} }
schemauser, err := existingSchemaUser(ctx, c, username.ResourceOwner, username.UserID) schemauser, err := existingSchemaUser(ctx, c, add.ResourceOwner, add.UserID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
add.ResourceOwner = schemauser.ResourceOwner
_, err = existingSchema(ctx, c, "", schemauser.SchemaID) _, err = existingSchema(ctx, c, "", schemauser.SchemaID)
if err != nil { if err != nil {
@ -30,21 +36,32 @@ func (c *Commands) AddUsername(ctx context.Context, username *AddUsername) (*dom
} }
// TODO check for possible authenticators // TODO check for possible authenticators
id, err := c.idGenerator.Next() writeModel, events, err := c.addUsername(ctx, add.ResourceOwner, add.UserID, add.Username)
if err != nil {
return nil, err
}
writeModel, err := c.getSchemaUsernameWM(ctx, schemauser.ResourceOwner, schemauser.AggregateID, id)
if err != nil {
return nil, err
}
events, err := writeModel.NewCreate(ctx, username.IsOrgSpecific, username.Username)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return c.pushAppendAndReduceDetails(ctx, writeModel, events...) return c.pushAppendAndReduceDetails(ctx, writeModel, events...)
} }
func (c *Commands) addUsername(ctx context.Context, resourceOwner, userID string, add *Username) (*UsernameV3WriteModel, []eventstore.Command, error) {
if resourceOwner == "" || userID == "" || add == nil {
return nil, nil, nil
}
id, err := c.idGenerator.Next()
if err != nil {
return nil, nil, err
}
writeModel, err := c.getSchemaUsernameWM(ctx, resourceOwner, userID, id)
if err != nil {
return nil, nil, err
}
events, err := writeModel.NewCreate(ctx, add.IsOrgSpecific, add.Username)
if err != nil {
return nil, nil, err
}
return writeModel, events, nil
}
func (c *Commands) DeleteUsername(ctx context.Context, resourceOwner, userID, id string) (*domain.ObjectDetails, error) { func (c *Commands) DeleteUsername(ctx context.Context, resourceOwner, userID, id string) (*domain.ObjectDetails, error) {
if userID == "" { if userID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-J6ybG5WZiy", "Errors.IDMissing") return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-J6ybG5WZiy", "Errors.IDMissing")

View File

@ -142,6 +142,9 @@ func TestCommands_AddUsername(t *testing.T) {
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &AddUsername{ user: &AddUsername{
UserID: "user1", UserID: "user1",
Username: &Username{
Username: "user1",
},
}, },
}, },
res{ res{
@ -163,6 +166,9 @@ func TestCommands_AddUsername(t *testing.T) {
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &AddUsername{ user: &AddUsername{
UserID: "user1", UserID: "user1",
Username: &Username{
Username: "user1",
},
}, },
}, },
res{ res{
@ -194,9 +200,11 @@ func TestCommands_AddUsername(t *testing.T) {
args{ args{
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &AddUsername{ user: &AddUsername{
UserID: "user1", UserID: "user1",
Username: "username", Username: &Username{
IsOrgSpecific: false, Username: "username",
IsOrgSpecific: false,
},
}, },
}, },
res{ res{
@ -228,9 +236,11 @@ func TestCommands_AddUsername(t *testing.T) {
args{ args{
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &AddUsername{ user: &AddUsername{
UserID: "user1", UserID: "user1",
Username: "username", Username: &Username{
IsOrgSpecific: true, Username: "username",
IsOrgSpecific: true,
},
}, },
}, },
res{ res{