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 != "" {
if err = c.checkPasswordComplexity(ctx, user.Password, writeModel.ResourceOwner); err != nil {
return nil, err 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)
if err != nil {
return nil, err 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)
if user.Verification != nil {
// otherwise check the password code... // otherwise check the password code...
if user.VerificationCode != "" { if user.Verification.Code != "" {
verification = c.setSchemaUserPasswordWithVerifyCode(writeModel.CodeCreationDate, writeModel.CodeExpiry, writeModel.Code, user.VerificationCode) verification = c.setSchemaUserPasswordWithVerifyCode(writeModel.CodeCreationDate, writeModel.CodeExpiry, writeModel.Code, user.Verification.Code)
} }
// ...or old password // ...or old password
if user.CurrentPassword != "" { if user.Verification.CurrentPassword != "" {
verification = c.checkSchemaUserCurrentPassword(user.Password, user.EncodedPasswordHash, user.CurrentPassword, writeModel.EncodedHash) 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

@ -95,6 +95,7 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
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(
@ -171,10 +173,12 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &SetSchemaUserPassword{ user: &SetSchemaUserPassword{
UserID: "user1", UserID: "user1",
Password: &SchemaUserPassword{
Password: "password", Password: "password",
ChangeRequired: false, ChangeRequired: false,
}, },
}, },
},
res{ res{
details: &domain.ObjectDetails{ details: &domain.ObjectDetails{
ResourceOwner: "org1", ResourceOwner: "org1",
@ -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(
@ -204,10 +210,12 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &SetSchemaUserPassword{ user: &SetSchemaUserPassword{
UserID: "user1", UserID: "user1",
Password: &SchemaUserPassword{
Password: "password", Password: "password",
ChangeRequired: false, ChangeRequired: false,
}, },
}, },
},
res{ res{
details: &domain.ObjectDetails{ details: &domain.ObjectDetails{
ResourceOwner: "org1", ResourceOwner: "org1",
@ -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(
@ -237,10 +247,12 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &SetSchemaUserPassword{ user: &SetSchemaUserPassword{
UserID: "user1", UserID: "user1",
Password: &SchemaUserPassword{
Password: "password", Password: "password",
ChangeRequired: false, ChangeRequired: false,
}, },
}, },
},
res{ res{
details: &domain.ObjectDetails{ details: &domain.ObjectDetails{
ResourceOwner: "org1", ResourceOwner: "org1",
@ -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(
@ -270,10 +284,12 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &SetSchemaUserPassword{ user: &SetSchemaUserPassword{
UserID: "user1", UserID: "user1",
Password: &SchemaUserPassword{
Password: "password", Password: "password",
ChangeRequired: true, ChangeRequired: true,
}, },
}, },
},
res{ res{
details: &domain.ObjectDetails{ details: &domain.ObjectDetails{
ResourceOwner: "org1", ResourceOwner: "org1",
@ -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(
@ -303,11 +321,13 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &SetSchemaUserPassword{ user: &SetSchemaUserPassword{
UserID: "user1", UserID: "user1",
Password: &SchemaUserPassword{
Password: "passwordnotused", Password: "passwordnotused",
EncodedPasswordHash: "$plain$x$password2", EncodedPasswordHash: "$plain$x$password2",
ChangeRequired: false, ChangeRequired: false,
}, },
}, },
},
res{ res{
details: &domain.ObjectDetails{ details: &domain.ObjectDetails{
ResourceOwner: "org1", ResourceOwner: "org1",
@ -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(
@ -336,10 +358,14 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &SetSchemaUserPassword{ user: &SetSchemaUserPassword{
UserID: "user1", UserID: "user1",
Password: &SchemaUserPassword{
Password: "password2", Password: "password2",
CurrentPassword: "password",
ChangeRequired: false, ChangeRequired: false,
}, },
Verification: &SchemaUserPasswordVerification{
CurrentPassword: "password",
},
},
}, },
res{ res{
details: &domain.ObjectDetails{ details: &domain.ObjectDetails{
@ -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(
@ -369,10 +397,14 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &SetSchemaUserPassword{ user: &SetSchemaUserPassword{
UserID: "user1", UserID: "user1",
Password: &SchemaUserPassword{
Password: "password2", Password: "password2",
CurrentPassword: "password",
ChangeRequired: false, ChangeRequired: false,
}, },
Verification: &SchemaUserPasswordVerification{
CurrentPassword: "password",
},
},
}, },
res{ res{
details: &domain.ObjectDetails{ details: &domain.ObjectDetails{
@ -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"),
@ -391,10 +425,14 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &SetSchemaUserPassword{ user: &SetSchemaUserPassword{
UserID: "user1", UserID: "user1",
Password: &SchemaUserPassword{
Password: "password2", Password: "password2",
CurrentPassword: "notreally",
ChangeRequired: false, ChangeRequired: false,
}, },
Verification: &SchemaUserPasswordVerification{
CurrentPassword: "notreally",
},
},
}, },
res{ res{
err: func(err error) bool { err: func(err error) bool {
@ -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(
@ -450,10 +490,14 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &SetSchemaUserPassword{ user: &SetSchemaUserPassword{
UserID: "user1", UserID: "user1",
Password: &SchemaUserPassword{
Password: "password2", Password: "password2",
VerificationCode: "code",
ChangeRequired: false, ChangeRequired: false,
}, },
Verification: &SchemaUserPasswordVerification{
Code: "code",
},
},
}, },
res{ res{
details: &domain.ObjectDetails{ details: &domain.ObjectDetails{
@ -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(
@ -499,10 +545,14 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &SetSchemaUserPassword{ user: &SetSchemaUserPassword{
UserID: "user1", UserID: "user1",
Password: &SchemaUserPassword{
Password: "password2", Password: "password2",
VerificationCode: "notreally",
ChangeRequired: false, ChangeRequired: false,
}, },
Verification: &SchemaUserPasswordVerification{
Code: "notreally",
},
},
}, },
res{ res{
err: func(err error) bool { err: func(err error) bool {
@ -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"),
@ -523,10 +575,14 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &SetSchemaUserPassword{ user: &SetSchemaUserPassword{
UserID: "user1", UserID: "user1",
Password: &SchemaUserPassword{
Password: "password2", Password: "password2",
VerificationCode: "notreally",
ChangeRequired: false, ChangeRequired: false,
}, },
Verification: &SchemaUserPasswordVerification{
Code: "notreally",
},
},
}, },
res{ res{
err: func(err error) bool { err: func(err error) bool {
@ -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

@ -63,7 +63,9 @@ func TestCommands_AddPAT(t *testing.T) {
}, },
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{
@ -195,8 +202,10 @@ func TestCommands_AddPAT(t *testing.T) {
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &AddPAT{ user: &AddPAT{
UserID: "user1", UserID: "user1",
PAT: &PAT{
ExpirationDate: time.Date(2024, time.January, 1, 1, 1, 1, 1, time.UTC), ExpirationDate: time.Date(2024, time.January, 1, 1, 1, 1, 1, time.UTC),
Scope: []string{"first", "second", "third"}, 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

@ -102,9 +102,11 @@ func TestCommands_AddPublicKey(t *testing.T) {
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &AddPublicKey{ user: &AddPublicKey{
UserID: "user1", UserID: "user1",
PublicKey: &PublicKey{
PublicKey: []byte("something"), PublicKey: []byte("something"),
}, },
}, },
},
res{ res{
err: func(err error) bool { err: func(err error) bool {
return errors.Is(err, zerrors.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied")) return errors.Is(err, zerrors.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied"))
@ -156,9 +158,11 @@ func TestCommands_AddPublicKey(t *testing.T) {
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &AddPublicKey{ user: &AddPublicKey{
UserID: "user1", UserID: "user1",
PublicKey: &PublicKey{
PublicKey: []byte("something"), PublicKey: []byte("something"),
}, },
}, },
},
res{ res{
details: &domain.ObjectDetails{ details: &domain.ObjectDetails{
ResourceOwner: "org1", ResourceOwner: "org1",
@ -189,10 +193,12 @@ func TestCommands_AddPublicKey(t *testing.T) {
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
user: &AddPublicKey{ user: &AddPublicKey{
UserID: "user1", UserID: "user1",
PublicKey: &PublicKey{
PublicKey: []byte("something"), PublicKey: []byte("something"),
ExpirationDate: time.Date(2024, time.January, 1, 1, 1, 1, 1, time.UTC), ExpirationDate: time.Date(2024, time.January, 1, 1, 1, 1, 1, time.UTC),
}, },
}, },
},
res{ res{
details: &domain.ObjectDetails{ details: &domain.ObjectDetails{
ResourceOwner: "org1", ResourceOwner: "org1",

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"
@ -27,6 +28,8 @@ func TestCommands_CreateSchemaUser(t *testing.T) {
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,6 +701,89 @@ 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) {
@ -707,6 +793,8 @@ func TestCommands_CreateSchemaUser(t *testing.T) {
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{
@ -195,10 +201,12 @@ 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: "username", Username: "username",
IsOrgSpecific: false, IsOrgSpecific: false,
}, },
}, },
},
res{ res{
details: &domain.ObjectDetails{ details: &domain.ObjectDetails{
ResourceOwner: "org1", ResourceOwner: "org1",
@ -229,10 +237,12 @@ 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: "username", Username: "username",
IsOrgSpecific: true, IsOrgSpecific: true,
}, },
}, },
},
res{ res{
details: &domain.ObjectDetails{ details: &domain.ObjectDetails{
ResourceOwner: "org1", ResourceOwner: "org1",