Support talking to multiple ORY Hydra deployments (#35)

This commit is contained in:
Paul Davis
2019-11-14 01:11:13 -07:00
committed by hackerman
parent c0bc5dffa5
commit 803c935b47
17 changed files with 404 additions and 51 deletions

View File

@ -25,12 +25,46 @@ import (
type StatusCode string
const (
StatusRegistrationFailed StatusCode = "CLIENT_REGISTRATION_FAILED"
StatusCreateSecretFailed StatusCode = "SECRET_CREATION_FAILED"
StatusUpdateFailed StatusCode = "CLIENT_UPDATE_FAILED"
StatusInvalidSecret StatusCode = "INVALID_SECRET"
StatusRegistrationFailed StatusCode = "CLIENT_REGISTRATION_FAILED"
StatusCreateSecretFailed StatusCode = "SECRET_CREATION_FAILED"
StatusUpdateFailed StatusCode = "CLIENT_UPDATE_FAILED"
StatusInvalidSecret StatusCode = "INVALID_SECRET"
StatusInvalidHydraAddress StatusCode = "INVALID_HYDRA_ADDRESS"
)
// HydraAdmin defines the desired hydra admin instance to use for OAuth2Client
type HydraAdmin struct {
// +kubebuilder:validation:MaxLength=64
// +kubebuilder:validation:Pattern=(^$|^https?://.*)
//
// URL is the URL for the hydra instance on
// which to set up the client. This value will override the value
// provided to `--hydra-url`
URL string `json:"url,omitempty"`
// +kubebuilder:validation:Maximum=65535
//
// Port is the port for the hydra instance on
// which to set up the client. This value will override the value
// provided to `--hydra-port`
Port int `json:"port,omitempty"`
// +kubebuilder:validation:Pattern=(^$|^/.*)
//
// Endpoint is the endpoint for the hydra instance on which
// to set up the client. This value will override the value
// provided to `--endpoint` (defaults to `"/clients"` in the
// application)
Endpoint string `json:"endpoint,omitempty"`
// +kubebuilder:validation:Pattern=(^$|https?|off)
//
// ForwardedProto overrides the `--forwarded-proto` flag. The
// value "off" will force this to be off even if
// `--forwarded-proto` is specified
ForwardedProto string `json:"forwardedProto,omitempty"`
}
// OAuth2ClientSpec defines the desired state of OAuth2Client
type OAuth2ClientSpec struct {
// +kubebuilder:validation:MaxItems=4
@ -46,6 +80,9 @@ type OAuth2ClientSpec struct {
// use at the authorization endpoint.
ResponseTypes []ResponseType `json:"responseTypes,omitempty"`
// RedirectURIs is an array of the redirect URIs allowed for the application
RedirectURIs []RedirectURI `json:"redirectUris,omitempty"`
// +kubebuilder:validation:Pattern=([a-zA-Z0-9\.\*]+\s?)+
//
// Scope is a string containing a space-separated list of scope values (as
@ -59,6 +96,10 @@ type OAuth2ClientSpec struct {
//
// SecretName points to the K8s secret that contains this client's ID and password
SecretName string `json:"secretName"`
// HydraAdmin is the optional configuration to use for managing
// this client
HydraAdmin HydraAdmin `json:"hydraAdmin,omitempty"`
}
// +kubebuilder:validation:Enum=client_credentials;authorization_code;implicit;refresh_token
@ -69,6 +110,10 @@ type GrantType string
// ResponseType represents an OAuth 2.0 response type strings
type ResponseType string
// +kubebuilder:validation:Pattern=\w+:/?/?[^\s]+
// RedirectURI represents a redirect URI for the client
type RedirectURI string
// OAuth2ClientStatus defines the observed state of OAuth2Client
type OAuth2ClientStatus struct {
// ObservedGeneration represents the most recent generation observed by the daemon set controller.
@ -114,6 +159,7 @@ func (c *OAuth2Client) ToOAuth2ClientJSON() *hydra.OAuth2ClientJSON {
return &hydra.OAuth2ClientJSON{
GrantTypes: grantToStringSlice(c.Spec.GrantTypes),
ResponseTypes: responseToStringSlice(c.Spec.ResponseTypes),
RedirectURIs: redirectToStringSlice(c.Spec.RedirectURIs),
Scope: c.Spec.Scope,
Owner: fmt.Sprintf("%s/%s", c.Name, c.Namespace),
}
@ -134,3 +180,11 @@ func grantToStringSlice(gt []GrantType) []string {
}
return output
}
func redirectToStringSlice(ru []RedirectURI) []string {
var output = make([]string, len(ru))
for i, elem := range ru {
output[i] = string(elem)
}
return output
}

View File

@ -52,7 +52,7 @@ func TestCreateAPI(t *testing.T) {
Namespace: "default",
}
t.Run("by creating an API object if it meets CRD requirements", func(t *testing.T) {
t.Run("by creating an API object if it meets CRD requirements without optional parameters", func(t *testing.T) {
resetTestClient()
@ -71,13 +71,45 @@ func TestCreateAPI(t *testing.T) {
require.Error(t, getErr)
})
t.Run("by creating an API object if it meets CRD requirements with optional parameters", func(t *testing.T) {
resetTestClient()
created.Spec.RedirectURIs = []RedirectURI{"https://client/account", "http://localhost:8080/account"}
created.Spec.HydraAdmin = HydraAdmin{
URL: "http://localhost",
Port: 4445,
// Endpoint: "/clients",
ForwardedProto: "https",
}
createErr = k8sClient.Create(context.TODO(), created)
require.NoError(t, createErr)
fetched = &OAuth2Client{}
getErr = k8sClient.Get(context.TODO(), key, fetched)
require.NoError(t, getErr)
assert.Equal(t, created, fetched)
deleteErr = k8sClient.Delete(context.TODO(), created)
require.NoError(t, deleteErr)
getErr = k8sClient.Get(context.TODO(), key, created)
require.Error(t, getErr)
})
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"} },
"invalid scope": func() { created.Spec.Scope = "" },
"missing secret name": func() { created.Spec.SecretName = "" },
"invalid grant type": func() { created.Spec.GrantTypes = []GrantType{"invalid"} },
"invalid response type": func() { created.Spec.ResponseTypes = []ResponseType{"invalid"} },
"invalid scope": func() { created.Spec.Scope = "" },
"missing secret name": func() { created.Spec.SecretName = "" },
"invalid redirect URI": func() { created.Spec.RedirectURIs = []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" },
} {
t.Run(fmt.Sprintf("case=%s", desc), func(t *testing.T) {

View File

@ -23,6 +23,21 @@ import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HydraAdmin) DeepCopyInto(out *HydraAdmin) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HydraAdmin.
func (in *HydraAdmin) DeepCopy() *HydraAdmin {
if in == nil {
return nil
}
out := new(HydraAdmin)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *OAuth2Client) DeepCopyInto(out *OAuth2Client) {
*out = *in
@ -95,6 +110,12 @@ func (in *OAuth2ClientSpec) DeepCopyInto(out *OAuth2ClientSpec) {
*out = make([]ResponseType, len(*in))
copy(*out, *in)
}
if in.RedirectURIs != nil {
in, out := &in.RedirectURIs, &out.RedirectURIs
*out = make([]RedirectURI, len(*in))
copy(*out, *in)
}
out.HydraAdmin = in.HydraAdmin
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OAuth2ClientSpec.