// Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package hydra import ( "bytes" "encoding/json" "fmt" "io" "net/http" "net/url" "path" hydrav1alpha1 "github.com/ory/hydra-maester/api/v1alpha1" "github.com/ory/hydra-maester/helpers" ) 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 HTTPClient *http.Client ForwardedProto string } // 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 req, err := c.newRequest(http.MethodGet, id, nil) if err != nil { return nil, false, err } resp, err := c.do(req, &jsonClient) if err != nil { return nil, false, err } switch resp.StatusCode { case http.StatusOK: return jsonClient, true, nil case http.StatusNotFound, http.StatusUnauthorized: return nil, false, nil default: return nil, false, fmt.Errorf("%s %s http request returned unexpected status code %s", req.Method, req.URL.String(), resp.Status) } } func (c *InternalClient) ListOAuth2Client() ([]*OAuth2ClientJSON, error) { var jsonClientList []*OAuth2ClientJSON req, err := c.newRequest(http.MethodGet, "", nil) if err != nil { return nil, err } resp, err := c.do(req, &jsonClientList) if err != nil { return nil, err } switch resp.StatusCode { case http.StatusOK: return jsonClientList, nil default: return nil, fmt.Errorf("%s %s http request returned unexpected status code %s", req.Method, req.URL.String(), resp.Status) } } func (c *InternalClient) PostOAuth2Client(o *OAuth2ClientJSON) (*OAuth2ClientJSON, error) { var jsonClient *OAuth2ClientJSON req, err := c.newRequest(http.MethodPost, "", o) if err != nil { return nil, err } resp, err := c.do(req, &jsonClient) if err != nil { return nil, err } switch resp.StatusCode { case http.StatusCreated: return jsonClient, nil case http.StatusConflict: return nil, fmt.Errorf("%s %s http request failed: requested ID already exists", req.Method, req.URL) default: return nil, fmt.Errorf("%s %s http request returned unexpected status code: %s", req.Method, req.URL, resp.Status) } } func (c *InternalClient) PutOAuth2Client(o *OAuth2ClientJSON) (*OAuth2ClientJSON, error) { var jsonClient *OAuth2ClientJSON req, err := c.newRequest(http.MethodPut, *o.ClientID, o) if err != nil { return nil, err } resp, err := c.do(req, &jsonClient) if err != nil { return nil, err } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("%s %s http request returned unexpected status code: %s", req.Method, req.URL, resp.Status) } return jsonClient, nil } func (c *InternalClient) DeleteOAuth2Client(id string) error { req, err := c.newRequest(http.MethodDelete, id, nil) if err != nil { return err } resp, err := c.do(req, nil) if err != nil { return err } switch resp.StatusCode { case http.StatusNoContent: return nil case http.StatusNotFound: fmt.Printf("InternalClient with id %s does not exist", id) return nil default: return fmt.Errorf("%s %s http request returned unexpected status code %s", req.Method, req.URL.String(), resp.Status) } } func (c *InternalClient) newRequest(method, relativePath string, body interface{}) (*http.Request, error) { var buf io.ReadWriter if body != nil { buf = new(bytes.Buffer) err := json.NewEncoder(buf).Encode(body) if err != nil { return nil, err } } u := c.HydraURL u.Path = path.Join(u.Path, relativePath) req, err := http.NewRequest(method, u.String(), buf) if err != nil { return nil, err } if c.ForwardedProto != "" { req.Header.Add("X-Forwarded-Proto", c.ForwardedProto) } if body != nil { req.Header.Set("Content-Type", "application/json") } req.Header.Set("Accept", "application/json") return req, nil } func (c *InternalClient) do(req *http.Request, v interface{}) (*http.Response, error) { resp, err := c.HTTPClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if v != nil && resp.StatusCode < 300 { err = json.NewDecoder(resp.Body).Decode(v) } return resp, err }