Skip to content

Commit

Permalink
vendor: Upgrade to fosite 0.28.0
Browse files Browse the repository at this point in the history
This patch enables refresh token expiry.

Closes #1088

Signed-off-by: arekkas <[email protected]>
  • Loading branch information
arekkas committed Nov 16, 2018
1 parent e42e7be commit f84faf6
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 8 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ able to securely manage JSON Web Keys.
- [Building from source](#building-from-source)
- [Ecosystem](#ecosystem)
- [ORY Security Console: Administrative User Interface](#ory-security-console-administrative-user-interface)
- [ORY Oathkeeper: Identity & Access Proxy](#ory-oathkeeper-identity-&-access-proxy)
- [ORY Oathkeeper: Identity & Access Proxy](#ory-oathkeeper-identity--access-proxy)
- [ORY Keto: Access Control Policies as a Server](#ory-keto-access-control-policies-as-a-server)
- [Examples](#examples)
- [Security](#security)
Expand All @@ -62,7 +62,7 @@ able to securely manage JSON Web Keys.
- [Command line documentation](#command-line-documentation)
- [Develop](#develop)
- [Libraries and third-party projects](#libraries-and-third-party-projects)
- [Blog posts & articles](#blog-posts-&-articles)
- [Blog posts & articles](#blog-posts--articles)
- [Contributors](#contributors)
- [Backers](#backers)
- [Sponsors](#sponsors)
Expand Down
28 changes: 24 additions & 4 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,17 @@ before finalizing the upgrade process.
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->


- [1.0.0-beta.10](#100-beta10)
- [Schema Changes](#schema-changes)
- [1.0.0-rc.1](#100-rc1)
- [Non-breaking Changes](#non-breaking-changes)
- [Access Token Audience](#access-token-audience)
- [Refresh Grant](#refresh-grant)
- [Customise login and consent flow timeout](#customise-login-and-consent-flow-timeout)
- [Schema Changes](#schema-changes)
- [Breaking Changes](#breaking-changes)
- [Refresh Token Expiry](#refresh-token-expiry)
- [JSON Web Token formatted Access Token data](#json-web-token-formatted-access-token-data)
- [CLI Changes](#cli-changes)
- [API Changes](#api-changes)
- [1.0.0-beta.9](#100-beta9)
- [CORS is disabled by default](#cors-is-disabled-by-default)
- [1.0.0-beta.8](#100-beta8)
Expand All @@ -31,7 +40,7 @@ before finalizing the upgrade process.
- [1.0.0-beta.1](#100-beta1)
- [Upgrading from versions v0.9.x](#upgrading-from-versions-v09x)
- [OpenID Connect Certified](#openid-connect-certified)
- [Breaking Changes](#breaking-changes)
- [Breaking Changes](#breaking-changes-1)
- [Introspection API](#introspection-api)
- [Introspection is now capable of introspecting refresh tokens](#introspection-is-now-capable-of-introspecting-refresh-tokens)
- [Access Control & Warden API](#access-control--warden-api)
Expand Down Expand Up @@ -62,7 +71,7 @@ before finalizing the upgrade process.
- [0.11.3](#0113)
- [0.11.0](#0110)
- [0.10.0](#0100)
- [Breaking Changes](#breaking-changes-1)
- [Breaking Changes](#breaking-changes-2)
- [Introspection now requires authorization](#introspection-now-requires-authorization)
- [New consent flow](#new-consent-flow)
- [Audience](#audience)
Expand Down Expand Up @@ -134,6 +143,17 @@ In order to [resolve table locking](https://github.com/ory/hydra/issues/1067) wh

### Breaking Changes

#### Refresh Token Expiry

All refresh tokens issued with this release will expire after 30 days of non-use. This behaviour can be modified
using the `REFRESH_TOKEN_LIFESPAN` environment variable. By setting `REFRESH_TOKEN_LIFESPAN=-1`, refresh tokens
are set to never expire, which is the previous behaviour.

Tokens issued before this change will still be valid forever.

We discourage setting `REFRESH_TOKEN_LIFESPAN=-1` as it might clog the database with tokens that will never be used again.
In high-scale systems, `REFRESH_TOKEN_LIFESPAN` should be set to something like 15 or 30 days.

#### JSON Web Token formatted Access Token data

Previously, extra fields coming from `session.access_token` where directly embedded in the OAuth 2.0 Access Token when
Expand Down
3 changes: 3 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ func initConfig() {
viper.BindEnv("OAUTH2_SHARE_ERROR_DEBUG")
viper.SetDefault("OAUTH2_SHARE_ERROR_DEBUG", false)

viper.BindEnv("REFRESH_TOKEN_LIFESPAN")
viper.SetDefault("REFRESH_TOKEN_LIFESPAN", "720h")

viper.BindEnv("ACCESS_TOKEN_LIFESPAN")
viper.SetDefault("ACCESS_TOKEN_LIFESPAN", "1h")

Expand Down
6 changes: 5 additions & 1 deletion cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,13 @@ OAUTH2 CONTROLS
- ID_TOKEN_LIFESPAN: Lifespan of OpenID Connect ID Tokens. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
Defaults to ID_TOKEN_LIFESPAN=1h
- ACCESS_TOKEN_LIFESPAN: Lifespan of OAuth2 access tokens. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
- ACCESS_TOKEN_LIFESPAN: Lifespan of OAuth2 Access Tokens. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
Defaults to ACCESS_TOKEN_LIFESPAN=1h
- REFRESH_TOKEN_LIFESPAN: Lifespan of OAuth2 Refresh Tokens. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
Set REFRESH_TOKEN_LIFESPAN=-1 to disable refresh token expiry (not recommended for high traffic environments).
Defaults to REFRESH_TOKEN_LIFESPAN=720h
- LOGIN_CONSENT_REQUEST_LIFESPAN: Maximum lifespan of a login and consent request. This specifies the maximum time a user
may take to complete the login and consent flow, before that requests times out and results in an error. Valid time
units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
Expand Down
1 change: 1 addition & 0 deletions cmd/server/handler_oauth2_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ func newOAuth2Provider(c *config.Config) fosite.OAuth2Provider {

fc := &compose.Config{
AccessTokenLifespan: c.GetAccessTokenLifespan(),
RefreshTokenLifespan: c.GetRefreshTokenLifespan(),
AuthorizeCodeLifespan: c.GetAuthCodeLifespan(),
IDTokenLifespan: c.GetIDTokenLifespan(),
IDTokenIssuer: c.Issuer,
Expand Down
15 changes: 15 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ type Config struct {
AccessTokenLifespan string `mapstructure:"ACCESS_TOKEN_LIFESPAN" yaml:"-"`
ScopeStrategy string `mapstructure:"SCOPE_STRATEGY" yaml:"-"`
AuthCodeLifespan string `mapstructure:"AUTH_CODE_LIFESPAN" yaml:"-"`
RefreshTokenLifespan string `mapstructure:"REFRESH_TOKEN_LIFESPAN" yaml:"-"`
IDTokenLifespan string `mapstructure:"ID_TOKEN_LIFESPAN" yaml:"-"`
LoginConsentRequestLifespan string `mapstructure:"LOGIN_CONSENT_REQUEST_LIFESPAN" yaml:"-"`
CookieSecret string `mapstructure:"COOKIE_SECRET" yaml:"-"`
Expand Down Expand Up @@ -279,6 +280,20 @@ func (c *Config) GetAccessTokenLifespan() time.Duration {
return d
}

func (c *Config) GetRefreshTokenLifespan() time.Duration {
if c.RefreshTokenLifespan == "-1" {
return 0
}

d, err := time.ParseDuration(c.RefreshTokenLifespan)
if err != nil {
c.GetLogger().Warnf("Could not parse refresh token lifespan value (%s). Defaulting to 720h", c.RefreshTokenLifespan)
return time.Hour * 720
}

return d
}

func (c *Config) GetAuthCodeLifespan() time.Duration {
d, err := time.ParseDuration(c.AuthCodeLifespan)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ require (
github.com/oleiade/reflections v1.0.0
github.com/opentracing/opentracing-go v1.0.2
github.com/ory/dockertest v3.3.2+incompatible
github.com/ory/fosite v0.27.4
github.com/ory/fosite v0.28.0
github.com/ory/go-convenience v0.1.0
github.com/ory/graceful v0.1.0
github.com/ory/herodot v0.4.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ github.com/ory/fosite v0.27.3 h1:+LekWjYNN9jZW6MMOan84Y4I6wFshOZEHCxJo16p/QY=
github.com/ory/fosite v0.27.3/go.mod h1:uttCRNB0lM7+BJFX7CC8Bqo9gAPrcpmA9Ezc80Trwuw=
github.com/ory/fosite v0.27.4 h1:+2Iu957COQM3vbWp5qjgq0W4icsjbtg+5y3AYJ87EjY=
github.com/ory/fosite v0.27.4/go.mod h1:uttCRNB0lM7+BJFX7CC8Bqo9gAPrcpmA9Ezc80Trwuw=
github.com/ory/fosite v0.28.0 h1:LxCkLXeU5PxYh9d/VbfGVn8GTKkSdOZfrHWdjmIE//c=
github.com/ory/fosite v0.28.0/go.mod h1:uttCRNB0lM7+BJFX7CC8Bqo9gAPrcpmA9Ezc80Trwuw=
github.com/ory/go-convenience v0.1.0 h1:zouLKfF2GoSGnJwGq+PE/nJAE6dj2Zj5QlTgmMTsTS8=
github.com/ory/go-convenience v0.1.0/go.mod h1:uEY/a60PL5c12nYz4V5cHY03IBmwIAEm8TWB0yn9KNs=
github.com/ory/graceful v0.1.0 h1:zilpYtcR5vp4GubV4bN2GFJewHaSkMFnnRiJxyH8FAc=
Expand Down
110 changes: 110 additions & 0 deletions oauth2/oauth2_auth_code_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ func TestAuthCodeWithDefaultStrategy(t *testing.T) {
require.NoError(t, jm.AddKeySet(context.TODO(), OpenIDConnectKeyName, keys))
jwtStrategy, err := jwk.NewRS256JWTStrategy(jm, OpenIDConnectKeyName)

fc.RefreshTokenLifespan = time.Second * 2
handler := &Handler{
OAuth2: compose.Compose(
fc, fs, strat.s, hasher,
Expand Down Expand Up @@ -240,6 +241,7 @@ func TestAuthCodeWithDefaultStrategy(t *testing.T) {
expectIDToken bool

assertAccessToken, assertIDToken func(*testing.T, string)
assertRefreshToken func(*testing.T, *oauth2.Token)
}{
{
d: "Checks if request fails when audience doesn't match",
Expand Down Expand Up @@ -332,6 +334,111 @@ func TestAuthCodeWithDefaultStrategy(t *testing.T) {
assert.NotEmpty(t, "what-a-cool-nonce", data["nonce"])
},
},
{
d: "Perform OAuth2 flow with refreshing which fails due to expiry",
authURL: oauthConfig.AuthCodeURL("some-hardcoded-state", oauth2.SetAuthURLParam("nonce", "what-a-cool-nonce")),
cj: newCookieJar(),
lph: func(t *testing.T) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
_, res, err := apiClient.GetLoginRequest(r.URL.Query().Get("login_challenge"))
require.NoError(t, err)
require.EqualValues(t, http.StatusOK, res.StatusCode)
v, res, err := apiClient.AcceptLoginRequest(r.URL.Query().Get("login_challenge"), swagger.AcceptLoginRequest{
Subject: "user-a", Remember: false, RememberFor: 0, Acr: "1",
})
require.NoError(t, err)
require.EqualValues(t, http.StatusOK, res.StatusCode)
require.NotEmpty(t, v.RedirectTo)
http.Redirect(w, r, v.RedirectTo, http.StatusFound)
}
},
cph: func(t *testing.T) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
rr, res, err := apiClient.GetConsentRequest(r.URL.Query().Get("consent_challenge"))
require.NoError(t, err)
require.EqualValues(t, http.StatusOK, res.StatusCode)
v, res, err := apiClient.AcceptConsentRequest(r.URL.Query().Get("consent_challenge"), swagger.AcceptConsentRequest{
GrantScope: []string{"hydra", "offline", "openid"}, Remember: false, RememberFor: 0,
GrantAccessTokenAudience: rr.RequestedAccessTokenAudience,
Session: swagger.ConsentRequestSession{},
})
require.NoError(t, err)
require.EqualValues(t, http.StatusOK, res.StatusCode)
require.NotEmpty(t, v.RedirectTo)
http.Redirect(w, r, v.RedirectTo, http.StatusFound)
}
},
cb: func(t *testing.T) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
code = r.URL.Query().Get("code")
require.NotEmpty(t, code)
w.WriteHeader(http.StatusOK)
}
},
expectOAuthAuthError: false,
expectOAuthTokenError: false,
expectIDToken: true,
expectRefreshToken: true,
assertRefreshToken: func(t *testing.T, token *oauth2.Token) {
time.Sleep(time.Second * 4)
token.Expiry = token.Expiry.Add(-time.Hour * 24)
_, err := oauthConfig.TokenSource(oauth2.NoContext, token).Token()
require.Error(t, err)
},
},
{
d: "Perform OAuth2 flow with refreshing which works just fine",
authURL: oauthConfig.AuthCodeURL("some-hardcoded-state", oauth2.SetAuthURLParam("nonce", "what-a-cool-nonce")),
cj: newCookieJar(),
lph: func(t *testing.T) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
_, res, err := apiClient.GetLoginRequest(r.URL.Query().Get("login_challenge"))
require.NoError(t, err)
require.EqualValues(t, http.StatusOK, res.StatusCode)
v, res, err := apiClient.AcceptLoginRequest(r.URL.Query().Get("login_challenge"), swagger.AcceptLoginRequest{
Subject: "user-a", Remember: false, RememberFor: 0, Acr: "1",
})
require.NoError(t, err)
require.EqualValues(t, http.StatusOK, res.StatusCode)
require.NotEmpty(t, v.RedirectTo)
http.Redirect(w, r, v.RedirectTo, http.StatusFound)
}
},
cph: func(t *testing.T) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
rr, res, err := apiClient.GetConsentRequest(r.URL.Query().Get("consent_challenge"))
require.NoError(t, err)
require.EqualValues(t, http.StatusOK, res.StatusCode)
v, res, err := apiClient.AcceptConsentRequest(r.URL.Query().Get("consent_challenge"), swagger.AcceptConsentRequest{
GrantScope: []string{"hydra", "offline", "openid"}, Remember: false, RememberFor: 0,
GrantAccessTokenAudience: rr.RequestedAccessTokenAudience,
Session: swagger.ConsentRequestSession{},
})
require.NoError(t, err)
require.EqualValues(t, http.StatusOK, res.StatusCode)
require.NotEmpty(t, v.RedirectTo)
http.Redirect(w, r, v.RedirectTo, http.StatusFound)
}
},
cb: func(t *testing.T) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
code = r.URL.Query().Get("code")
require.NotEmpty(t, code)
w.WriteHeader(http.StatusOK)
}
},
expectOAuthAuthError: false,
expectOAuthTokenError: false,
expectIDToken: true,
expectRefreshToken: true,
assertRefreshToken: func(t *testing.T, token *oauth2.Token) {
token.Expiry = token.Expiry.Add(-time.Hour * 24)
n, err := oauthConfig.TokenSource(oauth2.NoContext, token).Token()
require.NoError(t, err)
require.NotEqual(t, token.AccessToken, n.AccessToken)
require.NotEqual(t, token.RefreshToken, n.RefreshToken)
},
},
{
d: "Perform OAuth2 flow with audience",
authURL: oauthConfig.AuthCodeURL("some-hardcoded-state", oauth2.SetAuthURLParam("audience", "https://api.ory.sh/")),
Expand Down Expand Up @@ -894,6 +1001,9 @@ func TestAuthCodeWithDefaultStrategy(t *testing.T) {
if tc.assertIDToken != nil {
tc.assertIDToken(t, token.Extra("id_token").(string))
}
if tc.assertRefreshToken != nil {
tc.assertRefreshToken(t, token)
}
})
}
})
Expand Down

0 comments on commit f84faf6

Please sign in to comment.