feat(session): IDP Intent Check API with relation tables (#11820)

# Which Problems Are Solved

As part of #11035 , this PR implements the IDP intent check logic for
session validation

# How the Problems Are Solved

- Inject encryption algorithm for IDP intent tokens at DB initialization
  - Add the IDP intent repository as an invoke option
  - Rework crypto mocks using gomock
  - Implement IDP intent check logic
- minor - DB: expand IDP Intent deletion testing table with a test case
where IDP intent is not found

# Noteworthy

I did not implement `domain.Transactional` given the doubts that
@adlerhurst expressed in https://github.com/zitadel/zitadel/pull/11804 .
So, there is no transaction AT ALL in here because the `Execute()`
method contains one single DB call (Delete) and `Validate()` just does
Get calls.

# Additional Context

This is a cherry-picked PR + minor changes, coming from
https://github.com/zitadel/zitadel/pull/11164
- Relates to #11035
- Depends on https://github.com/zitadel/zitadel/pull/11804
This commit is contained in:
Marco A.
2026-03-17 10:50:47 +00:00
committed by GitHub
parent 3fdac54878
commit aa4432dc99
16 changed files with 1422 additions and 157 deletions
@@ -0,0 +1,17 @@
package convert
import (
"github.com/zitadel/zitadel/backend/v3/domain"
session_grpc "github.com/zitadel/zitadel/pkg/grpc/session/v2"
)
func CheckIDPIntentGRPCToDomain(checkIDPIntent *session_grpc.CheckIDPIntent) *domain.CheckIDPIntentType {
if checkIDPIntent == nil {
return nil
}
return &domain.CheckIDPIntentType{
ID: checkIDPIntent.GetIdpIntentId(),
Token: checkIDPIntent.GetIdpIntentToken(),
}
}
@@ -0,0 +1,55 @@
package convert
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/zitadel/zitadel/backend/v3/domain"
session_grpc "github.com/zitadel/zitadel/pkg/grpc/session/v2"
)
func TestCheckIDPIntentGRPCToDomain(t *testing.T) {
t.Parallel()
tt := []struct {
testName string
input *session_grpc.CheckIDPIntent
expected *domain.CheckIDPIntentType
}{
{
testName: "nil input",
input: nil,
expected: nil,
},
{
testName: "valid input",
input: &session_grpc.CheckIDPIntent{
IdpIntentId: "intent-123",
IdpIntentToken: "token-abc",
},
expected: &domain.CheckIDPIntentType{
ID: "intent-123",
Token: "token-abc",
},
},
{
testName: "empty strings",
input: &session_grpc.CheckIDPIntent{
IdpIntentId: "",
IdpIntentToken: "",
},
expected: &domain.CheckIDPIntentType{
ID: "",
Token: "",
},
},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
t.Parallel()
result := CheckIDPIntentGRPCToDomain(tc.input)
assert.Equal(t, tc.expected, result)
})
}
}
+11 -4
View File
@@ -8,10 +8,13 @@ import (
)
var (
pool database.Pool
legacyEventstore eventstore.LegacyEventstore
sysConfig systemdefaults.SystemDefaults
passwordHasher *crypto.Hasher
pool database.Pool
// tracer tracing.Tracer = tracing.GlobalTracer()
// runtimeLogger *slog.Logger = logging.New(logging.StreamRuntime, nil)
legacyEventstore eventstore.LegacyEventstore
sysConfig systemdefaults.SystemDefaults
passwordHasher *crypto.Hasher
idpEncryptionAlgo crypto.EncryptionAlgorithm
)
func SetPool(p database.Pool) {
@@ -29,3 +32,7 @@ func SetSystemConfig(cfg systemdefaults.SystemDefaults) {
func SetPasswordHasher(hasher *crypto.Hasher) {
passwordHasher = hasher
}
func SetIDPEncryptionAlgorithm(idpEncryptionAlg crypto.EncryptionAlgorithm) {
idpEncryptionAlgo = idpEncryptionAlg
}
+23
View File
@@ -1,7 +1,11 @@
package domain
import (
"errors"
"fmt"
"github.com/zitadel/zitadel/backend/v3/storage/database"
"github.com/zitadel/zitadel/internal/zerrors"
)
type wrongIDPTypeError struct {
@@ -89,6 +93,25 @@ func (e *PasswordVerificationError) Error() string {
return fmt.Sprintf("Message=failed password attempts (%d)", e.failedAttempts)
}
// handleGetError wraps DB errors coming from Get calls into [zerrors] errors.
//
// - errorID should be in the format <package short name>-<random id>
// - objectType should be the string representation of a DB object (e.g. 'user', 'session', 'idp'...)
//
// The function wraps [database.NoRowFoundError] to [zerrors.NotFound] error
// and any other error to [zerrors.InternalError]
func handleGetError(inputErr error, errorID, objectType string) error {
if inputErr == nil {
return nil
}
if errors.Is(inputErr, &database.NoRowFoundError{}) {
return zerrors.CreateZitadelError(zerrors.KindNotFound, inputErr, errorID, fmt.Sprintf("%s not found", objectType), 1)
}
return zerrors.CreateZitadelError(zerrors.KindInternal, inputErr, errorID, fmt.Sprintf("failed fetching %s", objectType), 1)
}
func (err *PasswordVerificationError) Is(target error) bool {
_, ok := target.(*PasswordVerificationError)
return ok
+7
View File
@@ -63,6 +63,12 @@ func WithPermissionChecker(checker PermissionChecker) InvokeOpt {
}
}
func WithIDPIntentRepo(repo IDPIntentRepository) InvokeOpt {
return func(opts *InvokeOpts) {
opts.idpIntentRepo = repo
}
}
// WithQueryExecutor sets the database client to be used by the command.
// If not set, the default pool will be used.
// This is mainly used for testing.
@@ -99,6 +105,7 @@ type InvokeOpts struct {
instanceDomainRepo InstanceDomainRepository
sessionRepo SessionRepository
userRepo UserRepository
idpIntentRepo IDPIntentRepository
// Settings repos
lockoutSettingRepo LockoutSettingsRepository
+43
View File
@@ -0,0 +1,43 @@
package domainmock
import (
context "context"
gomock "go.uber.org/mock/gomock"
domain "github.com/zitadel/zitadel/backend/v3/domain"
database "github.com/zitadel/zitadel/backend/v3/storage/database"
"github.com/zitadel/zitadel/backend/v3/storage/database/repository"
)
func NewIDPIntentRepo(ctrl *gomock.Controller) *IDPIntentRepo {
return &IDPIntentRepo{
mock: NewMockIDPIntentRepository(ctrl),
IDPIntentRepository: repository.IDPIntentRepository(),
}
}
type IDPIntentRepo struct {
domain.IDPIntentRepository
mock *MockIDPIntentRepository
}
func (u *IDPIntentRepo) EXPECT() *MockIDPIntentRepositoryMockRecorder {
return u.mock.EXPECT()
}
func (i *IDPIntentRepo) Get(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) (*domain.IDPIntent, error) {
return i.mock.Get(ctx, client, opts...)
}
func (i *IDPIntentRepo) Create(ctx context.Context, client database.QueryExecutor, intent *domain.IDPIntent) error {
return i.mock.Create(ctx, client, intent)
}
func (i *IDPIntentRepo) Update(ctx context.Context, client database.QueryExecutor, condition database.Condition, changes ...database.Change) (int64, error) {
return i.mock.Update(ctx, client, condition, changes...)
}
func (i *IDPIntentRepo) Delete(ctx context.Context, client database.QueryExecutor, condition database.Condition) (int64, error) {
return i.mock.Delete(ctx, client, condition)
}
@@ -0,0 +1,161 @@
package domain
import (
"context"
"time"
"github.com/zitadel/zitadel/backend/v3/storage/database"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/idpintent"
"github.com/zitadel/zitadel/internal/repository/session"
"github.com/zitadel/zitadel/internal/zerrors"
)
type CheckIDPIntentType struct {
ID string
Token string
}
type IDPIntentCheckCommand struct {
CheckIntent *CheckIDPIntentType
SessionID string
InstanceID string
EncAlgo crypto.EncryptionAlgorithm
FetchedUser User
IsCheckComplete bool
}
// NewIDPIntentCheckCommand returns an IDPIntentCheckCommand initialized with the input values.
//
// If encryptionAlgo is nil, the default [crypto.EncryptionAlgorithm] will be used
func NewIDPIntentCheckCommand(request *CheckIDPIntentType, sessionID, instanceID string, encryptionAlgo crypto.EncryptionAlgorithm) *IDPIntentCheckCommand {
idpCheckCommand := &IDPIntentCheckCommand{
CheckIntent: request,
SessionID: sessionID,
InstanceID: instanceID,
EncAlgo: idpEncryptionAlgo,
}
if encryptionAlgo != nil {
idpCheckCommand.EncAlgo = encryptionAlgo
}
return idpCheckCommand
}
// Events implements [Commander].
func (i *IDPIntentCheckCommand) Events(ctx context.Context, opts *InvokeOpts) ([]eventstore.Command, error) {
if i.CheckIntent == nil || !i.IsCheckComplete {
return nil, nil
}
return []eventstore.Command{
session.NewIntentCheckedEvent(ctx, &session.NewAggregate(i.SessionID, i.InstanceID).Aggregate, time.Now()),
idpintent.NewConsumedEvent(ctx, &idpintent.NewAggregate(i.CheckIntent.ID, "").Aggregate),
}, nil
}
// Execute implements [Commander].
func (i *IDPIntentCheckCommand) Execute(ctx context.Context, opts *InvokeOpts) (err error) {
if i.CheckIntent == nil {
return nil
}
intentRepo := opts.idpIntentRepo
removedRows, err := intentRepo.Delete(ctx, opts.DB(), intentRepo.PrimaryKeyCondition(i.InstanceID, i.CheckIntent.ID))
if err != nil {
return zerrors.ThrowInternal(err, "DOM-j1s5Eu", "failed deleting IDP intent")
}
if removedRows != 1 {
return zerrors.ThrowInternal(NewRowsReturnedMismatchError(1, removedRows), "DOM-3CBpdB", "unexpected number of rows deleted")
}
i.IsCheckComplete = true
return nil
}
// String implements [Commander].
func (i *IDPIntentCheckCommand) String() string {
return "IDPIntentCheckCommand"
}
// Validate implements [Commander].
func (i *IDPIntentCheckCommand) Validate(ctx context.Context, opts *InvokeOpts) (err error) {
if i.CheckIntent == nil {
return nil
}
if i.SessionID == "" {
return zerrors.ThrowPreconditionFailed(nil, "DOM-5Y8pb4", "Errors.Missing.SessionID")
}
if i.InstanceID == "" {
return zerrors.ThrowPreconditionFailed(nil, "DOM-Q4YFIq", "Errors.Missing.InstanceID")
}
sessionRepo := opts.sessionRepo
idpIntentRepo := opts.idpIntentRepo
userRepo := opts.userRepo
session, err := sessionRepo.Get(ctx, opts.DB(), database.WithCondition(sessionRepo.PrimaryKeyCondition(i.InstanceID, i.SessionID)))
if err = handleGetError(err, "DOM-EhIgey", "session"); err != nil {
return err
}
if session.UserID == "" {
return zerrors.ThrowPreconditionFailed(nil, "DOM-IJcVkV", "Errors.User.UserIDMissing")
}
if err := crypto.CheckToken(i.EncAlgo, i.CheckIntent.Token, i.CheckIntent.ID); err != nil {
return err
}
intent, err := idpIntentRepo.Get(ctx, opts.DB(), database.WithCondition(idpIntentRepo.PrimaryKeyCondition(i.InstanceID, i.CheckIntent.ID)))
if err = handleGetError(err, "DOM-5XkWJV", "intent"); err != nil {
return err
}
if intent.State != IDPIntentStateSucceeded {
return zerrors.ThrowPreconditionFailed(nil, "DOM-0UKHku", "Errors.Intent.NotSucceeded")
}
if intent.ExpiresAt == nil || intent.ExpiresAt.Before(time.Now()) {
return zerrors.ThrowPreconditionFailed(nil, "DOM-kDR1XK", "Errors.Intent.Expired")
}
user, err := userRepo.Get(ctx, opts.DB(), database.WithCondition(userRepo.PrimaryKeyCondition(i.InstanceID, session.UserID)))
if err = handleGetError(err, "DOM-Vnx2G9", "user"); err != nil {
return err
}
if user.Human == nil {
return zerrors.ThrowInternal(nil, "DOM-FkX5lZ", "user not human")
}
if intentUsr := intent.UserID; intentUsr != "" {
if intentUsr != session.UserID {
return zerrors.ThrowPreconditionFailed(nil, "DOM-FLdnLH", "Errors.Intent.OtherUser")
}
i.FetchedUser = *user
return nil
}
var matchingLink *IdentityProviderLink
for _, idpLink := range user.Human.IdentityProviderLinks {
if idpLink.ProviderID == intent.IDPID {
matchingLink = idpLink
break
}
}
if matchingLink == nil {
return zerrors.ThrowPreconditionFailed(nil, "DOM-XuNkt7", "Errors.Intent.OtherUser")
}
i.FetchedUser = *user
return nil
}
var _ Commander = (*IDPIntentCheckCommand)(nil)
@@ -0,0 +1,821 @@
package domain_test
import (
"context"
"encoding/base64"
"errors"
"testing"
"time"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"github.com/zitadel/zitadel/backend/v3/domain"
domainmock "github.com/zitadel/zitadel/backend/v3/domain/mock"
"github.com/zitadel/zitadel/backend/v3/storage/database"
"github.com/zitadel/zitadel/backend/v3/storage/database/dbmock"
noopdb "github.com/zitadel/zitadel/backend/v3/storage/database/dialect/noop"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/idpintent"
"github.com/zitadel/zitadel/internal/repository/session"
"github.com/zitadel/zitadel/internal/zerrors"
)
func TestIDPIntentCheckCommand_Events(t *testing.T) {
t.Parallel()
tt := []struct {
name string
command *domain.IDPIntentCheckCommand
expectedEvents []eventstore.Command
}{
{
name: "nil CheckIntent returns nil events",
command: &domain.IDPIntentCheckCommand{
CheckIntent: nil,
IsCheckComplete: true,
},
},
{
name: "isCheckComplete false returns nil events",
command: &domain.IDPIntentCheckCommand{
CheckIntent: &domain.CheckIDPIntentType{},
},
},
{
name: "valid command returns events",
command: &domain.IDPIntentCheckCommand{
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123"},
SessionID: "session-456",
InstanceID: "instance-789",
IsCheckComplete: true,
},
expectedEvents: []eventstore.Command{
session.NewIntentCheckedEvent(t.Context(), &session.NewAggregate("session-456", "instance-789").Aggregate, time.Now()),
idpintent.NewConsumedEvent(t.Context(), &idpintent.NewAggregate("intent-123", "").Aggregate),
},
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
// Test
events, err := tc.command.Events(context.Background(), &domain.InvokeOpts{})
// Verify
assert.NoError(t, err)
require.Len(t, events, len(tc.expectedEvents))
for i, expectedType := range tc.expectedEvents {
assert.IsType(t, expectedType, events[i])
switch expectedAssertedType := expectedType.(type) {
case *session.IntentCheckedEvent:
actualAssertedType, ok := events[i].(*session.IntentCheckedEvent)
require.True(t, ok)
assert.InDelta(t, expectedAssertedType.CheckedAt.UnixMilli(), actualAssertedType.CheckedAt.UnixMilli(), 10)
case *idpintent.ConsumedEvent:
_, ok := events[i].(*idpintent.ConsumedEvent)
require.True(t, ok)
}
}
})
}
}
func TestIDPIntentCheckCommand_Validate(t *testing.T) {
t.Parallel()
getErr := errors.New("get error")
notFoundErr := database.NewNoRowFoundError(nil)
decryptionError := errors.New("decryption error")
invalidTokenEncoding := base64.RawURLEncoding.EncodeToString([]byte("invalid-token"))
validTokenEncoding := base64.RawURLEncoding.EncodeToString([]byte("token-123"))
tt := []struct {
testName string
sessionRepo func(ctrl *gomock.Controller) domain.SessionRepository
idpIntentRepo func(ctrl *gomock.Controller) domain.IDPIntentRepository
userRepo func(ctrl *gomock.Controller) domain.UserRepository
encryptionAlgorithm func(ctrl *gomock.Controller) crypto.EncryptionAlgorithm
cmd *domain.IDPIntentCheckCommand
expectedError error
expectedUser domain.User
}{
{
testName: "when CheckIntent is nil should return no error",
cmd: &domain.IDPIntentCheckCommand{},
expectedError: nil,
},
{
testName: "when session ID is not set should return error",
cmd: &domain.IDPIntentCheckCommand{
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
SessionID: "",
InstanceID: "instance-1",
},
expectedError: zerrors.ThrowPreconditionFailed(nil, "DOM-5Y8pb4", "Errors.Missing.SessionID"),
},
{
testName: "when instance ID is not set should return error",
cmd: &domain.IDPIntentCheckCommand{
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
SessionID: "session-1",
InstanceID: "",
},
expectedError: zerrors.ThrowPreconditionFailed(nil, "DOM-Q4YFIq", "Errors.Missing.InstanceID"),
},
{
testName: "when retrieving session fails should return error",
cmd: &domain.IDPIntentCheckCommand{
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
SessionID: "session-1",
InstanceID: "instance-1",
},
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
repo := domainmock.NewSessionRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "session-1")))).
Return(nil, getErr)
return repo
},
expectedError: zerrors.ThrowInternal(getErr, "DOM-EhIgey", "failed fetching session"),
},
{
testName: "when session not found should return error",
cmd: &domain.IDPIntentCheckCommand{
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
SessionID: "session-1",
InstanceID: "instance-1",
},
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
repo := domainmock.NewSessionRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "session-1")))).
Return(nil, notFoundErr)
return repo
},
expectedError: zerrors.ThrowNotFound(notFoundErr, "DOM-EhIgey", "session not found"),
},
{
testName: "when session userID is empty should return error",
cmd: &domain.IDPIntentCheckCommand{
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
SessionID: "session-1",
InstanceID: "instance-1",
},
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
repo := domainmock.NewSessionRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "session-1")))).
Return(&domain.Session{ID: "session-1", UserID: ""}, nil)
return repo
},
expectedError: zerrors.ThrowPreconditionFailed(nil, "DOM-IJcVkV", "Errors.User.UserIDMissing"),
},
{
testName: "when token verification fails should return error",
cmd: &domain.IDPIntentCheckCommand{
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: invalidTokenEncoding},
SessionID: "session-1",
InstanceID: "instance-1",
},
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
repo := domainmock.NewSessionRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "session-1")))).
Return(&domain.Session{ID: "session-1", UserID: "user-1"}, nil)
return repo
},
encryptionAlgorithm: func(ctrl *gomock.Controller) crypto.EncryptionAlgorithm {
cryptoAlg := crypto.NewMockEncryptionAlgorithm(ctrl)
cryptoAlg.EXPECT().
EncryptionKeyID().
Times(1)
cryptoAlg.EXPECT().
DecryptString(gomock.Any(), gomock.Any()).
Times(1).
Return("", decryptionError)
return cryptoAlg
},
expectedError: zerrors.ThrowPermissionDenied(decryptionError, "CRYPTO-Sf4gt", "Errors.Intent.InvalidToken"),
},
{
testName: "when retrieving intent fails should return error",
cmd: &domain.IDPIntentCheckCommand{
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
SessionID: "session-1",
InstanceID: "instance-1",
},
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
repo := domainmock.NewSessionRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "session-1")))).
Return(&domain.Session{ID: "session-1", UserID: "user-1"}, nil)
return repo
},
encryptionAlgorithm: func(ctrl *gomock.Controller) crypto.EncryptionAlgorithm {
cryptoAlg := crypto.NewMockEncryptionAlgorithm(ctrl)
cryptoAlg.EXPECT().
EncryptionKeyID().
Times(1)
cryptoAlg.EXPECT().
DecryptString(gomock.Any(), gomock.Any()).
Times(1).
Return("intent-123", nil)
return cryptoAlg
},
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
repo := domainmock.NewIDPIntentRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "intent-123")))).
Return(nil, getErr)
return repo
},
expectedError: zerrors.ThrowInternal(getErr, "DOM-5XkWJV", "failed fetching intent"),
},
{
testName: "when intent not found should return error",
cmd: &domain.IDPIntentCheckCommand{
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
SessionID: "session-1",
InstanceID: "instance-1",
},
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
repo := domainmock.NewSessionRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "session-1")))).
Return(&domain.Session{ID: "session-1", UserID: "user-1"}, nil)
return repo
},
encryptionAlgorithm: func(ctrl *gomock.Controller) crypto.EncryptionAlgorithm {
cryptoAlg := crypto.NewMockEncryptionAlgorithm(ctrl)
cryptoAlg.EXPECT().
EncryptionKeyID().
Times(1)
cryptoAlg.EXPECT().
DecryptString(gomock.Any(), gomock.Any()).
Times(1).
Return("intent-123", nil)
return cryptoAlg
},
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
repo := domainmock.NewIDPIntentRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "intent-123")))).
Return(nil, notFoundErr)
return repo
},
expectedError: zerrors.ThrowNotFound(notFoundErr, "DOM-5XkWJV", "intent not found"),
},
{
testName: "when intent state is not succeeded should return error",
cmd: &domain.IDPIntentCheckCommand{
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
SessionID: "session-1",
InstanceID: "instance-1",
},
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
repo := domainmock.NewSessionRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "session-1")))).
Return(&domain.Session{ID: "session-1", UserID: "user-1"}, nil)
return repo
},
encryptionAlgorithm: func(ctrl *gomock.Controller) crypto.EncryptionAlgorithm {
cryptoAlg := crypto.NewMockEncryptionAlgorithm(ctrl)
cryptoAlg.EXPECT().
EncryptionKeyID().
Times(1)
cryptoAlg.EXPECT().
DecryptString(gomock.Any(), gomock.Any()).
Times(1).
Return("intent-123", nil)
return cryptoAlg
},
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
repo := domainmock.NewIDPIntentRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "intent-123")))).
Return(&domain.IDPIntent{ID: "intent-123", State: domain.IDPIntentStateFailed, ExpiresAt: gu.Ptr(time.Now().Add(time.Hour))}, nil)
return repo
},
expectedError: zerrors.ThrowPreconditionFailed(nil, "DOM-0UKHku", "Errors.Intent.NotSucceeded"),
},
{
testName: "when intent is expired should return error",
cmd: &domain.IDPIntentCheckCommand{
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
SessionID: "session-1",
InstanceID: "instance-1",
},
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
repo := domainmock.NewSessionRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "session-1")))).
Return(&domain.Session{ID: "session-1", UserID: "user-1"}, nil)
return repo
},
encryptionAlgorithm: func(ctrl *gomock.Controller) crypto.EncryptionAlgorithm {
cryptoAlg := crypto.NewMockEncryptionAlgorithm(ctrl)
cryptoAlg.EXPECT().
EncryptionKeyID().
Times(1)
cryptoAlg.EXPECT().
DecryptString(gomock.Any(), gomock.Any()).
Times(1).
Return("intent-123", nil)
return cryptoAlg
},
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
repo := domainmock.NewIDPIntentRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "intent-123")))).
Return(&domain.IDPIntent{ID: "intent-123", State: domain.IDPIntentStateSucceeded, ExpiresAt: gu.Ptr(time.Now().Add(-time.Hour))}, nil)
return repo
},
expectedError: zerrors.ThrowPreconditionFailed(nil, "DOM-kDR1XK", "Errors.Intent.Expired"),
},
{
testName: "when intent expiration is nil should return error",
cmd: &domain.IDPIntentCheckCommand{
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
SessionID: "session-1",
InstanceID: "instance-1",
},
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
repo := domainmock.NewSessionRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "session-1")))).
Return(&domain.Session{ID: "session-1", UserID: "user-1"}, nil)
return repo
},
encryptionAlgorithm: func(ctrl *gomock.Controller) crypto.EncryptionAlgorithm {
cryptoAlg := crypto.NewMockEncryptionAlgorithm(ctrl)
cryptoAlg.EXPECT().
EncryptionKeyID().
Times(1)
cryptoAlg.EXPECT().
DecryptString(gomock.Any(), gomock.Any()).
Times(1).
Return("intent-123", nil)
return cryptoAlg
},
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
repo := domainmock.NewIDPIntentRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "intent-123")))).
Return(&domain.IDPIntent{ID: "intent-123", State: domain.IDPIntentStateSucceeded, ExpiresAt: nil}, nil)
return repo
},
expectedError: zerrors.ThrowPreconditionFailed(nil, "DOM-kDR1XK", "Errors.Intent.Expired"),
},
{
testName: "when retrieving user fails should return error",
cmd: &domain.IDPIntentCheckCommand{
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
SessionID: "session-1",
InstanceID: "instance-1",
},
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
repo := domainmock.NewSessionRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "session-1")))).
Return(&domain.Session{ID: "session-1", UserID: "user-1"}, nil)
return repo
},
encryptionAlgorithm: func(ctrl *gomock.Controller) crypto.EncryptionAlgorithm {
cryptoAlg := crypto.NewMockEncryptionAlgorithm(ctrl)
cryptoAlg.EXPECT().
EncryptionKeyID().
Times(1)
cryptoAlg.EXPECT().
DecryptString(gomock.Any(), gomock.Any()).
Times(1).
Return("intent-123", nil)
return cryptoAlg
},
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
repo := domainmock.NewIDPIntentRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "intent-123")))).
Return(&domain.IDPIntent{ID: "intent-123", State: domain.IDPIntentStateSucceeded, ExpiresAt: gu.Ptr(time.Now().Add(time.Hour)), UserID: ""}, nil)
return repo
},
userRepo: func(ctrl *gomock.Controller) domain.UserRepository {
repo := domainmock.NewUserRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "user-1")))).
Return(nil, getErr)
return repo
},
expectedError: zerrors.ThrowInternal(getErr, "DOM-Vnx2G9", "failed fetching user"),
},
{
testName: "when user is not found should return not found error",
cmd: &domain.IDPIntentCheckCommand{
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
SessionID: "session-1",
InstanceID: "instance-1",
},
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
repo := domainmock.NewSessionRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "session-1")))).
Return(&domain.Session{ID: "session-1", UserID: "user-1"}, nil)
return repo
},
encryptionAlgorithm: func(ctrl *gomock.Controller) crypto.EncryptionAlgorithm {
cryptoAlg := crypto.NewMockEncryptionAlgorithm(ctrl)
cryptoAlg.EXPECT().
EncryptionKeyID().
Times(1)
cryptoAlg.EXPECT().
DecryptString(gomock.Any(), gomock.Any()).
Times(1).
Return("intent-123", nil)
return cryptoAlg
},
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
repo := domainmock.NewIDPIntentRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "intent-123")))).
Return(&domain.IDPIntent{ID: "intent-123", State: domain.IDPIntentStateSucceeded, ExpiresAt: gu.Ptr(time.Now().Add(time.Hour)), UserID: ""}, nil)
return repo
},
userRepo: func(ctrl *gomock.Controller) domain.UserRepository {
repo := domainmock.NewUserRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "user-1")))).
Return(nil, notFoundErr)
return repo
},
expectedError: zerrors.ThrowNotFound(notFoundErr, "DOM-Vnx2G9", "user not found"),
},
{
testName: "when user is not human should return error",
cmd: &domain.IDPIntentCheckCommand{
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
SessionID: "session-1",
InstanceID: "instance-1",
},
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
repo := domainmock.NewSessionRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "session-1")))).
Return(&domain.Session{ID: "session-1", UserID: "user-1"}, nil)
return repo
},
encryptionAlgorithm: func(ctrl *gomock.Controller) crypto.EncryptionAlgorithm {
cryptoAlg := crypto.NewMockEncryptionAlgorithm(ctrl)
cryptoAlg.EXPECT().
EncryptionKeyID().
Times(1)
cryptoAlg.EXPECT().
DecryptString(gomock.Any(), gomock.Any()).
Times(1).
Return("intent-123", nil)
return cryptoAlg
},
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
repo := domainmock.NewIDPIntentRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "intent-123")))).
Return(&domain.IDPIntent{ID: "intent-123", State: domain.IDPIntentStateSucceeded, ExpiresAt: gu.Ptr(time.Now().Add(time.Hour)), UserID: ""}, nil)
return repo
},
userRepo: func(ctrl *gomock.Controller) domain.UserRepository {
repo := domainmock.NewUserRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "user-1")))).
Return(&domain.User{ID: "user-1", State: domain.UserStateActive, Human: nil}, nil)
return repo
},
expectedError: zerrors.ThrowInternal(nil, "DOM-FkX5lZ", "user not human"),
},
{
testName: "when intent has user and it matches session user should return no error and set fetched user",
cmd: &domain.IDPIntentCheckCommand{
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
SessionID: "session-1",
InstanceID: "instance-1",
},
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
repo := domainmock.NewSessionRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "session-1")))).
Return(&domain.Session{ID: "session-1", UserID: "user-1"}, nil)
return repo
},
encryptionAlgorithm: func(ctrl *gomock.Controller) crypto.EncryptionAlgorithm {
cryptoAlg := crypto.NewMockEncryptionAlgorithm(ctrl)
cryptoAlg.EXPECT().
EncryptionKeyID().
Times(1)
cryptoAlg.EXPECT().
DecryptString(gomock.Any(), gomock.Any()).
Times(1).
Return("intent-123", nil)
return cryptoAlg
},
userRepo: func(ctrl *gomock.Controller) domain.UserRepository {
repo := domainmock.NewUserRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "user-1")))).
Return(&domain.User{
ID: "user-1",
State: domain.UserStateActive,
Human: &domain.HumanUser{IdentityProviderLinks: []*domain.IdentityProviderLink{{ProviderID: "idp-123"}}},
}, nil)
return repo
},
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
repo := domainmock.NewIDPIntentRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "intent-123")))).
Return(&domain.IDPIntent{ID: "intent-123", State: domain.IDPIntentStateSucceeded, ExpiresAt: gu.Ptr(time.Now().Add(time.Hour)), UserID: "user-1"}, nil)
return repo
},
expectedError: nil,
expectedUser: domain.User{
ID: "user-1",
State: domain.UserStateActive,
Human: &domain.HumanUser{
IdentityProviderLinks: []*domain.IdentityProviderLink{
{ProviderID: "idp-123"},
},
},
},
},
{
testName: "when intent has user but it does not match session user should return error",
cmd: &domain.IDPIntentCheckCommand{
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
SessionID: "session-1",
InstanceID: "instance-1",
},
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
repo := domainmock.NewSessionRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "session-1")))).
Return(&domain.Session{ID: "session-1", UserID: "user-1"}, nil)
return repo
},
encryptionAlgorithm: func(ctrl *gomock.Controller) crypto.EncryptionAlgorithm {
cryptoAlg := crypto.NewMockEncryptionAlgorithm(ctrl)
cryptoAlg.EXPECT().
EncryptionKeyID().
Times(1)
cryptoAlg.EXPECT().
DecryptString(gomock.Any(), gomock.Any()).
Times(1).
Return("intent-123", nil)
return cryptoAlg
},
userRepo: func(ctrl *gomock.Controller) domain.UserRepository {
repo := domainmock.NewUserRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "user-1")))).
Return(&domain.User{
ID: "user-1",
State: domain.UserStateActive,
Human: &domain.HumanUser{IdentityProviderLinks: []*domain.IdentityProviderLink{{ProviderID: "idp-123"}}},
}, nil)
return repo
},
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
repo := domainmock.NewIDPIntentRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "intent-123")))).
Return(&domain.IDPIntent{ID: "intent-123", State: domain.IDPIntentStateSucceeded, ExpiresAt: gu.Ptr(time.Now().Add(time.Hour)), UserID: "user-2"}, nil)
return repo
},
expectedError: zerrors.ThrowPreconditionFailed(nil, "DOM-FLdnLH", "Errors.Intent.OtherUser"),
},
{
testName: "when user has no matching IDP link should return error",
cmd: &domain.IDPIntentCheckCommand{
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
SessionID: "session-1",
InstanceID: "instance-1",
},
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
repo := domainmock.NewSessionRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "session-1")))).
Return(&domain.Session{ID: "session-1", UserID: "user-1"}, nil)
return repo
},
encryptionAlgorithm: func(ctrl *gomock.Controller) crypto.EncryptionAlgorithm {
cryptoAlg := crypto.NewMockEncryptionAlgorithm(ctrl)
cryptoAlg.EXPECT().
EncryptionKeyID().
Times(1)
cryptoAlg.EXPECT().
DecryptString(gomock.Any(), gomock.Any()).
Times(1).
Return("intent-123", nil)
return cryptoAlg
},
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
repo := domainmock.NewIDPIntentRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "intent-123")))).
Return(&domain.IDPIntent{ID: "intent-123", State: domain.IDPIntentStateSucceeded, ExpiresAt: gu.Ptr(time.Now().Add(time.Hour)), UserID: "", IDPID: "idp-123"}, nil)
return repo
},
userRepo: func(ctrl *gomock.Controller) domain.UserRepository {
repo := domainmock.NewUserRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "user-1")))).
Return(&domain.User{
ID: "user-1",
State: domain.UserStateActive,
Human: &domain.HumanUser{IdentityProviderLinks: []*domain.IdentityProviderLink{{ProviderID: "idp-456"}}},
}, nil)
return repo
},
expectedError: zerrors.ThrowPreconditionFailed(nil, "DOM-XuNkt7", "Errors.Intent.OtherUser"),
},
{
testName: "when all validations pass should return no error",
cmd: &domain.IDPIntentCheckCommand{
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
SessionID: "session-1",
InstanceID: "instance-1",
},
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
repo := domainmock.NewSessionRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "session-1")))).
Return(&domain.Session{ID: "session-1", UserID: "user-1"}, nil)
return repo
},
encryptionAlgorithm: func(ctrl *gomock.Controller) crypto.EncryptionAlgorithm {
cryptoAlg := crypto.NewMockEncryptionAlgorithm(ctrl)
cryptoAlg.EXPECT().
EncryptionKeyID().
Times(1)
cryptoAlg.EXPECT().
DecryptString(gomock.Any(), gomock.Any()).
Times(1).
Return("intent-123", nil)
return cryptoAlg
},
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
repo := domainmock.NewIDPIntentRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "intent-123")))).
Return(&domain.IDPIntent{ID: "intent-123", State: domain.IDPIntentStateSucceeded, ExpiresAt: gu.Ptr(time.Now().Add(time.Hour)), UserID: "", IDPID: "idp-123"}, nil)
return repo
},
userRepo: func(ctrl *gomock.Controller) domain.UserRepository {
repo := domainmock.NewUserRepo(ctrl)
repo.EXPECT().
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "user-1")))).
Return(&domain.User{
ID: "user-1",
State: domain.UserStateActive,
Human: &domain.HumanUser{
IdentityProviderLinks: []*domain.IdentityProviderLink{
{ProviderID: "idp-123"},
},
},
}, nil)
return repo
},
expectedError: nil,
expectedUser: domain.User{
ID: "user-1",
State: domain.UserStateActive,
Human: &domain.HumanUser{
IdentityProviderLinks: []*domain.IdentityProviderLink{
{ProviderID: "idp-123"},
},
},
},
},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
t.Parallel()
// Given
ctrl := gomock.NewController(t)
ctx := authz.NewMockContext(tc.cmd.InstanceID, "", "")
opts := &domain.InvokeOpts{
Invoker: domain.NewTransactionInvoker(nil),
}
domain.WithQueryExecutor(new(noopdb.Pool))(opts)
if tc.sessionRepo != nil {
domain.WithSessionRepo(tc.sessionRepo(ctrl))(opts)
}
if tc.userRepo != nil {
domain.WithUserRepo(tc.userRepo(ctrl))(opts)
}
if tc.idpIntentRepo != nil {
domain.WithIDPIntentRepo(tc.idpIntentRepo(ctrl))(opts)
}
if tc.encryptionAlgorithm != nil {
tc.cmd.EncAlgo = tc.encryptionAlgorithm(ctrl)
}
// Test
err := tc.cmd.Validate(ctx, opts)
// Verify
assert.ErrorIs(t, err, tc.expectedError)
assert.Equal(t, tc.expectedUser, tc.cmd.FetchedUser)
})
}
}
func TestIDPIntentCheckCommand_Execute(t *testing.T) {
t.Parallel()
deleteErr := errors.New("delete error")
tt := []struct {
testName string
idpIntentRepo func(ctrl *gomock.Controller) domain.IDPIntentRepository
cmd *domain.IDPIntentCheckCommand
expectedError error
expectComplete bool
}{
{
testName: "when CheckIntent is nil should return no error and not mark complete",
cmd: &domain.IDPIntentCheckCommand{},
expectedError: nil,
expectComplete: false,
},
{
testName: "when intent deletion fails should return error",
cmd: domain.NewIDPIntentCheckCommand(&domain.CheckIDPIntentType{ID: "intent-123"}, "session-1", "instance-1", nil),
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
repo := domainmock.NewIDPIntentRepo(ctrl)
repo.EXPECT().
Delete(gomock.Any(), gomock.Any(), repo.PrimaryKeyCondition("instance-1", "intent-123")).
Return(int64(0), deleteErr)
return repo
},
expectedError: zerrors.ThrowInternal(deleteErr, "DOM-j1s5Eu", "failed deleting IDP intent"),
expectComplete: false,
},
{
testName: "when intent deletion returns unexpected row count should return error",
cmd: domain.NewIDPIntentCheckCommand(&domain.CheckIDPIntentType{ID: "intent-123"}, "session-1", "instance-1", nil),
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
repo := domainmock.NewIDPIntentRepo(ctrl)
repo.EXPECT().
Delete(gomock.Any(), gomock.Any(), repo.PrimaryKeyCondition("instance-1", "intent-123")).
Return(int64(0), nil)
return repo
},
expectedError: zerrors.ThrowInternal(domain.NewRowsReturnedMismatchError(1, 0), "DOM-3CBpdB", "unexpected number of rows deleted"),
expectComplete: false,
},
{
testName: "when intent is successfully deleted should mark complete",
cmd: domain.NewIDPIntentCheckCommand(&domain.CheckIDPIntentType{ID: "intent-123"}, "session-1", "instance-1", nil),
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
repo := domainmock.NewIDPIntentRepo(ctrl)
repo.EXPECT().
Delete(gomock.Any(), gomock.Any(), repo.PrimaryKeyCondition("instance-1", "intent-123")).
Return(int64(1), nil)
return repo
},
expectedError: nil,
expectComplete: true,
},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
// t.Parallel()
// Given
ctrl := gomock.NewController(t)
ctx := authz.NewMockContext(tc.cmd.InstanceID, "", "")
opts := &domain.InvokeOpts{
Invoker: domain.NewTransactionInvoker(nil),
}
domain.WithQueryExecutor(new(noopdb.Pool))(opts)
if tc.idpIntentRepo != nil {
domain.WithIDPIntentRepo(tc.idpIntentRepo(ctrl))(opts)
}
// Test
err := tc.cmd.Execute(ctx, opts)
// Verify
assert.ErrorIs(t, err, tc.expectedError)
assert.Equal(t, tc.expectComplete, tc.cmd.IsCheckComplete)
})
}
}
@@ -570,6 +570,11 @@ func TestDeleteIDPIntent(t *testing.T) {
),
expectedDeletedCount: 2,
},
{
testName: "when condition matches no record should not delete anything",
inputConditions: idpIntentRepo.PrimaryKeyCondition(instanceID, "not found"),
expectedDeletedCount: 0,
},
}
for _, tc := range tt {
+1
View File
@@ -209,6 +209,7 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server
new_domain.SetLegacyEventstore(eventstoreClient)
new_domain.SetSystemConfig(config.SystemDefaults)
new_domain.SetIDPEncryptionAlgorithm(keys.IDPConfig)
sessionTokenVerifier := internal_authz.SessionTokenVerifier(keys.OIDC)
cacheConnectors, err := connector.StartConnectors(config.Caches, dbClient)
+1
View File
@@ -20,6 +20,7 @@ import (
type MockGenerator struct {
ctrl *gomock.Controller
recorder *MockGeneratorMockRecorder
isgomock struct{}
}
// MockGeneratorMockRecorder is the mock recorder for MockGenerator.
+3 -26
View File
@@ -9,37 +9,14 @@ import (
)
func CreateMockEncryptionAlg(ctrl *gomock.Controller) EncryptionAlgorithm {
return createMockEncryptionAlgorithm(
ctrl,
func(code []byte) ([]byte, error) {
return code, nil
},
)
}
// CreateMockEncryptionAlgWithCode compares the length of the value to be encrypted with the length of the provided code.
// It will return an error if they do not match.
// The provided code will be used to encrypt in favor of the value passed to the encryption.
// This function is intended to be used where the passed value is not in control, but where the returned encryption requires a static value.
func CreateMockEncryptionAlgWithCode(ctrl *gomock.Controller, code string) EncryptionAlgorithm {
return createMockEncryptionAlgorithm(
ctrl,
func(c []byte) ([]byte, error) {
if len(c) != len(code) {
return nil, zerrors.ThrowInvalidArgumentf(nil, "id", "invalid code length - expected %d, got %d", len(code), len(c))
}
return []byte(code), nil
},
)
}
func createMockEncryptionAlgorithm(ctrl *gomock.Controller, encryptFunction func(c []byte) ([]byte, error)) *MockEncryptionAlgorithm {
mCrypto := NewMockEncryptionAlgorithm(ctrl)
mCrypto.EXPECT().Algorithm().AnyTimes().Return("enc")
mCrypto.EXPECT().EncryptionKeyID().AnyTimes().Return("id")
mCrypto.EXPECT().DecryptionKeyIDs().AnyTimes().Return([]string{"id"})
mCrypto.EXPECT().Encrypt(gomock.Any()).AnyTimes().DoAndReturn(
encryptFunction,
func(code []byte) ([]byte, error) {
return code, nil
},
)
mCrypto.EXPECT().DecryptString(gomock.Any(), gomock.Any()).AnyTimes().DoAndReturn(
func(code []byte, keyID string) (string, error) {
+2
View File
@@ -14,6 +14,8 @@ const (
TypeHash // Depcrecated: use [passwap.Swapper] instead
)
//go:generate mockgen -typed -package crypto -destination ./crypto.mock.go . EncryptionAlgorithm
type EncryptionAlgorithm interface {
Algorithm() string
EncryptionKeyID() string
+271
View File
@@ -0,0 +1,271 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/zitadel/zitadel/internal/crypto (interfaces: EncryptionAlgorithm)
//
// Generated by this command:
//
// mockgen -typed -package crypto -destination ./crypto.mock.go . EncryptionAlgorithm
//
// Package crypto is a generated GoMock package.
package crypto
import (
reflect "reflect"
gomock "go.uber.org/mock/gomock"
)
// MockEncryptionAlgorithm is a mock of EncryptionAlgorithm interface.
type MockEncryptionAlgorithm struct {
ctrl *gomock.Controller
recorder *MockEncryptionAlgorithmMockRecorder
isgomock struct{}
}
// MockEncryptionAlgorithmMockRecorder is the mock recorder for MockEncryptionAlgorithm.
type MockEncryptionAlgorithmMockRecorder struct {
mock *MockEncryptionAlgorithm
}
// NewMockEncryptionAlgorithm creates a new mock instance.
func NewMockEncryptionAlgorithm(ctrl *gomock.Controller) *MockEncryptionAlgorithm {
mock := &MockEncryptionAlgorithm{ctrl: ctrl}
mock.recorder = &MockEncryptionAlgorithmMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockEncryptionAlgorithm) EXPECT() *MockEncryptionAlgorithmMockRecorder {
return m.recorder
}
// Algorithm mocks base method.
func (m *MockEncryptionAlgorithm) Algorithm() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Algorithm")
ret0, _ := ret[0].(string)
return ret0
}
// Algorithm indicates an expected call of Algorithm.
func (mr *MockEncryptionAlgorithmMockRecorder) Algorithm() *MockEncryptionAlgorithmAlgorithmCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Algorithm", reflect.TypeOf((*MockEncryptionAlgorithm)(nil).Algorithm))
return &MockEncryptionAlgorithmAlgorithmCall{Call: call}
}
// MockEncryptionAlgorithmAlgorithmCall wrap *gomock.Call
type MockEncryptionAlgorithmAlgorithmCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockEncryptionAlgorithmAlgorithmCall) Return(arg0 string) *MockEncryptionAlgorithmAlgorithmCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockEncryptionAlgorithmAlgorithmCall) Do(f func() string) *MockEncryptionAlgorithmAlgorithmCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockEncryptionAlgorithmAlgorithmCall) DoAndReturn(f func() string) *MockEncryptionAlgorithmAlgorithmCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// Decrypt mocks base method.
func (m *MockEncryptionAlgorithm) Decrypt(hashed []byte, keyID string) ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Decrypt", hashed, keyID)
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Decrypt indicates an expected call of Decrypt.
func (mr *MockEncryptionAlgorithmMockRecorder) Decrypt(hashed, keyID any) *MockEncryptionAlgorithmDecryptCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Decrypt", reflect.TypeOf((*MockEncryptionAlgorithm)(nil).Decrypt), hashed, keyID)
return &MockEncryptionAlgorithmDecryptCall{Call: call}
}
// MockEncryptionAlgorithmDecryptCall wrap *gomock.Call
type MockEncryptionAlgorithmDecryptCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockEncryptionAlgorithmDecryptCall) Return(arg0 []byte, arg1 error) *MockEncryptionAlgorithmDecryptCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockEncryptionAlgorithmDecryptCall) Do(f func([]byte, string) ([]byte, error)) *MockEncryptionAlgorithmDecryptCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockEncryptionAlgorithmDecryptCall) DoAndReturn(f func([]byte, string) ([]byte, error)) *MockEncryptionAlgorithmDecryptCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// DecryptString mocks base method.
func (m *MockEncryptionAlgorithm) DecryptString(hashed []byte, keyID string) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DecryptString", hashed, keyID)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// DecryptString indicates an expected call of DecryptString.
func (mr *MockEncryptionAlgorithmMockRecorder) DecryptString(hashed, keyID any) *MockEncryptionAlgorithmDecryptStringCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecryptString", reflect.TypeOf((*MockEncryptionAlgorithm)(nil).DecryptString), hashed, keyID)
return &MockEncryptionAlgorithmDecryptStringCall{Call: call}
}
// MockEncryptionAlgorithmDecryptStringCall wrap *gomock.Call
type MockEncryptionAlgorithmDecryptStringCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockEncryptionAlgorithmDecryptStringCall) Return(arg0 string, arg1 error) *MockEncryptionAlgorithmDecryptStringCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockEncryptionAlgorithmDecryptStringCall) Do(f func([]byte, string) (string, error)) *MockEncryptionAlgorithmDecryptStringCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockEncryptionAlgorithmDecryptStringCall) DoAndReturn(f func([]byte, string) (string, error)) *MockEncryptionAlgorithmDecryptStringCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// DecryptionKeyIDs mocks base method.
func (m *MockEncryptionAlgorithm) DecryptionKeyIDs() []string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DecryptionKeyIDs")
ret0, _ := ret[0].([]string)
return ret0
}
// DecryptionKeyIDs indicates an expected call of DecryptionKeyIDs.
func (mr *MockEncryptionAlgorithmMockRecorder) DecryptionKeyIDs() *MockEncryptionAlgorithmDecryptionKeyIDsCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecryptionKeyIDs", reflect.TypeOf((*MockEncryptionAlgorithm)(nil).DecryptionKeyIDs))
return &MockEncryptionAlgorithmDecryptionKeyIDsCall{Call: call}
}
// MockEncryptionAlgorithmDecryptionKeyIDsCall wrap *gomock.Call
type MockEncryptionAlgorithmDecryptionKeyIDsCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockEncryptionAlgorithmDecryptionKeyIDsCall) Return(arg0 []string) *MockEncryptionAlgorithmDecryptionKeyIDsCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockEncryptionAlgorithmDecryptionKeyIDsCall) Do(f func() []string) *MockEncryptionAlgorithmDecryptionKeyIDsCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockEncryptionAlgorithmDecryptionKeyIDsCall) DoAndReturn(f func() []string) *MockEncryptionAlgorithmDecryptionKeyIDsCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// Encrypt mocks base method.
func (m *MockEncryptionAlgorithm) Encrypt(value []byte) ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Encrypt", value)
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Encrypt indicates an expected call of Encrypt.
func (mr *MockEncryptionAlgorithmMockRecorder) Encrypt(value any) *MockEncryptionAlgorithmEncryptCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Encrypt", reflect.TypeOf((*MockEncryptionAlgorithm)(nil).Encrypt), value)
return &MockEncryptionAlgorithmEncryptCall{Call: call}
}
// MockEncryptionAlgorithmEncryptCall wrap *gomock.Call
type MockEncryptionAlgorithmEncryptCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockEncryptionAlgorithmEncryptCall) Return(arg0 []byte, arg1 error) *MockEncryptionAlgorithmEncryptCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockEncryptionAlgorithmEncryptCall) Do(f func([]byte) ([]byte, error)) *MockEncryptionAlgorithmEncryptCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockEncryptionAlgorithmEncryptCall) DoAndReturn(f func([]byte) ([]byte, error)) *MockEncryptionAlgorithmEncryptCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// EncryptionKeyID mocks base method.
func (m *MockEncryptionAlgorithm) EncryptionKeyID() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "EncryptionKeyID")
ret0, _ := ret[0].(string)
return ret0
}
// EncryptionKeyID indicates an expected call of EncryptionKeyID.
func (mr *MockEncryptionAlgorithmMockRecorder) EncryptionKeyID() *MockEncryptionAlgorithmEncryptionKeyIDCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EncryptionKeyID", reflect.TypeOf((*MockEncryptionAlgorithm)(nil).EncryptionKeyID))
return &MockEncryptionAlgorithmEncryptionKeyIDCall{Call: call}
}
// MockEncryptionAlgorithmEncryptionKeyIDCall wrap *gomock.Call
type MockEncryptionAlgorithmEncryptionKeyIDCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockEncryptionAlgorithmEncryptionKeyIDCall) Return(arg0 string) *MockEncryptionAlgorithmEncryptionKeyIDCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockEncryptionAlgorithmEncryptionKeyIDCall) Do(f func() string) *MockEncryptionAlgorithmEncryptionKeyIDCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockEncryptionAlgorithmEncryptionKeyIDCall) DoAndReturn(f func() string) *MockEncryptionAlgorithmEncryptionKeyIDCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
-126
View File
@@ -1,126 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: crypto.go
//
// Generated by this command:
//
// mockgen -source crypto.go -destination ./crypto_mock.go -package crypto
//
// Package crypto is a generated GoMock package.
package crypto
import (
reflect "reflect"
gomock "go.uber.org/mock/gomock"
)
// MockEncryptionAlgorithm is a mock of EncryptionAlgorithm interface.
type MockEncryptionAlgorithm struct {
ctrl *gomock.Controller
recorder *MockEncryptionAlgorithmMockRecorder
}
// MockEncryptionAlgorithmMockRecorder is the mock recorder for MockEncryptionAlgorithm.
type MockEncryptionAlgorithmMockRecorder struct {
mock *MockEncryptionAlgorithm
}
// NewMockEncryptionAlgorithm creates a new mock instance.
func NewMockEncryptionAlgorithm(ctrl *gomock.Controller) *MockEncryptionAlgorithm {
mock := &MockEncryptionAlgorithm{ctrl: ctrl}
mock.recorder = &MockEncryptionAlgorithmMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockEncryptionAlgorithm) EXPECT() *MockEncryptionAlgorithmMockRecorder {
return m.recorder
}
// Algorithm mocks base method.
func (m *MockEncryptionAlgorithm) Algorithm() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Algorithm")
ret0, _ := ret[0].(string)
return ret0
}
// Algorithm indicates an expected call of Algorithm.
func (mr *MockEncryptionAlgorithmMockRecorder) Algorithm() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Algorithm", reflect.TypeOf((*MockEncryptionAlgorithm)(nil).Algorithm))
}
// Decrypt mocks base method.
func (m *MockEncryptionAlgorithm) Decrypt(hashed []byte, keyID string) ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Decrypt", hashed, keyID)
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Decrypt indicates an expected call of Decrypt.
func (mr *MockEncryptionAlgorithmMockRecorder) Decrypt(hashed, keyID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Decrypt", reflect.TypeOf((*MockEncryptionAlgorithm)(nil).Decrypt), hashed, keyID)
}
// DecryptString mocks base method.
func (m *MockEncryptionAlgorithm) DecryptString(hashed []byte, keyID string) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DecryptString", hashed, keyID)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// DecryptString indicates an expected call of DecryptString.
func (mr *MockEncryptionAlgorithmMockRecorder) DecryptString(hashed, keyID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecryptString", reflect.TypeOf((*MockEncryptionAlgorithm)(nil).DecryptString), hashed, keyID)
}
// DecryptionKeyIDs mocks base method.
func (m *MockEncryptionAlgorithm) DecryptionKeyIDs() []string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DecryptionKeyIDs")
ret0, _ := ret[0].([]string)
return ret0
}
// DecryptionKeyIDs indicates an expected call of DecryptionKeyIDs.
func (mr *MockEncryptionAlgorithmMockRecorder) DecryptionKeyIDs() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecryptionKeyIDs", reflect.TypeOf((*MockEncryptionAlgorithm)(nil).DecryptionKeyIDs))
}
// Encrypt mocks base method.
func (m *MockEncryptionAlgorithm) Encrypt(value []byte) ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Encrypt", value)
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Encrypt indicates an expected call of Encrypt.
func (mr *MockEncryptionAlgorithmMockRecorder) Encrypt(value any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Encrypt", reflect.TypeOf((*MockEncryptionAlgorithm)(nil).Encrypt), value)
}
// EncryptionKeyID mocks base method.
func (m *MockEncryptionAlgorithm) EncryptionKeyID() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "EncryptionKeyID")
ret0, _ := ret[0].(string)
return ret0
}
// EncryptionKeyID indicates an expected call of EncryptionKeyID.
func (mr *MockEncryptionAlgorithmMockRecorder) EncryptionKeyID() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EncryptionKeyID", reflect.TypeOf((*MockEncryptionAlgorithm)(nil).EncryptionKeyID))
}
@@ -2018,7 +2018,7 @@ func expectTemplateWithNotifyUserQueriesSMS(queries *mock.MockQueries) {
queries.EXPECT().CustomTextListByTemplate(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(2).Return(&query.CustomTexts{}, nil)
}
func cryptoValue(t *testing.T, ctrl *gomock.Controller, value string) (*crypto.MockEncryptionAlgorithm, *crypto.CryptoValue) {
func cryptoValue(t *testing.T, ctrl *gomock.Controller, value string) (crypto.EncryptionAlgorithm, *crypto.CryptoValue) {
encAlg := crypto.NewMockEncryptionAlgorithm(ctrl)
encAlg.EXPECT().Algorithm().AnyTimes().Return("enc")
encAlg.EXPECT().EncryptionKeyID().AnyTimes().Return("id")