Skip to content
This repository has been archived by the owner on Dec 21, 2023. It is now read-only.

Commit

Permalink
feat: added oauthutils package (#395)
Browse files Browse the repository at this point in the history
  • Loading branch information
warber authored Feb 17, 2022
1 parent 6b99172 commit f30183e
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 0 deletions.
98 changes: 98 additions & 0 deletions pkg/common/oauth2/discovery.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package oauthutils

import (
"context"
"encoding/json"
"fmt"
"net/http"
"time"
)

// OauthLocationGetter is used to get the location parameters
// used in an oauth flow
type OauthLocationGetter interface {
// Discover is responsible for determining the parameters used for an oauth flow
// and returns them as a OauthDiscoveryResult
Discover(ctx context.Context, discoveryURL string) (*OauthDiscoveryResult, error)
}

// NewOauthDiscovery creates a new OauthDiscovery
func NewOauthDiscovery(client HTTPClient) *OauthDiscovery {
return &OauthDiscovery{
c: client,
}
}

// OauthDiscoveryResult is the result of a OauthLocation discovery call
// and contains all the parameters usable for a following oauth flow
type OauthDiscoveryResult struct {
Issuer string `json:"issuer"`
AuthorizationEndpoint string `json:"authorization_endpoint"`
TokenEndpoint string `json:"token_endpoint"`
UserinfoEndpoint string `json:"userinfo_endpoint"`
EndSessionEndpoint string `json:"end_session_endpoint"`
ResponseTypesSupported []string `json:"response_types_supported"`
GrantTypesSupported []string `json:"grant_types_supported"`
JwksURI string `json:"jwks_uri"`
IntrospectionEndpoint string `json:"introspection_endpoint"`
}

// OauthDiscovery is an implementation of OauthLocationGetter which calls
// a known URL to get the parameters
type OauthDiscovery struct {
c HTTPClient
}

// Discover starts the OAuth discovery and eventually returns the results
func (d OauthDiscovery) Discover(ctx context.Context, discoveryURL string) (*OauthDiscoveryResult, error) {
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, discoveryURL, nil)
if err != nil {
return nil, err
}
resp, err := d.c.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf(http.StatusText(resp.StatusCode))
}

var result OauthDiscoveryResult
return &result, json.NewDecoder(resp.Body).Decode(&result)
}

// StaticOauthDiscovery has a static/hard-coded set of oauth parameters
// and does not actually do a discovery call to get the parameters but just returns
// the hard-coded values as OauthDiscoveryResult
type StaticOauthDiscovery struct {
DiscoveryValues *OauthDiscoveryResult
}

// Discover tries to determine the parameters used for an oauth flow
// and returns them as a OauthDiscoveryResult
func (d StaticOauthDiscovery) Discover(ctx context.Context, discoveryURL string) (*OauthDiscoveryResult, error) {
return d.DiscoveryValues, nil
}

// OauthDiscoveryMock is an implementation of OauthLocationGetter usable
// as a mock implementation in tests
type OauthDiscoveryMock struct {
DiscoverFn func(context.Context, string) (*OauthDiscoveryResult, error)
}

// Discover calls the mocked function of the OauthDiscoveryMock
func (o *OauthDiscoveryMock) Discover(ctx context.Context, discoveryURL string) (*OauthDiscoveryResult, error) {
if o != nil && o.DiscoverFn != nil {
return o.DiscoverFn(ctx, discoveryURL)
}
return &OauthDiscoveryResult{}, nil
}

// HTTPClient is an interface that models *http.Client.
type HTTPClient interface {
Do(req *http.Request) (*http.Response, error)
}
69 changes: 69 additions & 0 deletions pkg/common/oauth2/discovery_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package oauthutils

import (
"context"
"fmt"
"github.com/keptn/go-utils/pkg/common/testutils"
"github.com/stretchr/testify/assert"
"io"
"net/http"
"strings"
"testing"
)

func TestDiscovery(t *testing.T) {
response := `{"issuer":"https://issuer.com:443","authorization_endpoint":"https://endpoint.com:443/oauth2/authorize","token_endpoint":"https://tokenendpoint.com:443/sso/oauth2/token","userinfo_endpoint":"https://userinfo.com:443/sso/oauth2/userinfo","end_session_endpoint":"https://endsession.com:443/oauth2/end_session","response_types_supported":["code"],"grant_types_supported":["authorization_code","refresh_token","password","client_credentials","urn:ietf:params:oauth:grant-type:token-exchange"],"jwks_uri":"https://jwksuri.com:443/.well-known/jwks.json","introspection_endpoint":"https://introspection.com:443/sso/oauth2/tokeninfo","subject_types_supported":["public"],"id_token_signing_alg_values_supported":["ECDSA256"]}`
client := &testutils.HTTPClientMock{}
discovery := NewOauthDiscovery(client)
tt := []struct {
Body string
StatusCode int
expResult *OauthDiscoveryResult
expErr error
}{
{
Body: response,
StatusCode: 200,
expResult: &OauthDiscoveryResult{
Issuer: "https://issuer.com:443",
AuthorizationEndpoint: "https://endpoint.com:443/oauth2/authorize",
TokenEndpoint: "https://tokenendpoint.com:443/sso/oauth2/token",
UserinfoEndpoint: "https://userinfo.com:443/sso/oauth2/userinfo",
EndSessionEndpoint: "https://endsession.com:443/oauth2/end_session",
ResponseTypesSupported: []string{"code"},
GrantTypesSupported: []string{"authorization_code", "refresh_token", "password", "client_credentials", "urn:ietf:params:oauth:grant-type:token-exchange"},
JwksURI: "https://jwksuri.com:443/.well-known/jwks.json",
IntrospectionEndpoint: "https://introspection.com:443/sso/oauth2/tokeninfo",
},
expErr: nil,
}, {
Body: "",
StatusCode: http.StatusNotFound,
expResult: nil,
expErr: fmt.Errorf(http.StatusText(http.StatusNotFound)),
}, {
Body: "",
StatusCode: http.StatusBadRequest,
expResult: nil,
expErr: fmt.Errorf(http.StatusText(http.StatusBadRequest)),
},
}

for _, test := range tt {
client.DoFunc = func(r *http.Request) (*http.Response, error) {
return &http.Response{
Body: io.NopCloser(strings.NewReader(test.Body)),
StatusCode: test.StatusCode,
}, nil
}
r, err := discovery.Discover(context.Background(), "http://well-known-discovery-url.com")

assert.Equal(t, test.expErr, err)
assert.Equal(t, test.expResult, r)
}

_, err := discovery.Discover(context.TODO(), "http://well-known-discovery-url.com")
if err != nil {
return
}
}

0 comments on commit f30183e

Please sign in to comment.