Skip to content
Open
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
12 changes: 9 additions & 3 deletions consent/strategy_default.go
Original file line number Diff line number Diff line change
Expand Up @@ -713,14 +713,20 @@ func (s *defaultStrategy) executeBackChannelLogout(ctx context.Context, r *http.
// s.r.ConsentManager().GetForcedObfuscatedLoginSession(context.Background(), subject, <missing>)
// sub := s.obfuscateSubjectIdentifier(c, subject, )

t, _, err := s.r.OpenIDJWTSigner().Generate(ctx, jwt.MapClaims{
now := time.Now().UTC()
claims := jwt.MapClaims{
"iss": s.r.Config().IssuerURL(ctx).String(),
"aud": []string{c.ID},
"iat": time.Now().UTC().Unix(),
"iat": now.Unix(),
"jti": uuid.New(),
"events": map[string]struct{}{"http://schemas.openid.net/event/backchannel-logout": {}},
"sid": sid,
}, &jwt.Headers{
}
if logoutTokenLifespan := s.r.Config().GetLogoutTokenLifespan(ctx); logoutTokenLifespan > 0 {
claims["exp"] = now.Add(logoutTokenLifespan).Unix()
}

t, _, err := s.r.OpenIDJWTSigner().Generate(ctx, claims, &jwt.Headers{
Extra: map[string]interface{}{"kid": openIDKeyID},
})
if err != nil {
Expand Down
31 changes: 31 additions & 0 deletions consent/strategy_logout_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ func TestLogoutFlows(t *testing.T) {
assert.EqualValues(t, <-sid, logoutToken.Get("sid").String(), logoutToken.Raw)
assert.Empty(t, logoutToken.Get("sub").String(), logoutToken.Raw) // The sub claim should be empty because it doesn't work with forced obfuscation and thus we can't easily recover it.
assert.Empty(t, logoutToken.Get("nonce").String(), logoutToken.Raw)
assert.False(t, logoutToken.Get("exp").Exists(), "exp claim should not be present when ttl.logout_token is not set")
})

t.Run("method=get", testExpectPostLogoutPage(createBrowserWithSession(t, c), http.MethodGet, url.Values{}, defaultRedirectedMessage))
Expand All @@ -342,6 +343,36 @@ func TestLogoutFlows(t *testing.T) {
backChannelWG.Wait() // we want to ensure that all back channels have been called!
})

t.Run("case=should include exp claim in logout token when ttl.logout_token is set", func(t *testing.T) {
require.NoError(t, reg.Config().Source(ctx).Set(config.KeyLogoutTokenLifespan, "2m"))
t.Cleanup(func() {
require.NoError(t, reg.Config().Source(ctx).Set(config.KeyLogoutTokenLifespan, "0s"))
})

sid := acceptLoginAsAndWatchSid(t, subject)

logoutWg := newWg(2)
setupCheckAndAcceptLogoutHandler(t, logoutWg, nil)

backChannelWG := newWg(2)
c := createClientWithBackchannelLogout(t, backChannelWG, func(t *testing.T, logoutToken gjson.Result) {
assert.EqualValues(t, <-sid, logoutToken.Get("sid").String(), logoutToken.Raw)
assert.True(t, logoutToken.Get("exp").Exists(), "exp claim should be present when ttl.logout_token is set")

iat := logoutToken.Get("iat").Int()
exp := logoutToken.Get("exp").Int()
diff := exp - iat
assert.InDelta(t, 120, diff, 2, "exp should be approximately iat + 120 seconds")
})

t.Run("method=get", testExpectPostLogoutPage(createBrowserWithSession(t, c), http.MethodGet, url.Values{}, defaultRedirectedMessage))

t.Run("method=post", testExpectPostLogoutPage(createBrowserWithSession(t, c), http.MethodPost, url.Values{}, defaultRedirectedMessage))

logoutWg.Wait()
backChannelWG.Wait()
})

// Only do GET requests from here on out, POST should be tested enough to ensure that it is working fine already.

t.Run("case=should fail several flows when id_token_hint is invalid", func(t *testing.T) {
Expand Down
6 changes: 6 additions & 0 deletions driver/config/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ const (
KeyIDTokenLifespan = "ttl.id_token" // #nosec G101
KeyAuthCodeLifespan = "ttl.auth_code"
KeyDeviceAndUserCodeLifespan = "ttl.device_user_code"
KeyLogoutTokenLifespan = "ttl.logout_token" // #nosec G101
KeyAuthenticationSessionLifespan = "ttl.authentication_session"
KeyScopeStrategy = "strategies.scope"
KeyGetCookieSecrets = "secrets.cookie"
Expand Down Expand Up @@ -429,6 +430,11 @@ func (p *DefaultProvider) GetDeviceAndUserCodeLifespan(ctx context.Context) time
return p.p.DurationF(KeyDeviceAndUserCodeLifespan, time.Minute*15)
}

// GetLogoutTokenLifespan returns the logout_token lifespan. Defaults to 0 (no exp claim).
func (p *DefaultProvider) GetLogoutTokenLifespan(ctx context.Context) time.Duration {
return p.p.DurationF(KeyLogoutTokenLifespan, 0)
}

// GetAuthenticationSessionLifespan returns the authentication_session lifespan.
func (p *DefaultProvider) GetAuthenticationSessionLifespan(ctx context.Context) time.Duration {
lifespan := p.p.Duration(KeyAuthenticationSessionLifespan)
Expand Down
3 changes: 2 additions & 1 deletion oryx/httpx/ssrf.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ import (
"time"

"github.com/gobwas/glob"
"github.com/ory/x/ipx"
"go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"

"github.com/ory/x/ipx"
)

var _ http.RoundTripper = (*noInternalIPRoundTripper)(nil)
Expand Down
3 changes: 2 additions & 1 deletion oryx/otelx/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import (
"net/http"
"strings"

"github.com/ory/x/httprouterx"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"

"github.com/ory/x/httprouterx"
)

var withDefaultFilters = otelhttp.WithFilter(func(r *http.Request) bool {
Expand Down
3 changes: 2 additions & 1 deletion oryx/prometheusx/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ import (
"time"

grpcPrometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
"github.com/ory/x/httprouterx"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/urfave/negroni"

"github.com/ory/x/httprouterx"
)

type HTTPMetrics struct {
Expand Down
3 changes: 2 additions & 1 deletion oryx/region/region.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ package region
import (
"database/sql/driver"

"github.com/ory/herodot"
"github.com/pkg/errors"

"github.com/ory/herodot"
)

// Region is an Ory Network region. Specific regions map to a single CRDB
Expand Down
2 changes: 1 addition & 1 deletion oryx/watcherx/directory.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ type directoryWatcher struct {
// potentially by external callers (e.g. tests) concurrently.
subDirsMtx sync.RWMutex
subDirs map[string]struct{}
w *fsnotify.Watcher
w *fsnotify.Watcher
}

func (w *directoryWatcher) handleEvent(ctx context.Context, e fsnotify.Event) {
Expand Down
9 changes: 9 additions & 0 deletions spec/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,15 @@
"$ref": "#/definitions/duration"
}
]
},
"logout_token": {
"description": "Configures how long logout tokens are valid. If set to \"0s\" (zero seconds) or left unset, no exp claim will be added to the logout token (preserving backward compatibility). The value must use the duration string format. The OpenID Connect Back-Channel Logout specification recommends a value of at most two minutes (e.g. \"2m\").",
"type": "string",
"allOf": [
{
"$ref": "#/definitions/duration"
}
]
}
}
},
Expand Down
Loading