Skip to content

Commit

Permalink
Implement handling OAuth2 client token lifespans. (#145)
Browse files Browse the repository at this point in the history
  • Loading branch information
David-Wobrock authored Jun 24, 2024
1 parent 8029e01 commit 8f679ba
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 34 deletions.
67 changes: 67 additions & 0 deletions api/v1alpha1/oauth2client_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,69 @@ type HydraAdmin struct {
ForwardedProto string `json:"forwardedProto,omitempty"`
}

// TokenLifespans defines the desired token durations by grant type for OAuth2Client
type TokenLifespans struct {
// +kubebuilder:validation:Pattern=[0-9]+(ns|us|ms|s|m|h)
//
// AuthorizationCodeGrantAccessTokenLifespan is the access token lifespan
// issued on an authorization_code grant.
AuthorizationCodeGrantAccessTokenLifespan string `json:"authorization_code_grant_access_token_lifespan,omitempty"`

// +kubebuilder:validation:Pattern=[0-9]+(ns|us|ms|s|m|h)
//
// AuthorizationCodeGrantIdTokenLifespan is the id token lifespan
// issued on an authorization_code grant.
AuthorizationCodeGrantIdTokenLifespan string `json:"authorization_code_grant_id_token_lifespan,omitempty"`

// +kubebuilder:validation:Pattern=[0-9]+(ns|us|ms|s|m|h)
//
// AuthorizationCodeGrantRefreshTokenLifespan is the refresh token lifespan
// issued on an authorization_code grant.
AuthorizationCodeGrantRefreshTokenLifespan string `json:"authorization_code_grant_refresh_token_lifespan,omitempty"`

// +kubebuilder:validation:Pattern=[0-9]+(ns|us|ms|s|m|h)
//
// AuthorizationCodeGrantRefreshTokenLifespan is the access token lifespan
// issued on a client_credentials grant.
ClientCredentialsGrantAccessTokenLifespan string `json:"client_credentials_grant_access_token_lifespan,omitempty"`

// +kubebuilder:validation:Pattern=[0-9]+(ns|us|ms|s|m|h)
//
// ImplicitGrantAccessTokenLifespan is the access token lifespan
// issued on an implicit grant.
ImplicitGrantAccessTokenLifespan string `json:"implicit_grant_access_token_lifespan,omitempty"`

// +kubebuilder:validation:Pattern=[0-9]+(ns|us|ms|s|m|h)
//
// ImplicitGrantIdTokenLifespan is the id token lifespan
// issued on an implicit grant.
ImplicitGrantIdTokenLifespan string `json:"implicit_grant_id_token_lifespan,omitempty"`

// +kubebuilder:validation:Pattern=[0-9]+(ns|us|ms|s|m|h)
//
// JwtBearerGrantAccessTokenLifespan is the access token lifespan
// issued on a jwt_bearer grant.
JwtBearerGrantAccessTokenLifespan string `json:"jwt_bearer_grant_access_token_lifespan,omitempty"`

// +kubebuilder:validation:Pattern=[0-9]+(ns|us|ms|s|m|h)
//
// RefreshTokenGrantAccessTokenLifespan is the access token lifespan
// issued on a refresh_token grant.
RefreshTokenGrantAccessTokenLifespan string `json:"refresh_token_grant_access_token_lifespan,omitempty"`

// +kubebuilder:validation:Pattern=[0-9]+(ns|us|ms|s|m|h)
//
// RefreshTokenGrantIdTokenLifespan is the id token lifespan
// issued on a refresh_token grant.
RefreshTokenGrantIdTokenLifespan string `json:"refresh_token_grant_id_token_lifespan,omitempty"`

// +kubebuilder:validation:Pattern=[0-9]+(ns|us|ms|s|m|h)
//
// RefreshTokenGrantRefreshTokenLifespan is the refresh token lifespan
// issued on a refresh_token grant.
RefreshTokenGrantRefreshTokenLifespan string `json:"refresh_token_grant_refresh_token_lifespan,omitempty"`
}

// OAuth2ClientSpec defines the desired state of OAuth2Client
type OAuth2ClientSpec struct {

Expand Down Expand Up @@ -110,6 +173,10 @@ type OAuth2ClientSpec struct {
// Indication which authentication method shoud be used for the token endpoint
TokenEndpointAuthMethod TokenEndpointAuthMethod `json:"tokenEndpointAuthMethod,omitempty"`

// TokenLifespans is the configuration to use for managing different token lifespans
// depending on the used grant type.
TokenLifespans TokenLifespans `json:"tokenLifespans,omitempty"`

// +kubebuilder:validation:Type=object
// +nullable
// +optional
Expand Down
41 changes: 26 additions & 15 deletions api/v1alpha1/oauth2client_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,17 +89,27 @@ func TestCreateAPI(t *testing.T) {
t.Run("by failing if the requested object doesn't meet CRD requirements", func(t *testing.T) {

for desc, modifyClient := range map[string]func(){
"invalid grant type": func() { created.Spec.GrantTypes = []GrantType{"invalid"} },
"invalid response type": func() { created.Spec.ResponseTypes = []ResponseType{"invalid", "code"} },
"invalid composite response type": func() { created.Spec.ResponseTypes = []ResponseType{"invalid code", "code id_token"} },
"invalid scope": func() { created.Spec.Scope = "" },
"missing secret name": func() { created.Spec.SecretName = "" },
"invalid redirect URI": func() { created.Spec.RedirectURIs = []RedirectURI{"invalid"} },
"invalid logout redirect URI": func() { created.Spec.PostLogoutRedirectURIs = []RedirectURI{"invalid"} },
"invalid hydra url": func() { created.Spec.HydraAdmin.URL = "invalid" },
"invalid hydra port high": func() { created.Spec.HydraAdmin.Port = 65536 },
"invalid hydra endpoint": func() { created.Spec.HydraAdmin.Endpoint = "invalid" },
"invalid hydra forwarded proto": func() { created.Spec.HydraAdmin.Endpoint = "invalid" },
"invalid grant type": func() { created.Spec.GrantTypes = []GrantType{"invalid"} },
"invalid response type": func() { created.Spec.ResponseTypes = []ResponseType{"invalid", "code"} },
"invalid composite response type": func() { created.Spec.ResponseTypes = []ResponseType{"invalid code", "code id_token"} },
"invalid scope": func() { created.Spec.Scope = "" },
"missing secret name": func() { created.Spec.SecretName = "" },
"invalid redirect URI": func() { created.Spec.RedirectURIs = []RedirectURI{"invalid"} },
"invalid logout redirect URI": func() { created.Spec.PostLogoutRedirectURIs = []RedirectURI{"invalid"} },
"invalid hydra url": func() { created.Spec.HydraAdmin.URL = "invalid" },
"invalid hydra port high": func() { created.Spec.HydraAdmin.Port = 65536 },
"invalid hydra endpoint": func() { created.Spec.HydraAdmin.Endpoint = "invalid" },
"invalid hydra forwarded proto": func() { created.Spec.HydraAdmin.ForwardedProto = "invalid" },
"invalid lifespan authorization code access token": func() { created.Spec.TokenLifespans.AuthorizationCodeGrantAccessTokenLifespan = "invalid" },
"invalid lifespan authorization code id token": func() { created.Spec.TokenLifespans.AuthorizationCodeGrantIdTokenLifespan = "invalid" },
"invalid lifespan authorization code refresh token": func() { created.Spec.TokenLifespans.AuthorizationCodeGrantRefreshTokenLifespan = "invalid" },
"invalid lifespan client credentials access token": func() { created.Spec.TokenLifespans.ClientCredentialsGrantAccessTokenLifespan = "invalid" },
"invalid lifespan implicit access token": func() { created.Spec.TokenLifespans.ImplicitGrantAccessTokenLifespan = "invalid" },
"invalid lifespan implicit id token": func() { created.Spec.TokenLifespans.ImplicitGrantIdTokenLifespan = "invalid" },
"invalid lifespan jwt bearer access token": func() { created.Spec.TokenLifespans.JwtBearerGrantAccessTokenLifespan = "invalid" },
"invalid lifespan refresh token access token": func() { created.Spec.TokenLifespans.RefreshTokenGrantAccessTokenLifespan = "invalid" },
"invalid lifespan refresh token id token": func() { created.Spec.TokenLifespans.RefreshTokenGrantIdTokenLifespan = "invalid" },
"invalid lifespan refresh token refresh token": func() { created.Spec.TokenLifespans.RefreshTokenGrantRefreshTokenLifespan = "invalid" },
} {
t.Run(fmt.Sprintf("case=%s", desc), func(t *testing.T) {
resetTestClient()
Expand Down Expand Up @@ -158,10 +168,11 @@ func resetTestClient() {
Namespace: "default",
},
Spec: OAuth2ClientSpec{
GrantTypes: []GrantType{"implicit", "client_credentials", "authorization_code", "refresh_token"},
ResponseTypes: []ResponseType{"id_token", "code", "token"},
Scope: "read,write",
SecretName: "secret-name",
GrantTypes: []GrantType{"implicit", "client_credentials", "authorization_code", "refresh_token"},
ResponseTypes: []ResponseType{"id_token", "code", "token"},
Scope: "read,write",
SecretName: "secret-name",
TokenLifespans: TokenLifespans{},
},
}
}
16 changes: 16 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

66 changes: 66 additions & 0 deletions config/crd/bases/hydra.ory.sh_oauth2clients.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,72 @@ spec:
Indication which authentication method shoud be used for the
token endpoint
type: string
tokenLifespans:
description: |-
TokenLifespans is the configuration to use for managing different token lifespans
depending on the used grant type.
properties:
authorization_code_grant_access_token_lifespan:
description: |-
AuthorizationCodeGrantAccessTokenLifespan is the access token lifespan
issued on an authorization_code grant.
pattern: "[0-9]+(ns|us|ms|s|m|h)"
type: string
authorization_code_grant_id_token_lifespan:
description: |-
AuthorizationCodeGrantIdTokenLifespan is the id token lifespan
issued on an authorization_code grant.
pattern: "[0-9]+(ns|us|ms|s|m|h)"
type: string
authorization_code_grant_refresh_token_lifespan:
description: |-
AuthorizationCodeGrantRefreshTokenLifespan is the refresh token lifespan
issued on an authorization_code grant.
pattern: "[0-9]+(ns|us|ms|s|m|h)"
type: string
client_credentials_grant_access_token_lifespan:
description: |-
AuthorizationCodeGrantRefreshTokenLifespan is the access token lifespan
issued on a client_credentials grant.
pattern: "[0-9]+(ns|us|ms|s|m|h)"
type: string
implicit_grant_access_token_lifespan:
description: |-
ImplicitGrantAccessTokenLifespan is the access token lifespan
issued on an implicit grant.
pattern: "[0-9]+(ns|us|ms|s|m|h)"
type: string
implicit_grant_id_token_lifespan:
description: |-
ImplicitGrantIdTokenLifespan is the id token lifespan
issued on an implicit grant.
pattern: "[0-9]+(ns|us|ms|s|m|h)"
type: string
jwt_bearer_grant_access_token_lifespan:
description: |-
JwtBearerGrantAccessTokenLifespan is the access token lifespan
issued on a jwt_bearer grant.
pattern: "[0-9]+(ns|us|ms|s|m|h)"
type: string
refresh_token_grant_access_token_lifespan:
description: |-
RefreshTokenGrantAccessTokenLifespan is the access token lifespan
issued on a refresh_token grant.
pattern: "[0-9]+(ns|us|ms|s|m|h)"
type: string
refresh_token_grant_id_token_lifespan:
description: |-
RefreshTokenGrantIdTokenLifespan is the id token lifespan
issued on a refresh_token grant.
pattern: "[0-9]+(ns|us|ms|s|m|h)"
type: string
refresh_token_grant_refresh_token_lifespan:
description: |-
RefreshTokenGrantRefreshTokenLifespan is the refresh token lifespan
issued on a refresh_token grant.
pattern: "[0-9]+(ns|us|ms|s|m|h)"
type: string
type: object
required:
- grantTypes
- scope
Expand Down
1 change: 1 addition & 0 deletions hydra/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ var testOAuthJSONPost = &hydra.OAuth2ClientJSON{
FrontChannelLogoutSessionRequired: false,
BackChannelLogoutURI: "https://localhost/backchannel-logout",
BackChannelLogoutSessionRequired: false,
AuthorizationCodeGrantAccessTokenLifespan: "6h",
}

var testOAuthJSONPut = &hydra.OAuth2ClientJSON{
Expand Down
58 changes: 39 additions & 19 deletions hydra/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,35 @@ import (

// OAuth2ClientJSON represents an OAuth2 client digestible by ORY Hydra
type OAuth2ClientJSON struct {
ClientName string `json:"client_name,omitempty"`
ClientID *string `json:"client_id,omitempty"`
Secret *string `json:"client_secret,omitempty"`
GrantTypes []string `json:"grant_types"`
RedirectURIs []string `json:"redirect_uris,omitempty"`
PostLogoutRedirectURIs []string `json:"post_logout_redirect_uris,omitempty"`
AllowedCorsOrigins []string `json:"allowed_cors_origins,omitempty"`
ResponseTypes []string `json:"response_types,omitempty"`
Audience []string `json:"audience,omitempty"`
Scope string `json:"scope"`
SkipConsent bool `json:"skip_consent,omitempty"`
Owner string `json:"owner"`
TokenEndpointAuthMethod string `json:"token_endpoint_auth_method,omitempty"`
Metadata json.RawMessage `json:"metadata,omitempty"`
JwksUri string `json:"jwks_uri,omitempty"`
FrontChannelLogoutSessionRequired bool `json:"frontchannel_logout_session_required"`
FrontChannelLogoutURI string `json:"frontchannel_logout_uri"`
BackChannelLogoutSessionRequired bool `json:"backchannel_logout_session_required"`
BackChannelLogoutURI string `json:"backchannel_logout_uri"`
ClientName string `json:"client_name,omitempty"`
ClientID *string `json:"client_id,omitempty"`
Secret *string `json:"client_secret,omitempty"`
GrantTypes []string `json:"grant_types"`
RedirectURIs []string `json:"redirect_uris,omitempty"`
PostLogoutRedirectURIs []string `json:"post_logout_redirect_uris,omitempty"`
AllowedCorsOrigins []string `json:"allowed_cors_origins,omitempty"`
ResponseTypes []string `json:"response_types,omitempty"`
Audience []string `json:"audience,omitempty"`
Scope string `json:"scope"`
SkipConsent bool `json:"skip_consent,omitempty"`
Owner string `json:"owner"`
TokenEndpointAuthMethod string `json:"token_endpoint_auth_method,omitempty"`
Metadata json.RawMessage `json:"metadata,omitempty"`
JwksUri string `json:"jwks_uri,omitempty"`
FrontChannelLogoutSessionRequired bool `json:"frontchannel_logout_session_required"`
FrontChannelLogoutURI string `json:"frontchannel_logout_uri"`
BackChannelLogoutSessionRequired bool `json:"backchannel_logout_session_required"`
BackChannelLogoutURI string `json:"backchannel_logout_uri"`
AuthorizationCodeGrantAccessTokenLifespan string `json:"authorization_code_grant_access_token_lifespan,omitempty"`
AuthorizationCodeGrantIdTokenLifespan string `json:"authorization_code_grant_id_token_lifespan,omitempty"`
AuthorizationCodeGrantRefreshTokenLifespan string `json:"authorization_code_grant_refresh_token_lifespan,omitempty"`
ClientCredentialsGrantAccessTokenLifespan string `json:"client_credentials_grant_access_token_lifespan,omitempty"`
ImplicitGrantAccessTokenLifespan string `json:"implicit_grant_access_token_lifespan,omitempty"`
ImplicitGrantIdTokenLifespan string `json:"implicit_grant_id_token_lifespan,omitempty"`
JwtBearerGrantAccessTokenLifespan string `json:"jwt_bearer_grant_access_token_lifespan,omitempty"`
RefreshTokenGrantAccessTokenLifespan string `json:"refresh_token_grant_access_token_lifespan,omitempty"`
RefreshTokenGrantIdTokenLifespan string `json:"refresh_token_grant_id_token_lifespan,omitempty"`
RefreshTokenGrantRefreshTokenLifespan string `json:"refresh_token_grant_refresh_token_lifespan,omitempty"`
}

// Oauth2ClientCredentials represents client ID and password fetched from a
Expand Down Expand Up @@ -74,6 +84,16 @@ func FromOAuth2Client(c *hydrav1alpha1.OAuth2Client) (*OAuth2ClientJSON, error)
FrontChannelLogoutSessionRequired: c.Spec.BackChannelLogoutSessionRequired,
BackChannelLogoutSessionRequired: c.Spec.BackChannelLogoutSessionRequired,
BackChannelLogoutURI: c.Spec.BackChannelLogoutURI,
AuthorizationCodeGrantAccessTokenLifespan: c.Spec.TokenLifespans.AuthorizationCodeGrantAccessTokenLifespan,
AuthorizationCodeGrantIdTokenLifespan: c.Spec.TokenLifespans.AuthorizationCodeGrantIdTokenLifespan,
AuthorizationCodeGrantRefreshTokenLifespan: c.Spec.TokenLifespans.AuthorizationCodeGrantRefreshTokenLifespan,
ClientCredentialsGrantAccessTokenLifespan: c.Spec.TokenLifespans.ClientCredentialsGrantAccessTokenLifespan,
ImplicitGrantAccessTokenLifespan: c.Spec.TokenLifespans.ImplicitGrantAccessTokenLifespan,
ImplicitGrantIdTokenLifespan: c.Spec.TokenLifespans.ImplicitGrantIdTokenLifespan,
JwtBearerGrantAccessTokenLifespan: c.Spec.TokenLifespans.JwtBearerGrantAccessTokenLifespan,
RefreshTokenGrantAccessTokenLifespan: c.Spec.TokenLifespans.RefreshTokenGrantAccessTokenLifespan,
RefreshTokenGrantIdTokenLifespan: c.Spec.TokenLifespans.RefreshTokenGrantIdTokenLifespan,
RefreshTokenGrantRefreshTokenLifespan: c.Spec.TokenLifespans.RefreshTokenGrantRefreshTokenLifespan,
}, nil
}

Expand Down

0 comments on commit 8f679ba

Please sign in to comment.