mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-31 19:42:15 +00:00
* Add DenyLists parsing * Remove unneeded returned error * Plug global denylist into Command * app creation: apply denylist to backchannel logout URI * Inject denylist to backchannel logout worker * webhook config: validate against blocked URLs * Add notificationsWebhook denylist target * command: Add SMTP endpoint validation against blocklist * command: Add SMS endpoint validation against blocklist * Validate webhook endpoint against denylist on channel notification * Remove unused tests * handle deprecated denylists * remove unintended denylist entry in deprecated list * use single http client * fix tests * update comments * fixes * cleanup * address comments * fix merge --------- Co-authored-by: Livio Spring <[email protected]>
166 lines
5.1 KiB
Go
166 lines
5.1 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/riverqueue/river"
|
|
"github.com/zitadel/oidc/v3/pkg/crypto"
|
|
"github.com/zitadel/oidc/v3/pkg/oidc"
|
|
|
|
"github.com/zitadel/zitadel/internal/api/oidc/sign"
|
|
"github.com/zitadel/zitadel/internal/eventstore"
|
|
"github.com/zitadel/zitadel/internal/id"
|
|
"github.com/zitadel/zitadel/internal/notification/backchannel"
|
|
"github.com/zitadel/zitadel/internal/notification/channels"
|
|
"github.com/zitadel/zitadel/internal/notification/channels/set"
|
|
"github.com/zitadel/zitadel/internal/notification/types"
|
|
"github.com/zitadel/zitadel/internal/queue"
|
|
)
|
|
|
|
type BackChannelLogoutWorker struct {
|
|
river.WorkerDefaults[*backchannel.LogoutRequest]
|
|
|
|
commands Commands
|
|
queries *NotificationQueries
|
|
eventstore *eventstore.Eventstore
|
|
queue Queue
|
|
channels types.ChannelChains
|
|
config *BackChannelLogoutWorkerConfig
|
|
now nowFunc
|
|
idGenerator id.Generator
|
|
httpClient *http.Client
|
|
}
|
|
|
|
// Timeout implements the Timeout-function of [river.Worker].
|
|
// Maximum time a job can run before the context gets canceled.
|
|
func (w *BackChannelLogoutWorker) Timeout(*river.Job[*backchannel.LogoutRequest]) time.Duration {
|
|
return w.config.TransactionDuration
|
|
}
|
|
|
|
// Work implements [river.Worker].
|
|
func (w *BackChannelLogoutWorker) Work(ctx context.Context, job *river.Job[*backchannel.LogoutRequest]) error {
|
|
ctx = ContextWithNotifier(ctx, job.Args.Aggregate)
|
|
|
|
ctx, err := enrichCtx(ctx, job.Args.TriggeredAtOrigin)
|
|
if err != nil {
|
|
return channels.NewCancelError(err)
|
|
}
|
|
|
|
// if the notification is too old, we can directly cancel
|
|
if job.CreatedAt.Add(w.config.MaxTtl).Before(w.now()) {
|
|
return river.JobCancel(errors.New("back channel logout notification is too old"))
|
|
}
|
|
|
|
if job.Args.OIDCSessionID == "" {
|
|
return w.createNotificationJobs(ctx, job.Args)
|
|
}
|
|
return w.sendLogoutRequest(ctx, job.Args)
|
|
}
|
|
|
|
func (w *BackChannelLogoutWorker) createNotificationJobs(ctx context.Context, request *backchannel.LogoutRequest) error {
|
|
sessions := &backChannelLogoutSession{sessionID: request.SessionID}
|
|
err := w.eventstore.FilterToQueryReducer(ctx, sessions)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, oidcSession := range sessions.sessions {
|
|
tokenID, err := w.idGenerator.Next()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
logoutRequest := &backchannel.LogoutRequest{
|
|
Aggregate: request.Aggregate,
|
|
SessionID: request.SessionID,
|
|
TriggeredAtOrigin: request.TriggeredAtOrigin,
|
|
TriggeringEventType: request.TriggeringEventType,
|
|
TokenID: tokenID,
|
|
UserID: oidcSession.UserID,
|
|
OIDCSessionID: oidcSession.OIDCSessionID,
|
|
ClientID: oidcSession.ClientID,
|
|
BackChannelLogoutURI: oidcSession.BackChannelLogoutURI,
|
|
}
|
|
err = w.queue.Insert(ctx, logoutRequest,
|
|
queue.WithQueueName(backchannel.QueueName),
|
|
queue.WithMaxAttempts(w.config.MaxAttempts))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (w *BackChannelLogoutWorker) sendLogoutRequest(ctx context.Context, request *backchannel.LogoutRequest) error {
|
|
getSigner := sign.GetSignerOnce(w.queries.GetActiveSigningWebKey)
|
|
token, err := w.logoutToken(ctx, request, getSigner)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
backchannelNotifCfg := set.Config{CallURL: request.BackChannelLogoutURI, Client: w.httpClient}
|
|
if err = types.SendSecurityTokenEvent(ctx, backchannelNotifCfg, w.channels, &LogoutTokenMessage{LogoutToken: token}, request.TriggeringEventType).WithoutTemplate(); err != nil {
|
|
return err
|
|
}
|
|
return w.commands.BackChannelLogoutSent(ctx, request.SessionID, request.OIDCSessionID, request.Aggregate.InstanceID)
|
|
}
|
|
|
|
func (w *BackChannelLogoutWorker) logoutToken(ctx context.Context, request *backchannel.LogoutRequest, getSigner sign.SignerFunc) (string, error) {
|
|
token := oidc.NewLogoutTokenClaims(
|
|
request.TriggeredAtOrigin,
|
|
request.UserID,
|
|
oidc.Audience{request.ClientID},
|
|
w.now().Add(w.config.TokenLifetime),
|
|
request.TokenID,
|
|
request.SessionID,
|
|
time.Second,
|
|
)
|
|
signer, _, err := getSigner(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return crypto.Sign(token, signer)
|
|
}
|
|
|
|
func NewBackChannelLogoutWorker(
|
|
commands Commands,
|
|
queries *NotificationQueries,
|
|
eventstore *eventstore.Eventstore,
|
|
queue Queue,
|
|
channels types.ChannelChains,
|
|
config *BackChannelLogoutWorkerConfig,
|
|
|
|
idGenerator id.Generator,
|
|
httpClient *http.Client,
|
|
) *BackChannelLogoutWorker {
|
|
return &BackChannelLogoutWorker{
|
|
commands: commands,
|
|
queries: queries,
|
|
eventstore: eventstore,
|
|
queue: queue,
|
|
channels: channels,
|
|
config: config,
|
|
now: time.Now,
|
|
idGenerator: idGenerator,
|
|
httpClient: httpClient,
|
|
}
|
|
}
|
|
|
|
var _ river.Worker[*backchannel.LogoutRequest] = (*BackChannelLogoutWorker)(nil)
|
|
|
|
func (w *BackChannelLogoutWorker) Register(workers *river.Workers, queues map[string]river.QueueConfig) {
|
|
river.AddWorker(workers, w)
|
|
queues[backchannel.QueueName] = river.QueueConfig{
|
|
MaxWorkers: int(w.config.Workers),
|
|
}
|
|
}
|
|
|
|
type BackChannelLogoutWorkerConfig struct {
|
|
Workers uint8
|
|
TransactionDuration time.Duration
|
|
MaxAttempts uint8
|
|
MaxTtl time.Duration
|
|
TokenLifetime time.Duration
|
|
}
|