2023-05-05 15:34:53 +00:00
|
|
|
package command
|
|
|
|
|
|
|
|
import (
|
2023-07-14 06:49:57 +00:00
|
|
|
"bytes"
|
2023-05-05 15:34:53 +00:00
|
|
|
"context"
|
|
|
|
"encoding/base64"
|
|
|
|
"fmt"
|
|
|
|
"time"
|
|
|
|
|
2024-05-30 22:08:48 +00:00
|
|
|
"github.com/zitadel/logging"
|
2024-05-16 05:07:56 +00:00
|
|
|
"golang.org/x/text/language"
|
|
|
|
|
2023-11-28 15:56:29 +00:00
|
|
|
"github.com/zitadel/zitadel/internal/activity"
|
2023-05-05 15:34:53 +00:00
|
|
|
"github.com/zitadel/zitadel/internal/api/authz"
|
|
|
|
"github.com/zitadel/zitadel/internal/crypto"
|
|
|
|
"github.com/zitadel/zitadel/internal/domain"
|
|
|
|
"github.com/zitadel/zitadel/internal/eventstore"
|
|
|
|
"github.com/zitadel/zitadel/internal/id"
|
2024-09-26 07:14:33 +00:00
|
|
|
"github.com/zitadel/zitadel/internal/notification/senders"
|
2023-05-05 15:34:53 +00:00
|
|
|
"github.com/zitadel/zitadel/internal/repository/session"
|
2023-07-14 06:49:57 +00:00
|
|
|
"github.com/zitadel/zitadel/internal/repository/user"
|
2023-12-08 14:30:55 +00:00
|
|
|
"github.com/zitadel/zitadel/internal/zerrors"
|
2023-05-05 15:34:53 +00:00
|
|
|
)
|
|
|
|
|
2024-05-30 22:08:48 +00:00
|
|
|
type SessionCommand func(ctx context.Context, cmd *SessionCommands) ([]eventstore.Command, error)
|
2023-05-05 15:34:53 +00:00
|
|
|
|
2023-06-07 15:28:42 +00:00
|
|
|
type SessionCommands struct {
|
2023-07-14 06:49:57 +00:00
|
|
|
sessionCommands []SessionCommand
|
2023-05-05 15:34:53 +00:00
|
|
|
|
2024-05-30 22:08:48 +00:00
|
|
|
sessionWriteModel *SessionWriteModel
|
|
|
|
intentWriteModel *IDPIntentWriteModel
|
|
|
|
eventstore *eventstore.Eventstore
|
|
|
|
eventCommands []eventstore.Command
|
2023-07-14 06:49:57 +00:00
|
|
|
|
2024-09-26 07:14:33 +00:00
|
|
|
hasher *crypto.Hasher
|
|
|
|
intentAlg crypto.EncryptionAlgorithm
|
|
|
|
totpAlg crypto.EncryptionAlgorithm
|
|
|
|
otpAlg crypto.EncryptionAlgorithm
|
|
|
|
createCode encryptedCodeWithDefaultFunc
|
|
|
|
createPhoneCode encryptedCodeGeneratorWithDefaultFunc
|
|
|
|
createToken func(sessionID string) (id string, token string, err error)
|
|
|
|
getCodeVerifier func(ctx context.Context, id string) (senders.CodeGenerator, error)
|
|
|
|
now func() time.Time
|
2023-05-05 15:34:53 +00:00
|
|
|
}
|
|
|
|
|
2023-06-07 15:28:42 +00:00
|
|
|
func (c *Commands) NewSessionCommands(cmds []SessionCommand, session *SessionWriteModel) *SessionCommands {
|
|
|
|
return &SessionCommands{
|
2023-07-14 06:49:57 +00:00
|
|
|
sessionCommands: cmds,
|
2023-05-05 15:34:53 +00:00
|
|
|
sessionWriteModel: session,
|
|
|
|
eventstore: c.eventstore,
|
2023-07-14 06:49:57 +00:00
|
|
|
hasher: c.userPasswordHasher,
|
2023-06-21 14:06:18 +00:00
|
|
|
intentAlg: c.idpConfigEncryption,
|
2023-08-15 09:50:42 +00:00
|
|
|
totpAlg: c.multifactors.OTP.CryptoMFA,
|
2023-08-24 09:41:52 +00:00
|
|
|
otpAlg: c.userEncryption,
|
2024-04-05 09:35:49 +00:00
|
|
|
createCode: c.newEncryptedCodeWithDefault,
|
2024-09-26 07:14:33 +00:00
|
|
|
createPhoneCode: c.newPhoneCode,
|
2023-05-05 15:34:53 +00:00
|
|
|
createToken: c.sessionTokenCreator,
|
2024-10-07 05:12:44 +00:00
|
|
|
getCodeVerifier: c.phoneCodeVerifierFromConfig,
|
2023-05-05 15:34:53 +00:00
|
|
|
now: time.Now,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// CheckUser defines a user check to be executed for a session update
|
2024-05-16 05:07:56 +00:00
|
|
|
func CheckUser(id string, resourceOwner string, preferredLanguage *language.Tag) SessionCommand {
|
2024-05-30 22:08:48 +00:00
|
|
|
return func(ctx context.Context, cmd *SessionCommands) ([]eventstore.Command, error) {
|
2023-05-05 15:34:53 +00:00
|
|
|
if cmd.sessionWriteModel.UserID != "" && id != "" && cmd.sessionWriteModel.UserID != id {
|
2024-05-30 22:08:48 +00:00
|
|
|
return nil, zerrors.ThrowInvalidArgument(nil, "", "user change not possible")
|
2023-05-05 15:34:53 +00:00
|
|
|
}
|
2024-05-30 22:08:48 +00:00
|
|
|
return nil, cmd.UserChecked(ctx, id, resourceOwner, cmd.now(), preferredLanguage)
|
2023-05-05 15:34:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// CheckPassword defines a password check to be executed for a session update
|
2023-06-07 15:28:42 +00:00
|
|
|
func CheckPassword(password string) SessionCommand {
|
2024-05-30 22:08:48 +00:00
|
|
|
return func(ctx context.Context, cmd *SessionCommands) ([]eventstore.Command, error) {
|
|
|
|
commands, err := checkPassword(ctx, cmd.sessionWriteModel.UserID, password, cmd.eventstore, cmd.hasher, nil)
|
2023-05-05 15:34:53 +00:00
|
|
|
if err != nil {
|
2024-05-30 22:08:48 +00:00
|
|
|
return commands, err
|
2023-05-05 15:34:53 +00:00
|
|
|
}
|
2024-05-30 22:08:48 +00:00
|
|
|
cmd.eventCommands = append(cmd.eventCommands, commands...)
|
2023-07-14 06:49:57 +00:00
|
|
|
cmd.PasswordChecked(ctx, cmd.now())
|
2024-05-30 22:08:48 +00:00
|
|
|
return nil, nil
|
2023-05-05 15:34:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-21 14:06:18 +00:00
|
|
|
// CheckIntent defines a check for a succeeded intent to be executed for a session update
|
|
|
|
func CheckIntent(intentID, token string) SessionCommand {
|
2024-05-30 22:08:48 +00:00
|
|
|
return func(ctx context.Context, cmd *SessionCommands) ([]eventstore.Command, error) {
|
2023-06-21 14:06:18 +00:00
|
|
|
if cmd.sessionWriteModel.UserID == "" {
|
2024-05-30 22:08:48 +00:00
|
|
|
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Sfw3r", "Errors.User.UserIDMissing")
|
2023-06-21 14:06:18 +00:00
|
|
|
}
|
|
|
|
if err := crypto.CheckToken(cmd.intentAlg, token, intentID); err != nil {
|
2024-05-30 22:08:48 +00:00
|
|
|
return nil, err
|
2023-06-21 14:06:18 +00:00
|
|
|
}
|
|
|
|
cmd.intentWriteModel = NewIDPIntentWriteModel(intentID, "")
|
|
|
|
err := cmd.eventstore.FilterToQueryReducer(ctx, cmd.intentWriteModel)
|
|
|
|
if err != nil {
|
2024-05-30 22:08:48 +00:00
|
|
|
return nil, err
|
2023-06-21 14:06:18 +00:00
|
|
|
}
|
|
|
|
if cmd.intentWriteModel.State != domain.IDPIntentStateSucceeded {
|
2024-05-30 22:08:48 +00:00
|
|
|
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Df4bw", "Errors.Intent.NotSucceeded")
|
2023-06-21 14:06:18 +00:00
|
|
|
}
|
|
|
|
if cmd.intentWriteModel.UserID != "" {
|
|
|
|
if cmd.intentWriteModel.UserID != cmd.sessionWriteModel.UserID {
|
2024-05-30 22:08:48 +00:00
|
|
|
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-O8xk3w", "Errors.Intent.OtherUser")
|
2023-06-21 14:06:18 +00:00
|
|
|
}
|
|
|
|
} else {
|
2024-05-30 07:06:32 +00:00
|
|
|
linkWriteModel := NewUserIDPLinkWriteModel(cmd.sessionWriteModel.UserID, cmd.intentWriteModel.IDPID, cmd.intentWriteModel.IDPUserID, cmd.sessionWriteModel.UserResourceOwner)
|
2023-06-21 14:06:18 +00:00
|
|
|
err := cmd.eventstore.FilterToQueryReducer(ctx, linkWriteModel)
|
|
|
|
if err != nil {
|
2024-05-30 22:08:48 +00:00
|
|
|
return nil, err
|
2023-06-21 14:06:18 +00:00
|
|
|
}
|
|
|
|
if linkWriteModel.State != domain.UserIDPLinkStateActive {
|
2024-05-30 22:08:48 +00:00
|
|
|
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-O8xk3w", "Errors.Intent.OtherUser")
|
2023-06-21 14:06:18 +00:00
|
|
|
}
|
|
|
|
}
|
2023-07-14 06:49:57 +00:00
|
|
|
cmd.IntentChecked(ctx, cmd.now())
|
2024-05-30 22:08:48 +00:00
|
|
|
return nil, nil
|
2023-06-21 14:06:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-15 09:50:42 +00:00
|
|
|
func CheckTOTP(code string) SessionCommand {
|
2024-05-30 22:08:48 +00:00
|
|
|
return func(ctx context.Context, cmd *SessionCommands) (_ []eventstore.Command, err error) {
|
|
|
|
commands, err := checkTOTP(
|
|
|
|
ctx,
|
|
|
|
cmd.sessionWriteModel.UserID,
|
|
|
|
"",
|
|
|
|
code,
|
|
|
|
cmd.eventstore.FilterToQueryReducer,
|
|
|
|
cmd.totpAlg,
|
|
|
|
nil,
|
|
|
|
)
|
2023-08-15 09:50:42 +00:00
|
|
|
if err != nil {
|
2024-05-30 22:08:48 +00:00
|
|
|
return commands, err
|
2023-08-15 09:50:42 +00:00
|
|
|
}
|
2024-05-30 22:08:48 +00:00
|
|
|
cmd.eventCommands = append(cmd.eventCommands, commands...)
|
2023-08-15 09:50:42 +00:00
|
|
|
cmd.TOTPChecked(ctx, cmd.now())
|
2024-05-30 22:08:48 +00:00
|
|
|
return nil, nil
|
2023-08-15 09:50:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-30 22:08:48 +00:00
|
|
|
// Exec will execute the commands specified and returns an error on the first occurrence.
|
|
|
|
// In case of an error there might be specific commands returned, e.g. a failed pw check will have to be stored.
|
|
|
|
func (s *SessionCommands) Exec(ctx context.Context) ([]eventstore.Command, error) {
|
2023-07-14 06:49:57 +00:00
|
|
|
for _, cmd := range s.sessionCommands {
|
2024-05-30 22:08:48 +00:00
|
|
|
if cmds, err := cmd(ctx, s); err != nil {
|
|
|
|
return cmds, err
|
2023-05-05 15:34:53 +00:00
|
|
|
}
|
|
|
|
}
|
2024-05-30 22:08:48 +00:00
|
|
|
return nil, nil
|
2023-05-05 15:34:53 +00:00
|
|
|
}
|
|
|
|
|
2023-10-12 12:16:59 +00:00
|
|
|
func (s *SessionCommands) Start(ctx context.Context, userAgent *domain.UserAgent) {
|
|
|
|
s.eventCommands = append(s.eventCommands, session.NewAddedEvent(ctx, s.sessionWriteModel.aggregate, userAgent))
|
2023-07-14 06:49:57 +00:00
|
|
|
}
|
|
|
|
|
2024-05-16 05:07:56 +00:00
|
|
|
func (s *SessionCommands) UserChecked(ctx context.Context, userID, resourceOwner string, checkedAt time.Time, preferredLanguage *language.Tag) error {
|
|
|
|
s.eventCommands = append(s.eventCommands, session.NewUserCheckedEvent(ctx, s.sessionWriteModel.aggregate, userID, resourceOwner, checkedAt, preferredLanguage))
|
2023-07-14 06:49:57 +00:00
|
|
|
// set the userID so other checks can use it
|
|
|
|
s.sessionWriteModel.UserID = userID
|
2023-11-16 06:35:50 +00:00
|
|
|
s.sessionWriteModel.UserResourceOwner = resourceOwner
|
2023-07-14 06:49:57 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SessionCommands) PasswordChecked(ctx context.Context, checkedAt time.Time) {
|
|
|
|
s.eventCommands = append(s.eventCommands, session.NewPasswordCheckedEvent(ctx, s.sessionWriteModel.aggregate, checkedAt))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SessionCommands) IntentChecked(ctx context.Context, checkedAt time.Time) {
|
|
|
|
s.eventCommands = append(s.eventCommands, session.NewIntentCheckedEvent(ctx, s.sessionWriteModel.aggregate, checkedAt))
|
|
|
|
}
|
|
|
|
|
2023-08-11 15:36:18 +00:00
|
|
|
func (s *SessionCommands) WebAuthNChallenged(ctx context.Context, challenge string, allowedCrentialIDs [][]byte, userVerification domain.UserVerificationRequirement, rpid string) {
|
|
|
|
s.eventCommands = append(s.eventCommands, session.NewWebAuthNChallengedEvent(ctx, s.sessionWriteModel.aggregate, challenge, allowedCrentialIDs, userVerification, rpid))
|
2023-07-14 06:49:57 +00:00
|
|
|
}
|
|
|
|
|
2023-08-11 15:36:18 +00:00
|
|
|
func (s *SessionCommands) WebAuthNChecked(ctx context.Context, checkedAt time.Time, tokenID string, signCount uint32, userVerified bool) {
|
2023-07-14 06:49:57 +00:00
|
|
|
s.eventCommands = append(s.eventCommands,
|
2023-08-11 15:36:18 +00:00
|
|
|
session.NewWebAuthNCheckedEvent(ctx, s.sessionWriteModel.aggregate, checkedAt, userVerified),
|
2023-07-14 06:49:57 +00:00
|
|
|
)
|
2023-08-11 15:36:18 +00:00
|
|
|
if s.sessionWriteModel.WebAuthNChallenge.UserVerification == domain.UserVerificationRequirementRequired {
|
|
|
|
s.eventCommands = append(s.eventCommands,
|
|
|
|
user.NewHumanPasswordlessSignCountChangedEvent(ctx, s.sessionWriteModel.aggregate, tokenID, signCount),
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
s.eventCommands = append(s.eventCommands,
|
|
|
|
user.NewHumanU2FSignCountChangedEvent(ctx, s.sessionWriteModel.aggregate, tokenID, signCount),
|
|
|
|
)
|
|
|
|
}
|
2023-07-14 06:49:57 +00:00
|
|
|
}
|
|
|
|
|
2023-08-15 09:50:42 +00:00
|
|
|
func (s *SessionCommands) TOTPChecked(ctx context.Context, checkedAt time.Time) {
|
|
|
|
s.eventCommands = append(s.eventCommands, session.NewTOTPCheckedEvent(ctx, s.sessionWriteModel.aggregate, checkedAt))
|
|
|
|
}
|
|
|
|
|
2024-09-26 07:14:33 +00:00
|
|
|
func (s *SessionCommands) OTPSMSChallenged(ctx context.Context, code *crypto.CryptoValue, expiry time.Duration, returnCode bool, generatorID string) {
|
|
|
|
s.eventCommands = append(s.eventCommands, session.NewOTPSMSChallengedEvent(ctx, s.sessionWriteModel.aggregate, code, expiry, returnCode, generatorID))
|
2023-08-24 09:41:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SessionCommands) OTPSMSChecked(ctx context.Context, checkedAt time.Time) {
|
|
|
|
s.eventCommands = append(s.eventCommands, session.NewOTPSMSCheckedEvent(ctx, s.sessionWriteModel.aggregate, checkedAt))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SessionCommands) OTPEmailChallenged(ctx context.Context, code *crypto.CryptoValue, expiry time.Duration, returnCode bool, urlTmpl string) {
|
|
|
|
s.eventCommands = append(s.eventCommands, session.NewOTPEmailChallengedEvent(ctx, s.sessionWriteModel.aggregate, code, expiry, returnCode, urlTmpl))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SessionCommands) OTPEmailChecked(ctx context.Context, checkedAt time.Time) {
|
|
|
|
s.eventCommands = append(s.eventCommands, session.NewOTPEmailCheckedEvent(ctx, s.sessionWriteModel.aggregate, checkedAt))
|
|
|
|
}
|
|
|
|
|
2023-07-14 06:49:57 +00:00
|
|
|
func (s *SessionCommands) SetToken(ctx context.Context, tokenID string) {
|
2023-11-28 15:56:29 +00:00
|
|
|
// trigger activity log for session for user
|
2024-03-14 08:49:10 +00:00
|
|
|
activity.Trigger(ctx, s.sessionWriteModel.UserResourceOwner, s.sessionWriteModel.UserID, activity.SessionAPI, s.eventstore.FilterToQueryReducer)
|
2023-07-14 06:49:57 +00:00
|
|
|
s.eventCommands = append(s.eventCommands, session.NewTokenSetEvent(ctx, s.sessionWriteModel.aggregate, tokenID))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SessionCommands) ChangeMetadata(ctx context.Context, metadata map[string][]byte) {
|
|
|
|
var changed bool
|
|
|
|
for key, value := range metadata {
|
|
|
|
currentValue, exists := s.sessionWriteModel.Metadata[key]
|
|
|
|
|
|
|
|
if len(value) != 0 {
|
|
|
|
// if a value is provided, and it's not equal, change it
|
|
|
|
if !bytes.Equal(currentValue, value) {
|
|
|
|
s.sessionWriteModel.Metadata[key] = value
|
|
|
|
changed = true
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// if there's no / an empty value, we only need to remove it on existing entries
|
|
|
|
if exists {
|
|
|
|
delete(s.sessionWriteModel.Metadata, key)
|
|
|
|
changed = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if changed {
|
|
|
|
s.eventCommands = append(s.eventCommands, session.NewMetadataSetEvent(ctx, s.sessionWriteModel.aggregate, s.sessionWriteModel.Metadata))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-06 09:48:28 +00:00
|
|
|
func (s *SessionCommands) SetLifetime(ctx context.Context, lifetime time.Duration) error {
|
|
|
|
if lifetime < 0 {
|
2023-12-08 14:30:55 +00:00
|
|
|
return zerrors.ThrowInvalidArgument(nil, "COMMAND-asEG4", "Errors.Session.PositiveLifetime")
|
2023-11-06 09:48:28 +00:00
|
|
|
}
|
|
|
|
if lifetime == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
s.eventCommands = append(s.eventCommands, session.NewLifetimeSetEvent(ctx, s.sessionWriteModel.aggregate, lifetime))
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-06-07 15:28:42 +00:00
|
|
|
func (s *SessionCommands) gethumanWriteModel(ctx context.Context) (*HumanWriteModel, error) {
|
|
|
|
if s.sessionWriteModel.UserID == "" {
|
2023-12-08 14:30:55 +00:00
|
|
|
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-eeR2e", "Errors.User.UserIDMissing")
|
2023-06-07 15:28:42 +00:00
|
|
|
}
|
2023-11-16 06:35:50 +00:00
|
|
|
humanWriteModel := NewHumanWriteModel(s.sessionWriteModel.UserID, s.sessionWriteModel.UserResourceOwner)
|
2023-06-07 15:28:42 +00:00
|
|
|
err := s.eventstore.FilterToQueryReducer(ctx, humanWriteModel)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if humanWriteModel.UserState != domain.UserStateActive {
|
2023-12-08 14:30:55 +00:00
|
|
|
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Df4b3", "Errors.User.NotFound")
|
2023-06-07 15:28:42 +00:00
|
|
|
}
|
|
|
|
return humanWriteModel, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SessionCommands) commands(ctx context.Context) (string, []eventstore.Command, error) {
|
2023-07-14 06:49:57 +00:00
|
|
|
if len(s.eventCommands) == 0 {
|
2023-05-05 15:34:53 +00:00
|
|
|
return "", nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
tokenID, token, err := s.createToken(s.sessionWriteModel.AggregateID)
|
|
|
|
if err != nil {
|
|
|
|
return "", nil, err
|
|
|
|
}
|
2023-07-14 06:49:57 +00:00
|
|
|
s.SetToken(ctx, tokenID)
|
|
|
|
return token, s.eventCommands, nil
|
2023-05-05 15:34:53 +00:00
|
|
|
}
|
|
|
|
|
2023-11-06 09:48:28 +00:00
|
|
|
func (c *Commands) CreateSession(ctx context.Context, cmds []SessionCommand, metadata map[string][]byte, userAgent *domain.UserAgent, lifetime time.Duration) (set *SessionChanged, err error) {
|
2023-05-05 15:34:53 +00:00
|
|
|
sessionID, err := c.idGenerator.Next()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-11-16 06:35:50 +00:00
|
|
|
sessionWriteModel := NewSessionWriteModel(sessionID, authz.GetInstance(ctx).InstanceID())
|
2023-05-05 15:34:53 +00:00
|
|
|
err = c.eventstore.FilterToQueryReducer(ctx, sessionWriteModel)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-06-07 15:28:42 +00:00
|
|
|
cmd := c.NewSessionCommands(cmds, sessionWriteModel)
|
2023-10-12 12:16:59 +00:00
|
|
|
cmd.Start(ctx, userAgent)
|
2023-11-06 09:48:28 +00:00
|
|
|
return c.updateSession(ctx, cmd, metadata, lifetime)
|
2023-05-05 15:34:53 +00:00
|
|
|
}
|
|
|
|
|
2024-05-22 05:56:11 +00:00
|
|
|
func (c *Commands) UpdateSession(ctx context.Context, sessionID string, cmds []SessionCommand, metadata map[string][]byte, lifetime time.Duration) (set *SessionChanged, err error) {
|
2023-11-16 06:35:50 +00:00
|
|
|
sessionWriteModel := NewSessionWriteModel(sessionID, authz.GetInstance(ctx).InstanceID())
|
2023-05-05 15:34:53 +00:00
|
|
|
err = c.eventstore.FilterToQueryReducer(ctx, sessionWriteModel)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-06-07 15:28:42 +00:00
|
|
|
cmd := c.NewSessionCommands(cmds, sessionWriteModel)
|
2023-11-06 09:48:28 +00:00
|
|
|
return c.updateSession(ctx, cmd, metadata, lifetime)
|
2023-05-05 15:34:53 +00:00
|
|
|
}
|
|
|
|
|
2023-07-19 11:17:39 +00:00
|
|
|
func (c *Commands) TerminateSession(ctx context.Context, sessionID string, sessionToken string) (*domain.ObjectDetails, error) {
|
|
|
|
return c.terminateSession(ctx, sessionID, sessionToken, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Commands) TerminateSessionWithoutTokenCheck(ctx context.Context, sessionID string) (*domain.ObjectDetails, error) {
|
|
|
|
return c.terminateSession(ctx, sessionID, "", false)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Commands) terminateSession(ctx context.Context, sessionID, sessionToken string, mustCheckToken bool) (*domain.ObjectDetails, error) {
|
2023-11-16 06:35:50 +00:00
|
|
|
sessionWriteModel := NewSessionWriteModel(sessionID, authz.GetInstance(ctx).InstanceID())
|
2023-05-05 15:34:53 +00:00
|
|
|
if err := c.eventstore.FilterToQueryReducer(ctx, sessionWriteModel); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-07-19 11:17:39 +00:00
|
|
|
if mustCheckToken {
|
2023-11-16 06:35:50 +00:00
|
|
|
if err := c.checkSessionTerminationPermission(ctx, sessionWriteModel, sessionToken); err != nil {
|
2023-07-19 11:17:39 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2023-05-05 15:34:53 +00:00
|
|
|
}
|
2023-11-06 09:48:28 +00:00
|
|
|
if sessionWriteModel.CheckIsActive() != nil {
|
2023-05-05 15:34:53 +00:00
|
|
|
return writeModelToObjectDetails(&sessionWriteModel.WriteModel), nil
|
|
|
|
}
|
|
|
|
terminate := session.NewTerminateEvent(ctx, &session.NewAggregate(sessionWriteModel.AggregateID, sessionWriteModel.ResourceOwner).Aggregate)
|
|
|
|
pushedEvents, err := c.eventstore.Push(ctx, terminate)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
err = AppendAndReduce(sessionWriteModel, pushedEvents...)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return writeModelToObjectDetails(&sessionWriteModel.WriteModel), nil
|
|
|
|
}
|
|
|
|
|
2023-06-07 15:28:42 +00:00
|
|
|
// updateSession execute the [SessionCommands] where new events will be created and as well as for metadata (changes)
|
2023-11-06 09:48:28 +00:00
|
|
|
func (c *Commands) updateSession(ctx context.Context, checks *SessionCommands, metadata map[string][]byte, lifetime time.Duration) (set *SessionChanged, err error) {
|
|
|
|
if err = checks.sessionWriteModel.CheckNotInvalidated(); err != nil {
|
|
|
|
return nil, err
|
2023-05-05 15:34:53 +00:00
|
|
|
}
|
2024-05-30 22:08:48 +00:00
|
|
|
if cmds, err := checks.Exec(ctx); err != nil {
|
|
|
|
if len(cmds) > 0 {
|
|
|
|
_, pushErr := c.eventstore.Push(ctx, cmds...)
|
|
|
|
logging.OnError(pushErr).Error("unable to store check failures")
|
|
|
|
}
|
2023-05-05 15:34:53 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2023-07-14 06:49:57 +00:00
|
|
|
checks.ChangeMetadata(ctx, metadata)
|
2023-11-06 09:48:28 +00:00
|
|
|
err = checks.SetLifetime(ctx, lifetime)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-05-05 15:34:53 +00:00
|
|
|
sessionToken, cmds, err := checks.commands(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if len(cmds) == 0 {
|
|
|
|
return sessionWriteModelToSessionChanged(checks.sessionWriteModel), nil
|
|
|
|
}
|
|
|
|
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
err = AppendAndReduce(checks.sessionWriteModel, pushedEvents...)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
changed := sessionWriteModelToSessionChanged(checks.sessionWriteModel)
|
|
|
|
changed.NewToken = sessionToken
|
|
|
|
return changed, nil
|
|
|
|
}
|
|
|
|
|
2023-11-16 06:35:50 +00:00
|
|
|
// checkSessionTerminationPermission will check that the provided sessionToken is correct or
|
|
|
|
// if empty, check that the caller is either terminating the own session or
|
|
|
|
// is granted the "session.delete" permission on the resource owner of the authenticated user.
|
|
|
|
func (c *Commands) checkSessionTerminationPermission(ctx context.Context, model *SessionWriteModel, token string) error {
|
|
|
|
if token != "" {
|
|
|
|
return c.sessionTokenVerifier(ctx, token, model.AggregateID, model.TokenID)
|
2023-05-05 15:34:53 +00:00
|
|
|
}
|
2023-11-16 06:35:50 +00:00
|
|
|
if model.UserID != "" && model.UserID == authz.GetCtxData(ctx).UserID {
|
|
|
|
return nil
|
|
|
|
}
|
2023-12-09 08:59:51 +00:00
|
|
|
userResourceOwner, err := c.sessionUserResourceOwner(ctx, model)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return c.checkPermission(ctx, domain.PermissionSessionDelete, userResourceOwner, model.UserID)
|
|
|
|
}
|
|
|
|
|
|
|
|
// sessionUserResourceOwner will return the resourceOwner of the session form the [SessionWriteModel] or by additionally calling the eventstore,
|
|
|
|
// because before 2.42.0, the resourceOwner of a session used to be the organisation of the creator.
|
|
|
|
// Further the (checked) users organisation id was not stored.
|
|
|
|
// To be able to check the permission, we need to get the user's resourceOwner in this case.
|
|
|
|
func (c *Commands) sessionUserResourceOwner(ctx context.Context, model *SessionWriteModel) (string, error) {
|
|
|
|
if model.UserID == "" || model.UserResourceOwner != "" {
|
|
|
|
return model.UserResourceOwner, nil
|
|
|
|
}
|
|
|
|
r := NewResourceOwnerModel(authz.GetInstance(ctx).InstanceID(), user.AggregateType, model.UserID)
|
|
|
|
err := c.eventstore.FilterToQueryReducer(ctx, r)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return r.resourceOwner, nil
|
2023-05-05 15:34:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func sessionTokenCreator(idGenerator id.Generator, sessionAlg crypto.EncryptionAlgorithm) func(sessionID string) (id string, token string, err error) {
|
|
|
|
return func(sessionID string) (id string, token string, err error) {
|
|
|
|
id, err = idGenerator.Next()
|
|
|
|
if err != nil {
|
|
|
|
return "", "", err
|
|
|
|
}
|
|
|
|
encrypted, err := sessionAlg.Encrypt([]byte(fmt.Sprintf(authz.SessionTokenFormat, sessionID, id)))
|
|
|
|
if err != nil {
|
|
|
|
return "", "", err
|
|
|
|
}
|
|
|
|
return id, base64.RawURLEncoding.EncodeToString(encrypted), nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type SessionChanged struct {
|
|
|
|
*domain.ObjectDetails
|
|
|
|
ID string
|
|
|
|
NewToken string
|
|
|
|
}
|
|
|
|
|
|
|
|
func sessionWriteModelToSessionChanged(wm *SessionWriteModel) *SessionChanged {
|
|
|
|
return &SessionChanged{
|
|
|
|
ObjectDetails: &domain.ObjectDetails{
|
|
|
|
Sequence: wm.ProcessedSequence,
|
|
|
|
EventDate: wm.ChangeDate,
|
|
|
|
ResourceOwner: wm.ResourceOwner,
|
|
|
|
},
|
|
|
|
ID: wm.AggregateID,
|
|
|
|
}
|
|
|
|
}
|