mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-18 05:52:51 +00:00
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:
@@ -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)
|
||||
Reference in New Issue
Block a user