Implement handling OAuth2 client token lifespans. (#145)

This commit is contained in:
David Wobrock
2024-06-24 13:52:50 +02:00
committed by GitHub
parent 8029e019dd
commit 8f679ba89a
6 changed files with 215 additions and 34 deletions

View File

@ -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 {
@ -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

View File

@ -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()
@ -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{},
},
}
}

View File

@ -148,6 +148,7 @@ func (in *OAuth2ClientSpec) DeepCopyInto(out *OAuth2ClientSpec) {
copy(*out, *in)
}
out.HydraAdmin = in.HydraAdmin
out.TokenLifespans = in.TokenLifespans
in.Metadata.DeepCopyInto(&out.Metadata)
}
@ -196,3 +197,18 @@ func (in *ReconciliationError) DeepCopy() *ReconciliationError {
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TokenLifespans) DeepCopyInto(out *TokenLifespans) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TokenLifespans.
func (in *TokenLifespans) DeepCopy() *TokenLifespans {
if in == nil {
return nil
}
out := new(TokenLifespans)
in.DeepCopyInto(out)
return out
}