fix(controller): Ensure that OAuth2Client reconciliation creates hydra client for specs (#83)
This commit is contained in:
parent
273922d2e2
commit
028c3df4c2
@ -16,11 +16,6 @@ limitations under the License.
|
|||||||
package v1alpha1
|
package v1alpha1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/ory/hydra-maester/hydra"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
@ -185,49 +180,3 @@ type OAuth2ClientList struct {
|
|||||||
func init() {
|
func init() {
|
||||||
SchemeBuilder.Register(&OAuth2Client{}, &OAuth2ClientList{})
|
SchemeBuilder.Register(&OAuth2Client{}, &OAuth2ClientList{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToOAuth2ClientJSON converts an OAuth2Client into a OAuth2ClientJSON object that represents an OAuth2 client digestible by ORY Hydra
|
|
||||||
func (c *OAuth2Client) ToOAuth2ClientJSON() (*hydra.OAuth2ClientJSON, error) {
|
|
||||||
meta, err := json.Marshal(c.Spec.Metadata)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithMessage(err, "unable to encode `metadata` property value to json")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &hydra.OAuth2ClientJSON{
|
|
||||||
ClientName: c.Spec.ClientName,
|
|
||||||
GrantTypes: grantToStringSlice(c.Spec.GrantTypes),
|
|
||||||
ResponseTypes: responseToStringSlice(c.Spec.ResponseTypes),
|
|
||||||
RedirectURIs: redirectToStringSlice(c.Spec.RedirectURIs),
|
|
||||||
PostLogoutRedirectURIs: redirectToStringSlice(c.Spec.PostLogoutRedirectURIs),
|
|
||||||
AllowedCorsOrigins: redirectToStringSlice(c.Spec.AllowedCorsOrigins),
|
|
||||||
Audience: c.Spec.Audience,
|
|
||||||
Scope: c.Spec.Scope,
|
|
||||||
Owner: fmt.Sprintf("%s/%s", c.Name, c.Namespace),
|
|
||||||
TokenEndpointAuthMethod: string(c.Spec.TokenEndpointAuthMethod),
|
|
||||||
Metadata: meta,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func responseToStringSlice(rt []ResponseType) []string {
|
|
||||||
var output = make([]string, len(rt))
|
|
||||||
for i, elem := range rt {
|
|
||||||
output[i] = string(elem)
|
|
||||||
}
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
|
|
||||||
func grantToStringSlice(gt []GrantType) []string {
|
|
||||||
var output = make([]string, len(gt))
|
|
||||||
for i, elem := range gt {
|
|
||||||
output[i] = string(elem)
|
|
||||||
}
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
|
|
||||||
func redirectToStringSlice(ru []RedirectURI) []string {
|
|
||||||
var output = make([]string, len(ru))
|
|
||||||
for i, elem := range ru {
|
|
||||||
output[i] = string(elem)
|
|
||||||
}
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
|
@ -2,16 +2,18 @@
|
|||||||
|
|
||||||
package mocks
|
package mocks
|
||||||
|
|
||||||
import hydra "github.com/ory/hydra-maester/hydra"
|
import (
|
||||||
import mock "github.com/stretchr/testify/mock"
|
hydra "github.com/ory/hydra-maester/hydra"
|
||||||
|
mock "github.com/stretchr/testify/mock"
|
||||||
|
)
|
||||||
|
|
||||||
// HydraClientInterface is an autogenerated mock type for the HydraClientInterface type
|
// Client is an autogenerated mock type for the Client type
|
||||||
type HydraClientInterface struct {
|
type Client struct {
|
||||||
mock.Mock
|
mock.Mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteOAuth2Client provides a mock function with given fields: id
|
// DeleteOAuth2Client provides a mock function with given fields: id
|
||||||
func (_m *HydraClientInterface) DeleteOAuth2Client(id string) error {
|
func (_m *Client) DeleteOAuth2Client(id string) error {
|
||||||
ret := _m.Called(id)
|
ret := _m.Called(id)
|
||||||
|
|
||||||
var r0 error
|
var r0 error
|
||||||
@ -25,7 +27,7 @@ func (_m *HydraClientInterface) DeleteOAuth2Client(id string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetOAuth2Client provides a mock function with given fields: id
|
// GetOAuth2Client provides a mock function with given fields: id
|
||||||
func (_m *HydraClientInterface) GetOAuth2Client(id string) (*hydra.OAuth2ClientJSON, bool, error) {
|
func (_m *Client) GetOAuth2Client(id string) (*hydra.OAuth2ClientJSON, bool, error) {
|
||||||
ret := _m.Called(id)
|
ret := _m.Called(id)
|
||||||
|
|
||||||
var r0 *hydra.OAuth2ClientJSON
|
var r0 *hydra.OAuth2ClientJSON
|
||||||
@ -55,7 +57,7 @@ func (_m *HydraClientInterface) GetOAuth2Client(id string) (*hydra.OAuth2ClientJ
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListOAuth2Client provides a mock function with given fields:
|
// ListOAuth2Client provides a mock function with given fields:
|
||||||
func (_m *HydraClientInterface) ListOAuth2Client() ([]*hydra.OAuth2ClientJSON, error) {
|
func (_m *Client) ListOAuth2Client() ([]*hydra.OAuth2ClientJSON, error) {
|
||||||
ret := _m.Called()
|
ret := _m.Called()
|
||||||
|
|
||||||
var r0 []*hydra.OAuth2ClientJSON
|
var r0 []*hydra.OAuth2ClientJSON
|
||||||
@ -78,7 +80,7 @@ func (_m *HydraClientInterface) ListOAuth2Client() ([]*hydra.OAuth2ClientJSON, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PostOAuth2Client provides a mock function with given fields: o
|
// PostOAuth2Client provides a mock function with given fields: o
|
||||||
func (_m *HydraClientInterface) PostOAuth2Client(o *hydra.OAuth2ClientJSON) (*hydra.OAuth2ClientJSON, error) {
|
func (_m *Client) PostOAuth2Client(o *hydra.OAuth2ClientJSON) (*hydra.OAuth2ClientJSON, error) {
|
||||||
ret := _m.Called(o)
|
ret := _m.Called(o)
|
||||||
|
|
||||||
var r0 *hydra.OAuth2ClientJSON
|
var r0 *hydra.OAuth2ClientJSON
|
||||||
@ -101,7 +103,7 @@ func (_m *HydraClientInterface) PostOAuth2Client(o *hydra.OAuth2ClientJSON) (*hy
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PutOAuth2Client provides a mock function with given fields: o
|
// PutOAuth2Client provides a mock function with given fields: o
|
||||||
func (_m *HydraClientInterface) PutOAuth2Client(o *hydra.OAuth2ClientJSON) (*hydra.OAuth2ClientJSON, error) {
|
func (_m *Client) PutOAuth2Client(o *hydra.OAuth2ClientJSON) (*hydra.OAuth2ClientJSON, error) {
|
||||||
ret := _m.Called(o)
|
ret := _m.Called(o)
|
||||||
|
|
||||||
var r0 *hydra.OAuth2ClientJSON
|
var r0 *hydra.OAuth2ClientJSON
|
@ -18,9 +18,9 @@ package controllers
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/go-logr/logr"
|
"github.com/go-logr/logr"
|
||||||
hydrav1alpha1 "github.com/ory/hydra-maester/api/v1alpha1"
|
|
||||||
"github.com/ory/hydra-maester/hydra"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
apiv1 "k8s.io/api/core/v1"
|
apiv1 "k8s.io/api/core/v1"
|
||||||
apierrs "k8s.io/apimachinery/pkg/api/errors"
|
apierrs "k8s.io/apimachinery/pkg/api/errors"
|
||||||
@ -28,36 +28,89 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
ctrl "sigs.k8s.io/controller-runtime"
|
ctrl "sigs.k8s.io/controller-runtime"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
|
||||||
|
hydrav1alpha1 "github.com/ory/hydra-maester/api/v1alpha1"
|
||||||
|
"github.com/ory/hydra-maester/hydra"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ClientIDKey = "client_id"
|
ClientIDKey = "client_id"
|
||||||
ClientSecretKey = "client_secret"
|
ClientSecretKey = "client_secret"
|
||||||
FinalizerName = "finalizer.ory.hydra.sh"
|
FinalizerName = "finalizer.ory.hydra.sh"
|
||||||
|
|
||||||
|
DefaultNamespace = "default"
|
||||||
)
|
)
|
||||||
|
|
||||||
type clientMapKey struct {
|
type clientKey struct {
|
||||||
url string
|
url string
|
||||||
port int
|
port int
|
||||||
endpoint string
|
endpoint string
|
||||||
forwardedProto string
|
forwardedProto string
|
||||||
}
|
}
|
||||||
|
|
||||||
type HydraClientInterface interface {
|
// OAuth2ClientFactory is a function that creates oauth2 client.
|
||||||
GetOAuth2Client(id string) (*hydra.OAuth2ClientJSON, bool, error)
|
// The OAuth2ClientReconciler defaults to use hydra.New and the factory allows
|
||||||
ListOAuth2Client() ([]*hydra.OAuth2ClientJSON, error)
|
// to override this behavior for mocks during tests.
|
||||||
PostOAuth2Client(o *hydra.OAuth2ClientJSON) (*hydra.OAuth2ClientJSON, error)
|
type OAuth2ClientFactory func(
|
||||||
PutOAuth2Client(o *hydra.OAuth2ClientJSON) (*hydra.OAuth2ClientJSON, error)
|
spec hydrav1alpha1.OAuth2ClientSpec,
|
||||||
DeleteOAuth2Client(id string) error
|
tlsTrustStore string,
|
||||||
|
insecureSkipVerify bool,
|
||||||
|
) (hydra.Client, error)
|
||||||
|
|
||||||
|
// OAuth2ClientReconciler reconciles a OAuth2Client object.
|
||||||
|
type OAuth2ClientReconciler struct {
|
||||||
|
client.Client
|
||||||
|
HydraClient hydra.Client
|
||||||
|
Log logr.Logger
|
||||||
|
ControllerNamespace string
|
||||||
|
|
||||||
|
oauth2Clients map[clientKey]hydra.Client
|
||||||
|
oauth2ClientFactory OAuth2ClientFactory
|
||||||
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// OAuth2ClientReconciler reconciles a OAuth2Client object
|
// Options represent options to pass to the oauth2 client reconciler.
|
||||||
type OAuth2ClientReconciler struct {
|
type Options struct {
|
||||||
HydraClient HydraClientInterface
|
Namespace string
|
||||||
Log logr.Logger
|
OAuth2ClientFactory OAuth2ClientFactory
|
||||||
otherClients map[clientMapKey]HydraClientInterface
|
}
|
||||||
client.Client
|
|
||||||
ControllerNamespace string
|
// Option is a functional option.
|
||||||
|
type Option func(*Options)
|
||||||
|
|
||||||
|
// WithNamespace sets the kubernetes namespace for the controller.
|
||||||
|
// The default is "default".
|
||||||
|
func WithNamespace(ns string) Option {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.Namespace = ns
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithClientFactory sets a function to create new oauth2 clients during the reconciliation logic.
|
||||||
|
func WithClientFactory(factory OAuth2ClientFactory) Option {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.OAuth2ClientFactory = factory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new Oauth2ClientReconciler.
|
||||||
|
func New(c client.Client, hydraClient hydra.Client, log logr.Logger, opts ...Option) *OAuth2ClientReconciler {
|
||||||
|
options := &Options{
|
||||||
|
Namespace: DefaultNamespace,
|
||||||
|
OAuth2ClientFactory: hydra.New,
|
||||||
|
}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &OAuth2ClientReconciler{
|
||||||
|
Client: c,
|
||||||
|
HydraClient: hydraClient,
|
||||||
|
Log: log,
|
||||||
|
ControllerNamespace: options.Namespace,
|
||||||
|
oauth2Clients: make(map[clientKey]hydra.Client, 0),
|
||||||
|
oauth2ClientFactory: options.OAuth2ClientFactory,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// +kubebuilder:rbac:groups=hydra.ory.sh,resources=oauth2clients,verbs=get;list;watch;create;update;patch;delete
|
// +kubebuilder:rbac:groups=hydra.ory.sh,resources=oauth2clients,verbs=get;list;watch;create;update;patch;delete
|
||||||
@ -200,12 +253,12 @@ func (r *OAuth2ClientReconciler) registerOAuth2Client(ctx context.Context, c *hy
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
hydra, err := r.getHydraClientForClient(*c)
|
hydraClient, err := r.getHydraClientForClient(*c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
oauth2client, err := c.ToOAuth2ClientJSON()
|
oauth2client, err := hydra.FromOAuth2Client(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if updateErr := r.updateReconciliationStatusError(ctx, c, hydrav1alpha1.StatusRegistrationFailed, err); updateErr != nil {
|
if updateErr := r.updateReconciliationStatusError(ctx, c, hydrav1alpha1.StatusRegistrationFailed, err); updateErr != nil {
|
||||||
return updateErr
|
return updateErr
|
||||||
@ -214,7 +267,7 @@ func (r *OAuth2ClientReconciler) registerOAuth2Client(ctx context.Context, c *hy
|
|||||||
}
|
}
|
||||||
|
|
||||||
if credentials != nil {
|
if credentials != nil {
|
||||||
if _, err := hydra.PostOAuth2Client(oauth2client.WithCredentials(credentials)); err != nil {
|
if _, err := hydraClient.PostOAuth2Client(oauth2client.WithCredentials(credentials)); err != nil {
|
||||||
if updateErr := r.updateReconciliationStatusError(ctx, c, hydrav1alpha1.StatusRegistrationFailed, err); updateErr != nil {
|
if updateErr := r.updateReconciliationStatusError(ctx, c, hydrav1alpha1.StatusRegistrationFailed, err); updateErr != nil {
|
||||||
return updateErr
|
return updateErr
|
||||||
}
|
}
|
||||||
@ -222,7 +275,7 @@ func (r *OAuth2ClientReconciler) registerOAuth2Client(ctx context.Context, c *hy
|
|||||||
return r.ensureEmptyStatusError(ctx, c)
|
return r.ensureEmptyStatusError(ctx, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
created, err := hydra.PostOAuth2Client(oauth2client)
|
created, err := hydraClient.PostOAuth2Client(oauth2client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if updateErr := r.updateReconciliationStatusError(ctx, c, hydrav1alpha1.StatusRegistrationFailed, err); updateErr != nil {
|
if updateErr := r.updateReconciliationStatusError(ctx, c, hydrav1alpha1.StatusRegistrationFailed, err); updateErr != nil {
|
||||||
return updateErr
|
return updateErr
|
||||||
@ -260,12 +313,12 @@ func (r *OAuth2ClientReconciler) registerOAuth2Client(ctx context.Context, c *hy
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *OAuth2ClientReconciler) updateRegisteredOAuth2Client(ctx context.Context, c *hydrav1alpha1.OAuth2Client, credentials *hydra.Oauth2ClientCredentials) error {
|
func (r *OAuth2ClientReconciler) updateRegisteredOAuth2Client(ctx context.Context, c *hydrav1alpha1.OAuth2Client, credentials *hydra.Oauth2ClientCredentials) error {
|
||||||
hydra, err := r.getHydraClientForClient(*c)
|
hydraClient, err := r.getHydraClientForClient(*c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
oauth2client, err := c.ToOAuth2ClientJSON()
|
oauth2client, err := hydra.FromOAuth2Client(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if updateErr := r.updateReconciliationStatusError(ctx, c, hydrav1alpha1.StatusUpdateFailed, err); updateErr != nil {
|
if updateErr := r.updateReconciliationStatusError(ctx, c, hydrav1alpha1.StatusUpdateFailed, err); updateErr != nil {
|
||||||
return updateErr
|
return updateErr
|
||||||
@ -273,7 +326,7 @@ func (r *OAuth2ClientReconciler) updateRegisteredOAuth2Client(ctx context.Contex
|
|||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := hydra.PutOAuth2Client(oauth2client.WithCredentials(credentials)); err != nil {
|
if _, err := hydraClient.PutOAuth2Client(oauth2client.WithCredentials(credentials)); err != nil {
|
||||||
if updateErr := r.updateReconciliationStatusError(ctx, c, hydrav1alpha1.StatusUpdateFailed, err); updateErr != nil {
|
if updateErr := r.updateReconciliationStatusError(ctx, c, hydrav1alpha1.StatusUpdateFailed, err); updateErr != nil {
|
||||||
return updateErr
|
return updateErr
|
||||||
}
|
}
|
||||||
@ -352,17 +405,31 @@ func parseSecret(secret apiv1.Secret, authMethod hydrav1alpha1.TokenEndpointAuth
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *OAuth2ClientReconciler) getHydraClientForClient(oauth2client hydrav1alpha1.OAuth2Client) (HydraClientInterface, error) {
|
func (r *OAuth2ClientReconciler) getHydraClientForClient(
|
||||||
|
oauth2client hydrav1alpha1.OAuth2Client) (hydra.Client, error) {
|
||||||
spec := oauth2client.Spec
|
spec := oauth2client.Spec
|
||||||
key := clientMapKey{
|
if spec.HydraAdmin.URL != "" {
|
||||||
|
key := clientKey{
|
||||||
url: spec.HydraAdmin.URL,
|
url: spec.HydraAdmin.URL,
|
||||||
port: spec.HydraAdmin.Port,
|
port: spec.HydraAdmin.Port,
|
||||||
endpoint: spec.HydraAdmin.Endpoint,
|
endpoint: spec.HydraAdmin.Endpoint,
|
||||||
forwardedProto: spec.HydraAdmin.ForwardedProto,
|
forwardedProto: spec.HydraAdmin.ForwardedProto,
|
||||||
}
|
}
|
||||||
if c, ok := r.otherClients[key]; ok {
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
if c, ok := r.oauth2Clients[key]; ok {
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client, err := r.oauth2ClientFactory(spec, "", false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "cannot create oauth2 client from CRD")
|
||||||
|
}
|
||||||
|
|
||||||
|
r.oauth2Clients[key] = client
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
if r.HydraClient == nil {
|
if r.HydraClient == nil {
|
||||||
return nil, errors.New("Not default client or other clients configured")
|
return nil, errors.New("Not default client or other clients configured")
|
||||||
}
|
}
|
||||||
|
@ -10,10 +10,6 @@ import (
|
|||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
hydrav1alpha1 "github.com/ory/hydra-maester/api/v1alpha1"
|
|
||||||
"github.com/ory/hydra-maester/controllers"
|
|
||||||
"github.com/ory/hydra-maester/controllers/mocks"
|
|
||||||
"github.com/ory/hydra-maester/hydra"
|
|
||||||
. "github.com/stretchr/testify/mock"
|
. "github.com/stretchr/testify/mock"
|
||||||
apiv1 "k8s.io/api/core/v1"
|
apiv1 "k8s.io/api/core/v1"
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
@ -27,6 +23,11 @@ import (
|
|||||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/source"
|
"sigs.k8s.io/controller-runtime/pkg/source"
|
||||||
|
|
||||||
|
hydrav1alpha1 "github.com/ory/hydra-maester/api/v1alpha1"
|
||||||
|
"github.com/ory/hydra-maester/controllers"
|
||||||
|
mocks "github.com/ory/hydra-maester/controllers/mocks/hydra"
|
||||||
|
"github.com/ory/hydra-maester/hydra"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -59,7 +60,7 @@ var _ = Describe("OAuth2Client Controller", func() {
|
|||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
c := mgr.GetClient()
|
c := mgr.GetClient()
|
||||||
|
|
||||||
mch := &mocks.HydraClientInterface{}
|
mch := &mocks.Client{}
|
||||||
mch.On("GetOAuth2Client", Anything).Return(nil, false, nil)
|
mch.On("GetOAuth2Client", Anything).Return(nil, false, nil)
|
||||||
mch.On("DeleteOAuth2Client", Anything).Return(nil)
|
mch.On("DeleteOAuth2Client", Anything).Return(nil)
|
||||||
mch.On("ListOAuth2Client", Anything).Return(nil, nil)
|
mch.On("ListOAuth2Client", Anything).Return(nil, nil)
|
||||||
@ -138,7 +139,7 @@ var _ = Describe("OAuth2Client Controller", func() {
|
|||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
c := mgr.GetClient()
|
c := mgr.GetClient()
|
||||||
|
|
||||||
mch := &mocks.HydraClientInterface{}
|
mch := &mocks.Client{}
|
||||||
mch.On("GetOAuth2Client", Anything).Return(nil, false, nil)
|
mch.On("GetOAuth2Client", Anything).Return(nil, false, nil)
|
||||||
mch.On("PostOAuth2Client", Anything).Return(nil, errors.New("error"))
|
mch.On("PostOAuth2Client", Anything).Return(nil, errors.New("error"))
|
||||||
mch.On("DeleteOAuth2Client", Anything).Return(nil)
|
mch.On("DeleteOAuth2Client", Anything).Return(nil)
|
||||||
@ -168,6 +169,7 @@ var _ = Describe("OAuth2Client Controller", func() {
|
|||||||
err = c.Get(context.TODO(), ok, &retrieved)
|
err = c.Get(context.TODO(), ok, &retrieved)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(retrieved.Status.ReconciliationError).NotTo(BeNil())
|
Expect(retrieved.Status.ReconciliationError).NotTo(BeNil())
|
||||||
|
|
||||||
Expect(retrieved.Status.ReconciliationError.Code).To(Equal(hydrav1alpha1.StatusRegistrationFailed))
|
Expect(retrieved.Status.ReconciliationError.Code).To(Equal(hydrav1alpha1.StatusRegistrationFailed))
|
||||||
Expect(retrieved.Status.ReconciliationError.Description).To(Equal("error"))
|
Expect(retrieved.Status.ReconciliationError.Description).To(Equal("error"))
|
||||||
|
|
||||||
@ -204,7 +206,7 @@ var _ = Describe("OAuth2Client Controller", func() {
|
|||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
c := mgr.GetClient()
|
c := mgr.GetClient()
|
||||||
|
|
||||||
mch := mocks.HydraClientInterface{}
|
mch := mocks.Client{}
|
||||||
mch.On("GetOAuth2Client", Anything).Return(nil, false, nil)
|
mch.On("GetOAuth2Client", Anything).Return(nil, false, nil)
|
||||||
mch.On("DeleteOAuth2Client", Anything).Return(nil)
|
mch.On("DeleteOAuth2Client", Anything).Return(nil)
|
||||||
mch.On("ListOAuth2Client", Anything).Return(nil, nil)
|
mch.On("ListOAuth2Client", Anything).Return(nil, nil)
|
||||||
@ -299,7 +301,7 @@ var _ = Describe("OAuth2Client Controller", func() {
|
|||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
c := mgr.GetClient()
|
c := mgr.GetClient()
|
||||||
|
|
||||||
mch := mocks.HydraClientInterface{}
|
mch := mocks.Client{}
|
||||||
mch.On("GetOAuth2Client", Anything).Return(nil, false, nil)
|
mch.On("GetOAuth2Client", Anything).Return(nil, false, nil)
|
||||||
mch.On("DeleteOAuth2Client", Anything).Return(nil)
|
mch.On("DeleteOAuth2Client", Anything).Return(nil)
|
||||||
mch.On("ListOAuth2Client", Anything).Return(nil, nil)
|
mch.On("ListOAuth2Client", Anything).Return(nil, nil)
|
||||||
@ -369,7 +371,7 @@ var _ = Describe("OAuth2Client Controller", func() {
|
|||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
c := mgr.GetClient()
|
c := mgr.GetClient()
|
||||||
|
|
||||||
mch := &mocks.HydraClientInterface{}
|
mch := &mocks.Client{}
|
||||||
mch.On("GetOAuth2Client", Anything).Return(nil, false, nil)
|
mch.On("GetOAuth2Client", Anything).Return(nil, false, nil)
|
||||||
mch.On("DeleteOAuth2Client", Anything).Return(nil)
|
mch.On("DeleteOAuth2Client", Anything).Return(nil)
|
||||||
mch.On("ListOAuth2Client", Anything).Return(nil, nil)
|
mch.On("ListOAuth2Client", Anything).Return(nil, nil)
|
||||||
@ -460,12 +462,17 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAPIReconciler(mgr ctrl.Manager, mock controllers.HydraClientInterface) reconcile.Reconciler {
|
func getAPIReconciler(mgr ctrl.Manager, mock hydra.Client) reconcile.Reconciler {
|
||||||
return &controllers.OAuth2ClientReconciler{
|
clientMocker := func(spec hydrav1alpha1.OAuth2ClientSpec, tlsTrustStore string, insecureSkipVerify bool) (hydra.Client, error) {
|
||||||
Client: mgr.GetClient(),
|
return mock, nil
|
||||||
Log: ctrl.Log.WithName("controllers").WithName("OAuth2Client"),
|
|
||||||
HydraClient: mock,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return controllers.New(
|
||||||
|
mgr.GetClient(),
|
||||||
|
mock,
|
||||||
|
ctrl.Log.WithName("controllers").WithName("OAuth2Client"),
|
||||||
|
controllers.WithClientFactory(clientMocker),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testInstance(name, secretName string) *hydrav1alpha1.OAuth2Client {
|
func testInstance(name, secretName string) *hydrav1alpha1.OAuth2Client {
|
||||||
|
@ -8,15 +8,51 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
|
hydrav1alpha1 "github.com/ory/hydra-maester/api/v1alpha1"
|
||||||
|
"github.com/ory/hydra-maester/helpers"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
type Client interface {
|
||||||
|
GetOAuth2Client(id string) (*OAuth2ClientJSON, bool, error)
|
||||||
|
ListOAuth2Client() ([]*OAuth2ClientJSON, error)
|
||||||
|
PostOAuth2Client(o *OAuth2ClientJSON) (*OAuth2ClientJSON, error)
|
||||||
|
PutOAuth2Client(o *OAuth2ClientJSON) (*OAuth2ClientJSON, error)
|
||||||
|
DeleteOAuth2Client(id string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type InternalClient struct {
|
||||||
HydraURL url.URL
|
HydraURL url.URL
|
||||||
HTTPClient *http.Client
|
HTTPClient *http.Client
|
||||||
ForwardedProto string
|
ForwardedProto string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetOAuth2Client(id string) (*OAuth2ClientJSON, bool, error) {
|
// New returns a new hydra InternalClient instance.
|
||||||
|
func New(spec hydrav1alpha1.OAuth2ClientSpec, tlsTrustStore string, insecureSkipVerify bool) (Client, error) {
|
||||||
|
address := fmt.Sprintf("%s:%d", spec.HydraAdmin.URL, spec.HydraAdmin.Port)
|
||||||
|
u, err := url.Parse(address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := helpers.CreateHttpClient(insecureSkipVerify, tlsTrustStore)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &InternalClient{
|
||||||
|
HydraURL: *u.ResolveReference(&url.URL{Path: spec.HydraAdmin.Endpoint}),
|
||||||
|
HTTPClient: c,
|
||||||
|
}
|
||||||
|
|
||||||
|
if spec.HydraAdmin.ForwardedProto != "" && spec.HydraAdmin.ForwardedProto != "off" {
|
||||||
|
client.ForwardedProto = spec.HydraAdmin.ForwardedProto
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *InternalClient) GetOAuth2Client(id string) (*OAuth2ClientJSON, bool, error) {
|
||||||
|
|
||||||
var jsonClient *OAuth2ClientJSON
|
var jsonClient *OAuth2ClientJSON
|
||||||
|
|
||||||
@ -40,7 +76,7 @@ func (c *Client) GetOAuth2Client(id string) (*OAuth2ClientJSON, bool, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) ListOAuth2Client() ([]*OAuth2ClientJSON, error) {
|
func (c *InternalClient) ListOAuth2Client() ([]*OAuth2ClientJSON, error) {
|
||||||
|
|
||||||
var jsonClientList []*OAuth2ClientJSON
|
var jsonClientList []*OAuth2ClientJSON
|
||||||
|
|
||||||
@ -62,7 +98,7 @@ func (c *Client) ListOAuth2Client() ([]*OAuth2ClientJSON, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) PostOAuth2Client(o *OAuth2ClientJSON) (*OAuth2ClientJSON, error) {
|
func (c *InternalClient) PostOAuth2Client(o *OAuth2ClientJSON) (*OAuth2ClientJSON, error) {
|
||||||
|
|
||||||
var jsonClient *OAuth2ClientJSON
|
var jsonClient *OAuth2ClientJSON
|
||||||
|
|
||||||
@ -86,7 +122,7 @@ func (c *Client) PostOAuth2Client(o *OAuth2ClientJSON) (*OAuth2ClientJSON, error
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) PutOAuth2Client(o *OAuth2ClientJSON) (*OAuth2ClientJSON, error) {
|
func (c *InternalClient) PutOAuth2Client(o *OAuth2ClientJSON) (*OAuth2ClientJSON, error) {
|
||||||
|
|
||||||
var jsonClient *OAuth2ClientJSON
|
var jsonClient *OAuth2ClientJSON
|
||||||
|
|
||||||
@ -107,7 +143,7 @@ func (c *Client) PutOAuth2Client(o *OAuth2ClientJSON) (*OAuth2ClientJSON, error)
|
|||||||
return jsonClient, nil
|
return jsonClient, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) DeleteOAuth2Client(id string) error {
|
func (c *InternalClient) DeleteOAuth2Client(id string) error {
|
||||||
|
|
||||||
req, err := c.newRequest(http.MethodDelete, id, nil)
|
req, err := c.newRequest(http.MethodDelete, id, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -123,14 +159,14 @@ func (c *Client) DeleteOAuth2Client(id string) error {
|
|||||||
case http.StatusNoContent:
|
case http.StatusNoContent:
|
||||||
return nil
|
return nil
|
||||||
case http.StatusNotFound:
|
case http.StatusNotFound:
|
||||||
fmt.Printf("client with id %s does not exist", id)
|
fmt.Printf("InternalClient with id %s does not exist", id)
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("%s %s http request returned unexpected status code %s", req.Method, req.URL.String(), resp.Status)
|
return fmt.Errorf("%s %s http request returned unexpected status code %s", req.Method, req.URL.String(), resp.Status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) newRequest(method, relativePath string, body interface{}) (*http.Request, error) {
|
func (c *InternalClient) newRequest(method, relativePath string, body interface{}) (*http.Request, error) {
|
||||||
|
|
||||||
var buf io.ReadWriter
|
var buf io.ReadWriter
|
||||||
if body != nil {
|
if body != nil {
|
||||||
@ -162,7 +198,7 @@ func (c *Client) newRequest(method, relativePath string, body interface{}) (*htt
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) do(req *http.Request, v interface{}) (*http.Response, error) {
|
func (c *InternalClient) do(req *http.Request, v interface{}) (*http.Response, error) {
|
||||||
resp, err := c.HTTPClient.Do(req)
|
resp, err := c.HTTPClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -12,9 +12,10 @@ import (
|
|||||||
|
|
||||||
"k8s.io/utils/pointer"
|
"k8s.io/utils/pointer"
|
||||||
|
|
||||||
"github.com/ory/hydra-maester/hydra"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/ory/hydra-maester/hydra"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -60,7 +61,7 @@ func TestCRUD(t *testing.T) {
|
|||||||
|
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
c := hydra.Client{
|
c := hydra.InternalClient{
|
||||||
HTTPClient: &http.Client{},
|
HTTPClient: &http.Client{},
|
||||||
HydraURL: url.URL{Scheme: schemeHTTP},
|
HydraURL: url.URL{Scheme: schemeHTTP},
|
||||||
}
|
}
|
||||||
@ -397,7 +398,7 @@ func TestCRUD(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func runServer(c *hydra.Client, h http.HandlerFunc) {
|
func runServer(c *hydra.InternalClient, h http.HandlerFunc) {
|
||||||
s := httptest.NewServer(h)
|
s := httptest.NewServer(h)
|
||||||
serverUrl, _ := url.Parse(s.URL)
|
serverUrl, _ := url.Parse(s.URL)
|
||||||
c.HydraURL = *serverUrl.ResolveReference(&url.URL{Path: clientsEndpoint})
|
c.HydraURL = *serverUrl.ResolveReference(&url.URL{Path: clientsEndpoint})
|
||||||
|
@ -2,8 +2,12 @@ package hydra
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
"k8s.io/utils/pointer"
|
"k8s.io/utils/pointer"
|
||||||
|
|
||||||
|
hydrav1alpha1 "github.com/ory/hydra-maester/api/v1alpha1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// OAuth2ClientJSON represents an OAuth2 client digestible by ORY Hydra
|
// OAuth2ClientJSON represents an OAuth2 client digestible by ORY Hydra
|
||||||
@ -23,7 +27,8 @@ type OAuth2ClientJSON struct {
|
|||||||
Metadata json.RawMessage `json:"metadata,omitempty"`
|
Metadata json.RawMessage `json:"metadata,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Oauth2ClientCredentials represents client ID and password fetched from a Kubernetes secret
|
// Oauth2ClientCredentials represents client ID and password fetched from a
|
||||||
|
// Kubernetes secret
|
||||||
type Oauth2ClientCredentials struct {
|
type Oauth2ClientCredentials struct {
|
||||||
ID []byte
|
ID []byte
|
||||||
Password []byte
|
Password []byte
|
||||||
@ -36,3 +41,49 @@ func (oj *OAuth2ClientJSON) WithCredentials(credentials *Oauth2ClientCredentials
|
|||||||
}
|
}
|
||||||
return oj
|
return oj
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FromOAuth2Client converts an OAuth2Client into a OAuth2ClientJSON object that represents an OAuth2 InternalClient digestible by ORY Hydra
|
||||||
|
func FromOAuth2Client(c *hydrav1alpha1.OAuth2Client) (*OAuth2ClientJSON, error) {
|
||||||
|
meta, err := json.Marshal(c.Spec.Metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "unable to encode `metadata` property value to json")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &OAuth2ClientJSON{
|
||||||
|
ClientName: c.Spec.ClientName,
|
||||||
|
GrantTypes: grantToStringSlice(c.Spec.GrantTypes),
|
||||||
|
ResponseTypes: responseToStringSlice(c.Spec.ResponseTypes),
|
||||||
|
RedirectURIs: redirectToStringSlice(c.Spec.RedirectURIs),
|
||||||
|
PostLogoutRedirectURIs: redirectToStringSlice(c.Spec.PostLogoutRedirectURIs),
|
||||||
|
AllowedCorsOrigins: redirectToStringSlice(c.Spec.AllowedCorsOrigins),
|
||||||
|
Audience: c.Spec.Audience,
|
||||||
|
Scope: c.Spec.Scope,
|
||||||
|
Owner: fmt.Sprintf("%s/%s", c.Name, c.Namespace),
|
||||||
|
TokenEndpointAuthMethod: string(c.Spec.TokenEndpointAuthMethod),
|
||||||
|
Metadata: meta,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func responseToStringSlice(rt []hydrav1alpha1.ResponseType) []string {
|
||||||
|
var output = make([]string, len(rt))
|
||||||
|
for i, elem := range rt {
|
||||||
|
output[i] = string(elem)
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
func grantToStringSlice(gt []hydrav1alpha1.GrantType) []string {
|
||||||
|
var output = make([]string, len(gt))
|
||||||
|
for i, elem := range gt {
|
||||||
|
output[i] = string(elem)
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
func redirectToStringSlice(ru []hydrav1alpha1.RedirectURI) []string {
|
||||||
|
var output = make([]string, len(ru))
|
||||||
|
for i, elem := range ru {
|
||||||
|
output[i] = string(elem)
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
47
main.go
47
main.go
@ -18,21 +18,19 @@ package main
|
|||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ory/hydra-maester/helpers"
|
|
||||||
|
|
||||||
"github.com/ory/hydra-maester/hydra"
|
"github.com/ory/hydra-maester/hydra"
|
||||||
|
|
||||||
hydrav1alpha1 "github.com/ory/hydra-maester/api/v1alpha1"
|
|
||||||
"github.com/ory/hydra-maester/controllers"
|
|
||||||
apiv1 "k8s.io/api/core/v1"
|
apiv1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||||
ctrl "sigs.k8s.io/controller-runtime"
|
ctrl "sigs.k8s.io/controller-runtime"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||||
|
|
||||||
|
hydrav1alpha1 "github.com/ory/hydra-maester/api/v1alpha1"
|
||||||
|
"github.com/ory/hydra-maester/controllers"
|
||||||
// +kubebuilder:scaffold:imports
|
// +kubebuilder:scaffold:imports
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -109,19 +107,19 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hydraClient, err := getHydraClient(defaultSpec, tlsTrustStore, insecureSkipVerify)
|
hydraClient, err := hydra.New(defaultSpec, tlsTrustStore, insecureSkipVerify)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
setupLog.Error(err, "making default hydra client", "controller", "OAuth2Client")
|
setupLog.Error(err, "making default hydra client", "controller", "OAuth2Client")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = (&controllers.OAuth2ClientReconciler{
|
err = controllers.New(
|
||||||
Client: mgr.GetClient(),
|
mgr.GetClient(),
|
||||||
Log: ctrl.Log.WithName("controllers").WithName("OAuth2Client"),
|
hydraClient,
|
||||||
HydraClient: hydraClient,
|
ctrl.Log.WithName("controllers").WithName("OAuth2Client"),
|
||||||
ControllerNamespace: namespace,
|
controllers.WithNamespace(namespace),
|
||||||
}).SetupWithManager(mgr)
|
).SetupWithManager(mgr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
setupLog.Error(err, "unable to create controller", "controller", "OAuth2Client")
|
setupLog.Error(err, "unable to create controller", "controller", "OAuth2Client")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@ -134,28 +132,3 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getHydraClient(spec hydrav1alpha1.OAuth2ClientSpec, tlsTrustStore string, insecureSkipVerify bool) (controllers.HydraClientInterface, error) {
|
|
||||||
|
|
||||||
address := fmt.Sprintf("%s:%d", spec.HydraAdmin.URL, spec.HydraAdmin.Port)
|
|
||||||
u, err := url.Parse(address)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := helpers.CreateHttpClient(insecureSkipVerify, tlsTrustStore)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
client := &hydra.Client{
|
|
||||||
HydraURL: *u.ResolveReference(&url.URL{Path: spec.HydraAdmin.Endpoint}),
|
|
||||||
HTTPClient: c,
|
|
||||||
}
|
|
||||||
|
|
||||||
if spec.HydraAdmin.ForwardedProto != "" && spec.HydraAdmin.ForwardedProto != "off" {
|
|
||||||
client.ForwardedProto = spec.HydraAdmin.ForwardedProto
|
|
||||||
}
|
|
||||||
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user