Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions internal/api/session_delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func DeleteSession(router *gin.RouterGroup) {
// On the caller's own logout (not a manager deleting by ref id), end the provider
// session too when PHOTOPRISM_OIDC_LOGOUT is enabled.
if conf := get.Config(); !rnd.IsRefID(id) {
if logoutUri := oidcLogoutURL(conf, get.OIDC(), s); logoutUri != "" {
if logoutUri := oidcLogoutURL(conf, get.OIDC, s); logoutUri != "" {
// Delegate to the provider's end-session endpoint (the Portal OP in a cluster),
// which clears the OP session cookie itself — clearing it here would strip the
// cookie the Portal OP needs to resolve the session and chain the upstream logout.
Expand All @@ -115,14 +115,21 @@ func DeleteSession(router *gin.RouterGroup) {
// oidcLogoutURL returns the RP-initiated logout URL for a just-deleted OIDC session, or ""
// when logout is disabled, the session was not OIDC, or the provider advertises no
// end_session_endpoint. The browser is redirected there to end the provider SSO session.
func oidcLogoutURL(conf *config.Config, provider *oidc.Client, s *entity.Session) string {
func oidcLogoutURL(conf *config.Config, provider func() *oidc.Client, s *entity.Session) string {
if conf == nil || provider == nil || s == nil {
return ""
} else if !conf.OIDCLogout() || s.IdToken == "" || !s.GetProvider().IsOIDC() {
return ""
}

logoutUri, err := provider.EndSessionURL(s.IdToken, AbsoluteLoginURL(conf), "")
// Resolved only here, since resolving the client may contact the provider.
client := provider()

if client == nil {
return ""
}

logoutUri, err := client.EndSessionURL(s.IdToken, AbsoluteLoginURL(conf), "")

if err != nil {
event.AuditWarn([]string{"oidc", "provider logout", status.Error(err)})
Expand Down
24 changes: 22 additions & 2 deletions internal/api/session_delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func oidcSession(provider authn.ProviderType, idToken string) *entity.Session {

func TestOidcLogoutURL(t *testing.T) {
conf := config.NewConfig(config.CliTestContext())
provider := oidcProvider("https://provider.example/logout")
provider := func() *oidc.Client { return oidcProvider("https://provider.example/logout") }

t.Run("Success", func(t *testing.T) {
conf.Options().OIDCLogout = true
Expand Down Expand Up @@ -74,12 +74,32 @@ func TestOidcLogoutURL(t *testing.T) {
t.Run("ProviderWithoutEndSession", func(t *testing.T) {
conf.Options().OIDCLogout = true
defer func() { conf.Options().OIDCLogout = false }()
assert.Equal(t, "", oidcLogoutURL(conf, oidcProvider(""), oidcSession(authn.ProviderOIDC, "id-token-123")))
assert.Equal(t, "", oidcLogoutURL(conf, func() *oidc.Client { return oidcProvider("") }, oidcSession(authn.ProviderOIDC, "id-token-123")))
})
t.Run("NilProvider", func(t *testing.T) {
conf.Options().OIDCLogout = true
defer func() { conf.Options().OIDCLogout = false }()
assert.Equal(t, "", oidcLogoutURL(conf, nil, oidcSession(authn.ProviderOIDC, "id-token-123")))
assert.Equal(t, "", oidcLogoutURL(conf, func() *oidc.Client { return nil }, oidcSession(authn.ProviderOIDC, "id-token-123")))
})
t.Run("ProviderResolvedOnlyWhenNeeded", func(t *testing.T) {
calls := 0
counted := func() *oidc.Client {
calls++
return oidcProvider("https://provider.example/logout")
}

conf.Options().OIDCLogout = false
assert.Equal(t, "", oidcLogoutURL(conf, counted, oidcSession(authn.ProviderOIDC, "id-token-123")))

conf.Options().OIDCLogout = true
defer func() { conf.Options().OIDCLogout = false }()
assert.Equal(t, "", oidcLogoutURL(conf, counted, oidcSession(authn.ProviderLocal, "")))
assert.Equal(t, "", oidcLogoutURL(conf, counted, oidcSession(authn.ProviderOIDC, "")))
assert.Equal(t, 0, calls, "the provider is not resolved for sessions that end without it")

assert.NotEmpty(t, oidcLogoutURL(conf, counted, oidcSession(authn.ProviderOIDC, "id-token-123")))
assert.Equal(t, 1, calls)
})
}

Expand Down
Loading