This repository has been archived by the owner on Dec 21, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: added oauthutils package (#395)
- Loading branch information
Showing
2 changed files
with
167 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |