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 {
pw, verification := setPasswordToSetSchemaUserPassword(req.GetNewPassword())
return &command.SetSchemaUserPassword{
ResourceOwner: organizationToUpdateResourceOwner(req.Organization),
UserID: req.GetId(),
Password: req.GetNewPassword().GetPassword(),
EncodedPasswordHash: req.GetNewPassword().GetHash(),
ChangeRequired: req.GetNewPassword().GetChangeRequired(),
VerificationCode: req.GetNewPassword().GetVerificationCode(),
CurrentPassword: req.GetNewPassword().GetCurrentPassword(),
ResourceOwner: organizationToUpdateResourceOwner(req.Organization),
UserID: req.GetId(),
Password: pw,
Verification: verification,
}
}
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{
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner),
PersonalAccessTokenId: details.ID,
PersonalAccessToken: pat.Token,
PersonalAccessToken: pat.PAT.Token,
}, nil
}
func addPersonalAccessTokenRequestToAddPAT(req *user.AddPersonalAccessTokenRequest) *command.AddPAT {
expDate := time.Time{}
if req.GetPersonalAccessToken().GetExpirationDate() != nil {
expDate = req.GetPersonalAccessToken().GetExpirationDate().AsTime()
if req == nil {
return nil
}
return &command.AddPAT{
ResourceOwner: organizationToUpdateResourceOwner(req.Organization),
UserID: req.GetId(),
ResourceOwner: organizationToUpdateResourceOwner(req.Organization),
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,
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{
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner),
PublicKeyId: details.ID,
PrivateKey: pk.PrivateKey,
PrivateKey: pk.GetPrivateKey(),
}, nil
}
func addPublicKeyRequestToAddPublicKey(req *user.AddPublicKeyRequest) *command.AddPublicKey {
expDate := time.Time{}
if req.GetPublicKey().GetExpirationDate() != nil {
expDate = req.GetPublicKey().GetExpirationDate().AsTime()
if req == nil {
return nil
}
return &command.AddPublicKey{
ResourceOwner: organizationToUpdateResourceOwner(req.Organization),
UserID: req.GetId(),
PublicKey: req.GetPublicKey().GetPublicKey().GetPublicKey(),
ResourceOwner: organizationToUpdateResourceOwner(req.Organization),
UserID: req.GetId(),
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,
}
}

View File

@ -30,20 +30,54 @@ func (s *Server) CreateUser(ctx context.Context, req *user.CreateUserRequest) (_
}, 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) {
data, err := req.GetUser().GetData().MarshalJSON()
if err != nil {
return nil, err
}
return &command.CreateSchemaUser{
user := &command.CreateSchemaUser{
ResourceOwner: organizationToCreateResourceOwner(ctx, req.Organization),
SchemaID: req.GetUser().GetSchemaId(),
ID: req.GetUser().GetUserId(),
Data: data,
Email: setEmailToEmail(req.GetUser().GetContact().GetEmail()),
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 {

View File

@ -24,11 +24,23 @@ func (s *Server) AddUsername(ctx context.Context, req *user.AddUsernameRequest)
}
func addUsernameRequestToAddUsername(req *user.AddUsernameRequest) *command.AddUsername {
if req == nil {
return nil
}
return &command.AddUsername{
ResourceOwner: organizationToUpdateResourceOwner(req.Organization),
UserID: req.GetId(),
Username: req.GetUsername().GetUsername(),
IsOrgSpecific: req.GetUsername().GetIsOrganizationSpecific(),
Username: setUsernameToAddUsername(req.GetUsername()),
}
}
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
Phone *Phone
ReturnCodePhone *string
Usernames []*Username
Password *SchemaUserPassword
PublicKeys []*PublicKey
PATs []*PAT
}
func (s *CreateSchemaUser) Valid() (err error) {
@ -84,6 +89,34 @@ func (c *Commands) CreateSchemaUser(ctx context.Context, user *CreateSchemaUser)
if 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...)
}

View File

@ -6,6 +6,7 @@ import (
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
"github.com/zitadel/zitadel/internal/zerrors"
)
@ -14,19 +15,22 @@ type SetSchemaUserPassword struct {
ResourceOwner string
UserID string
Verification *SchemaUserPasswordVerification
Password *SchemaUserPassword
}
type SchemaUserPasswordVerification struct {
CurrentPassword string
Code string
}
type SchemaUserPassword struct {
Password string
EncodedPasswordHash string
ChangeRequired bool
CurrentPassword string
VerificationCode string
}
func (p *SetSchemaUserPassword) Validate(hasher *crypto.Hasher) (err error) {
if p.UserID == "" {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-aS3Vz5t6BS", "Errors.IDMissing")
}
func (p *SchemaUserPassword) Validate(hasher *crypto.Hasher) (err error) {
if p.EncodedPasswordHash != "" {
if !hasher.EncodingSupported(p.EncodedPasswordHash) {
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 == "" {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-3klek4sbns", "Errors.User.Password.Empty")
}
return nil
}
func (c *Commands) SetSchemaUserPassword(ctx context.Context, user *SetSchemaUserPassword) (*domain.ObjectDetails, error) {
if err := user.Validate(c.userPasswordHasher); err != nil {
func (c *Commands) SetSchemaUserPassword(ctx context.Context, set *SetSchemaUserPassword) (*domain.ObjectDetails, error) {
if set.UserID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-aS3Vz5t6BS", "Errors.IDMissing")
}
if err := set.Password.Validate(c.userPasswordHasher); err != nil {
return nil, err
}
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)
schemauser, err := existingSchemaUser(ctx, c, set.ResourceOwner, set.UserID)
if err != nil {
return nil, err
}
set.ResourceOwner = schemauser.ResourceOwner
// If password is provided, let's check if is compliant with the policy.
// If only a encodedPassword is passed, we can skip this.
if user.Password != "" {
if err = c.checkPasswordComplexity(ctx, user.Password, writeModel.ResourceOwner); err != nil {
return nil, err
}
_, err = existingSchema(ctx, c, "", schemauser.SchemaID)
if err != nil {
return nil, err
}
// TODO check for possible authenticators
encodedPassword := schemaUser.EncodedPasswordHash
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,
)
writeModel, events, err := c.setSchemaUserPassword(ctx, set.ResourceOwner, set.UserID, set.Verification, set.Password)
if err != nil {
return nil, err
}
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 {
ResourceOwner string
UserID string
@ -132,13 +155,11 @@ func (c *Commands) DeleteSchemaUserPassword(ctx context.Context, resourceOwner,
}
type schemaUserPassword struct {
Create bool
ResourceOwner string
UserID string
VerificationCode string
CurrentPassword string
Password string
EncodedPasswordHash string
Set bool
ResourceOwner string
UserID string
Verification *SchemaUserPasswordVerification
NewPassword *SchemaUserPassword
}
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 {
return nil, err
}
if err := writeModel.Exists(); user.Create && err != nil {
schemauser, err := existingSchemaUser(ctx, c, user.ResourceOwner, user.UserID)
if err != nil {
return nil, err
}
writeModel.ResourceOwner = schemauser.ResourceOwner
if err := writeModel.Exists(); !user.Set && err != nil {
return nil, err
}
// if no verification is set, the user must have the permission to change the password
verification := c.setSchemaUserPasswordWithPermission(writeModel.UserID, writeModel.ResourceOwner)
// otherwise check the password code...
if user.VerificationCode != "" {
verification = c.setSchemaUserPasswordWithVerifyCode(writeModel.CodeCreationDate, writeModel.CodeExpiry, writeModel.Code, user.VerificationCode)
}
// ...or old password
if user.CurrentPassword != "" {
verification = c.checkSchemaUserCurrentPassword(user.Password, user.EncodedPasswordHash, user.CurrentPassword, writeModel.EncodedHash)
if user.Verification != nil {
// otherwise check the password code...
if user.Verification.Code != "" {
verification = c.setSchemaUserPasswordWithVerifyCode(writeModel.CodeCreationDate, writeModel.CodeExpiry, writeModel.Code, user.Verification.Code)
}
// ...or old password
if user.Verification.CurrentPassword != "" {
verification = c.checkSchemaUserCurrentPassword(user.NewPassword.Password, user.NewPassword.EncodedPasswordHash, user.Verification.CurrentPassword, writeModel.EncodedHash)
}
}
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)
if newEncodedPassword != "" {
user.EncodedPasswordHash = newEncodedPassword
user.NewPassword.EncodedPasswordHash = newEncodedPassword
}
}
return writeModel, nil

View File

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

View File

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

View File

@ -62,8 +62,10 @@ func TestCommands_AddPAT(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(),
},
args{
ctx: authz.NewMockContext("instanceID", "", ""),
user: &AddPAT{},
ctx: authz.NewMockContext("instanceID", "", ""),
user: &AddPAT{
PAT: &PAT{},
},
},
res{
err: func(err error) bool {
@ -83,6 +85,7 @@ func TestCommands_AddPAT(t *testing.T) {
ctx: authz.NewMockContext("instanceID", "", ""),
user: &AddPAT{
UserID: "notexisting",
PAT: &PAT{},
},
},
res{
@ -106,6 +109,7 @@ func TestCommands_AddPAT(t *testing.T) {
ctx: authz.NewMockContext("instanceID", "", ""),
user: &AddPAT{
UserID: "user1",
PAT: &PAT{},
},
},
res{
@ -127,6 +131,7 @@ func TestCommands_AddPAT(t *testing.T) {
ctx: authz.NewMockContext("instanceID", "", ""),
user: &AddPAT{
UserID: "user1",
PAT: &PAT{},
},
},
res{
@ -160,7 +165,9 @@ func TestCommands_AddPAT(t *testing.T) {
ctx: authz.NewMockContext("instanceID", "", ""),
user: &AddPAT{
UserID: "user1",
Scope: []string{"first", "second", "third"},
PAT: &PAT{
Scopes: []string{"first", "second", "third"},
},
},
},
res{
@ -194,9 +201,11 @@ func TestCommands_AddPAT(t *testing.T) {
args{
ctx: authz.NewMockContext("instanceID", "", ""),
user: &AddPAT{
UserID: "user1",
ExpirationDate: time.Date(2024, time.January, 1, 1, 1, 1, 1, time.UTC),
Scope: []string{"first", "second", "third"},
UserID: "user1",
PAT: &PAT{
ExpirationDate: time.Date(2024, time.January, 1, 1, 1, 1, 1, time.UTC),
Scopes: []string{"first", "second", "third"},
},
},
},
res{
@ -224,7 +233,7 @@ func TestCommands_AddPAT(t *testing.T) {
}
if tt.res.err == nil {
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"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/zerrors"
)
@ -12,35 +13,47 @@ type AddPublicKey struct {
ResourceOwner 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
PublicKey []byte
PrivateKey []byte
}
func (wm *AddPublicKey) GetExpirationDate() time.Time {
func (wm *PublicKey) GetExpirationDate() time.Time {
return wm.ExpirationDate
}
func (wm *AddPublicKey) SetExpirationDate(date time.Time) {
func (wm *PublicKey) SetExpirationDate(date time.Time) {
wm.ExpirationDate = date
}
func (wm *AddPublicKey) SetPublicKey(data []byte) {
func (wm *PublicKey) SetPublicKey(data []byte) {
wm.PublicKey = data
}
func (wm *AddPublicKey) SetPrivateKey(data []byte) {
func (wm *PublicKey) SetPrivateKey(data []byte) {
wm.PrivateKey = data
}
func (c *Commands) AddPublicKey(ctx context.Context, pk *AddPublicKey) (*domain.ObjectDetails, error) {
if pk.UserID == "" {
func (c *Commands) AddPublicKey(ctx context.Context, add *AddPublicKey) (*domain.ObjectDetails, error) {
if add.UserID == "" {
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 {
return nil, err
}
add.ResourceOwner = schemauser.ResourceOwner
_, err = existingSchema(ctx, c, "", schemauser.SchemaID)
if err != nil {
@ -48,28 +61,37 @@ func (c *Commands) AddPublicKey(ctx context.Context, pk *AddPublicKey) (*domain.
}
// TODO check for possible authenticators
id, err := c.idGenerator.Next()
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)
writeModel, events, err := c.addPublicKey(ctx, add.ResourceOwner, add.UserID, add.PublicKey)
if err != nil {
return nil, err
}
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) {
if userID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-hzqeAXW1qP", "Errors.IDMissing")

View File

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

View File

@ -16,6 +16,7 @@ import (
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/id"
"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/schemauser"
"github.com/zitadel/zitadel/internal/zerrors"
@ -23,10 +24,12 @@ import (
func TestCommands_CreateSchemaUser(t *testing.T) {
type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore
idGenerator id.Generator
checkPermission domain.PermissionCheck
newCode encrypedCodeFunc
eventstore func(t *testing.T) *eventstore.Eventstore
idGenerator id.Generator
checkPermission domain.PermissionCheck
newCode encrypedCodeFunc
userPasswordHasher *crypto.Hasher
tokenAlg crypto.EncryptionAlgorithm
}
type args struct {
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 {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore(t),
idGenerator: tt.fields.idGenerator,
checkPermission: tt.fields.checkPermission,
newEncryptedCode: tt.fields.newCode,
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
eventstore: tt.fields.eventstore(t),
idGenerator: tt.fields.idGenerator,
checkPermission: tt.fields.checkPermission,
newEncryptedCode: tt.fields.newCode,
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
userPasswordHasher: tt.fields.userPasswordHasher,
keyAlgorithm: tt.fields.tokenAlg,
}
details, err := c.CreateSchemaUser(tt.args.ctx, tt.args.user)
if tt.res.err == nil {

View File

@ -4,6 +4,7 @@ import (
"context"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/zerrors"
)
@ -11,18 +12,23 @@ type AddUsername struct {
ResourceOwner string
UserID string
Username *Username
}
type Username struct {
Username string
IsOrgSpecific bool
}
func (c *Commands) AddUsername(ctx context.Context, username *AddUsername) (*domain.ObjectDetails, error) {
if username.UserID == "" {
func (c *Commands) AddUsername(ctx context.Context, add *AddUsername) (*domain.ObjectDetails, error) {
if add.UserID == "" {
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 {
return nil, err
}
add.ResourceOwner = schemauser.ResourceOwner
_, err = existingSchema(ctx, c, "", schemauser.SchemaID)
if err != nil {
@ -30,21 +36,32 @@ func (c *Commands) AddUsername(ctx context.Context, username *AddUsername) (*dom
}
// TODO check for possible authenticators
id, err := c.idGenerator.Next()
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)
writeModel, events, err := c.addUsername(ctx, add.ResourceOwner, add.UserID, add.Username)
if err != nil {
return nil, err
}
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) {
if userID == "" {
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", "", ""),
user: &AddUsername{
UserID: "user1",
Username: &Username{
Username: "user1",
},
},
},
res{
@ -163,6 +166,9 @@ func TestCommands_AddUsername(t *testing.T) {
ctx: authz.NewMockContext("instanceID", "", ""),
user: &AddUsername{
UserID: "user1",
Username: &Username{
Username: "user1",
},
},
},
res{
@ -194,9 +200,11 @@ func TestCommands_AddUsername(t *testing.T) {
args{
ctx: authz.NewMockContext("instanceID", "", ""),
user: &AddUsername{
UserID: "user1",
Username: "username",
IsOrgSpecific: false,
UserID: "user1",
Username: &Username{
Username: "username",
IsOrgSpecific: false,
},
},
},
res{
@ -228,9 +236,11 @@ func TestCommands_AddUsername(t *testing.T) {
args{
ctx: authz.NewMockContext("instanceID", "", ""),
user: &AddUsername{
UserID: "user1",
Username: "username",
IsOrgSpecific: true,
UserID: "user1",
Username: &Username{
Username: "username",
IsOrgSpecific: true,
},
},
},
res{