Merge branch 'master' into scopes
# Conflicts: # pkg/op/authrequest.go # pkg/op/authrequest_test.go
This commit is contained in:
commit
007a68d861
20 changed files with 298 additions and 209 deletions
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
|
@ -28,6 +28,10 @@ jobs:
|
||||||
uses: actions/checkout@v1
|
uses: actions/checkout@v1
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
- name: Create Version
|
- name: Semantic Release
|
||||||
uses: caos/semantic-release@v0.2.4
|
uses: cycjimmy/semantic-release-action@v2
|
||||||
|
with:
|
||||||
|
dry_run: false
|
||||||
|
semantic_version: 17.0.4
|
||||||
|
extra_plugins: |
|
||||||
|
@semantic-release/exec@5.0.0
|
||||||
|
|
|
@ -195,14 +195,14 @@ func (s *AuthStorage) GetClientByClientID(_ context.Context, id string) (op.Clie
|
||||||
authMethod = op.AuthMethodNone
|
authMethod = op.AuthMethodNone
|
||||||
accessTokenType = op.AccessTokenTypeJWT
|
accessTokenType = op.AccessTokenTypeJWT
|
||||||
}
|
}
|
||||||
return &ConfClient{ID: id, applicationType: appType, authMethod: authMethod, accessTokenType: accessTokenType}, nil
|
return &ConfClient{ID: id, applicationType: appType, authMethod: authMethod, accessTokenType: accessTokenType, devMode: false}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AuthStorage) AuthorizeClientIDSecret(_ context.Context, id string, _ string) error {
|
func (s *AuthStorage) AuthorizeClientIDSecret(_ context.Context, id string, _ string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AuthStorage) GetUserinfoFromToken(ctx context.Context, _ string) (*oidc.Userinfo, error) {
|
func (s *AuthStorage) GetUserinfoFromToken(ctx context.Context, _, _ string) (*oidc.Userinfo, error) {
|
||||||
return s.GetUserinfoFromScopes(ctx, "", []string{})
|
return s.GetUserinfoFromScopes(ctx, "", []string{})
|
||||||
}
|
}
|
||||||
func (s *AuthStorage) GetUserinfoFromScopes(_ context.Context, _ string, _ []string) (*oidc.Userinfo, error) {
|
func (s *AuthStorage) GetUserinfoFromScopes(_ context.Context, _ string, _ []string) (*oidc.Userinfo, error) {
|
||||||
|
@ -232,8 +232,10 @@ func (s *AuthStorage) GetUserinfoFromScopes(_ context.Context, _ string, _ []str
|
||||||
type ConfClient struct {
|
type ConfClient struct {
|
||||||
applicationType op.ApplicationType
|
applicationType op.ApplicationType
|
||||||
authMethod op.AuthMethod
|
authMethod op.AuthMethod
|
||||||
|
responseTypes []oidc.ResponseType
|
||||||
ID string
|
ID string
|
||||||
accessTokenType op.AccessTokenType
|
accessTokenType op.AccessTokenType
|
||||||
|
devMode bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConfClient) GetID() string {
|
func (c *ConfClient) GetID() string {
|
||||||
|
@ -262,7 +264,7 @@ func (c *ConfClient) ApplicationType() op.ApplicationType {
|
||||||
return c.applicationType
|
return c.applicationType
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConfClient) GetAuthMethod() op.AuthMethod {
|
func (c *ConfClient) AuthMethod() op.AuthMethod {
|
||||||
return c.authMethod
|
return c.authMethod
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,3 +274,10 @@ func (c *ConfClient) IDTokenLifetime() time.Duration {
|
||||||
func (c *ConfClient) AccessTokenType() op.AccessTokenType {
|
func (c *ConfClient) AccessTokenType() op.AccessTokenType {
|
||||||
return c.accessTokenType
|
return c.accessTokenType
|
||||||
}
|
}
|
||||||
|
func (c *ConfClient) ResponseTypes() []oidc.ResponseType {
|
||||||
|
return c.responseTypes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConfClient) DevMode() bool {
|
||||||
|
return c.devMode
|
||||||
|
}
|
||||||
|
|
6
go.mod
6
go.mod
|
@ -4,12 +4,12 @@ go 1.13
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/caos/logging v0.0.0-20191210002624-b3260f690a6a
|
github.com/caos/logging v0.0.0-20191210002624-b3260f690a6a
|
||||||
github.com/golang/mock v1.4.3
|
github.com/golang/mock v1.4.4
|
||||||
github.com/google/go-cmp v0.4.1 // indirect
|
github.com/google/go-cmp v0.4.1 // indirect
|
||||||
github.com/google/go-github/v31 v31.0.0
|
github.com/google/go-github/v31 v31.0.0
|
||||||
github.com/google/uuid v1.1.1
|
github.com/google/uuid v1.1.2
|
||||||
github.com/gorilla/handlers v1.4.2
|
github.com/gorilla/handlers v1.4.2
|
||||||
github.com/gorilla/mux v1.7.4
|
github.com/gorilla/mux v1.8.0
|
||||||
github.com/gorilla/schema v1.1.0
|
github.com/gorilla/schema v1.1.0
|
||||||
github.com/gorilla/securecookie v1.1.1
|
github.com/gorilla/securecookie v1.1.1
|
||||||
github.com/kr/pretty v0.1.0 // indirect
|
github.com/kr/pretty v0.1.0 // indirect
|
||||||
|
|
6
go.sum
6
go.sum
|
@ -6,6 +6,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw=
|
github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw=
|
||||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
|
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
|
||||||
|
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||||
|
@ -18,10 +20,14 @@ github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASu
|
||||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||||
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg=
|
github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg=
|
||||||
github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||||
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
|
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
|
||||||
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||||
|
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||||
|
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||||
github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY=
|
github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY=
|
||||||
github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
|
github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
|
||||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||||
|
|
|
@ -20,5 +20,6 @@ type DiscoveryConfiguration struct {
|
||||||
SubjectTypesSupported []string `json:"subject_types_supported,omitempty"`
|
SubjectTypesSupported []string `json:"subject_types_supported,omitempty"`
|
||||||
IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported,omitempty"`
|
IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported,omitempty"`
|
||||||
TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported,omitempty"`
|
TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported,omitempty"`
|
||||||
|
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported,omitempty"`
|
||||||
ClaimsSupported []string `json:"claims_supported,omitempty"`
|
ClaimsSupported []string `json:"claims_supported,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,13 +80,17 @@ func ParseAuthorizeRequest(r *http.Request, decoder utils.Decoder) (*oidc.AuthRe
|
||||||
}
|
}
|
||||||
|
|
||||||
func ValidateAuthRequest(ctx context.Context, authReq *oidc.AuthRequest, storage Storage, verifier rp.Verifier) (string, error) {
|
func ValidateAuthRequest(ctx context.Context, authReq *oidc.AuthRequest, storage Storage, verifier rp.Verifier) (string, error) {
|
||||||
|
client, err := storage.GetClientByClientID(ctx, authReq.ClientID)
|
||||||
|
if err != nil {
|
||||||
|
return "", ErrServerError(err.Error())
|
||||||
|
}
|
||||||
if err := ValidateAuthReqScopes(authReq.Scopes); err != nil {
|
if err := ValidateAuthReqScopes(authReq.Scopes); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if err := ValidateAuthReqRedirectURI(ctx, authReq.RedirectURI, authReq.ClientID, authReq.ResponseType, storage); err != nil {
|
if err := ValidateAuthReqRedirectURI(client, authReq.RedirectURI, authReq.ResponseType); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if err := ValidateAuthReqResponseType(authReq.ResponseType); err != nil {
|
if err := ValidateAuthReqResponseType(client, authReq.ResponseType); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return ValidateAuthReqIDTokenHint(ctx, authReq.IDTokenHint, verifier)
|
return ValidateAuthReqIDTokenHint(ctx, authReq.IDTokenHint, verifier)
|
||||||
|
@ -94,24 +98,23 @@ func ValidateAuthRequest(ctx context.Context, authReq *oidc.AuthRequest, storage
|
||||||
|
|
||||||
func ValidateAuthReqScopes(scopes []string) error {
|
func ValidateAuthReqScopes(scopes []string) error {
|
||||||
if len(scopes) == 0 {
|
if len(scopes) == 0 {
|
||||||
return ErrInvalidRequest("Unfortunately, the scope parameter of your request is missing. Please ensure your scope value is not empty, and try again. If you have any questions, you may contact the administrator of the application.")
|
return ErrInvalidRequest("The scope of your request is missing. Please ensure some scopes are requested. If you have any questions, you may contact the administrator of the application.")
|
||||||
}
|
}
|
||||||
if !utils.Contains(scopes, oidc.ScopeOpenID) {
|
if !utils.Contains(scopes, oidc.ScopeOpenID) {
|
||||||
return ErrInvalidRequest("Unfortunately, the scope `openid` is missing. Please ensure your scope configuration is correct (containing the `openid` value), and try again. If you have any questions, you may contact the administrator of the application.")
|
return ErrInvalidRequest("The scope openid is missing in your request. Please ensure the scope openid is added to the request. If you have any questions, you may contact the administrator of the application.")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ValidateAuthReqRedirectURI(ctx context.Context, uri, clientID string, responseType oidc.ResponseType, storage OPStorage) error {
|
func ValidateAuthReqRedirectURI(client Client, uri string, responseType oidc.ResponseType) error {
|
||||||
if uri == "" {
|
if uri == "" {
|
||||||
return ErrInvalidRequestRedirectURI("Unfortunately, the client's redirect_uri is missing. Please ensure your redirect_uri is included in the request, and try again. If you have any questions, you may contact the administrator of the application.")
|
return ErrInvalidRequestRedirectURI("The redirect_uri is missing in the request. Please ensure it is added to the request. If you have any questions, you may contact the administrator of the application.")
|
||||||
}
|
|
||||||
client, err := storage.GetClientByClientID(ctx, clientID)
|
|
||||||
if err != nil {
|
|
||||||
return ErrServerError(err.Error())
|
|
||||||
}
|
}
|
||||||
if !utils.Contains(client.RedirectURIs(), uri) {
|
if !utils.Contains(client.RedirectURIs(), uri) {
|
||||||
return ErrInvalidRequestRedirectURI("Unfortunately, the redirect_uri is missing in the client configuration. Please ensure your redirect_uri is added in the client configuration, and try again. If you have any questions, you may contact the administrator of the application.")
|
return ErrInvalidRequestRedirectURI("The requested redirect_uri is missing in the client configuration. If you have any questions, you may contact the administrator of the application.")
|
||||||
|
}
|
||||||
|
if client.DevMode() {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(uri, "https://") {
|
if strings.HasPrefix(uri, "https://") {
|
||||||
return nil
|
return nil
|
||||||
|
@ -120,32 +123,29 @@ func ValidateAuthReqRedirectURI(ctx context.Context, uri, clientID string, respo
|
||||||
if strings.HasPrefix(uri, "http://") && IsConfidentialType(client) {
|
if strings.HasPrefix(uri, "http://") && IsConfidentialType(client) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if client.ApplicationType() == ApplicationTypeNative {
|
if !strings.HasPrefix(uri, "http://") && client.ApplicationType() == ApplicationTypeNative {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return ErrInvalidRequest("Unfortunately, this client's redirect_uri is http and is not allowed. If you have any questions, you may contact the administrator of the application.")
|
return ErrInvalidRequest("This client's redirect_uri is http and is not allowed. If you have any questions, you may contact the administrator of the application.")
|
||||||
} else {
|
} else {
|
||||||
if client.ApplicationType() != ApplicationTypeNative {
|
if client.ApplicationType() != ApplicationTypeNative {
|
||||||
return ErrInvalidRequestRedirectURI("Unfortunately, http is only allowed for native applications. Please change your redirect uri configuration and try again. If you have any questions, you may contact the administrator of the application.")
|
return ErrInvalidRequestRedirectURI("Http is only allowed for native applications. Please change your redirect uri try again. If you have any questions, you may contact the administrator of the application.")
|
||||||
}
|
}
|
||||||
if !(strings.HasPrefix(uri, "http://localhost:") || strings.HasPrefix(uri, "http://localhost/")) {
|
if !(strings.HasPrefix(uri, "http://localhost:") || strings.HasPrefix(uri, "http://localhost/")) {
|
||||||
return ErrInvalidRequestRedirectURI("Unfortunately, http is only allowed for localhost url. Please change your redirect uri configuration and try again. If you have any questions, you may contact the administrator of the application at:")
|
return ErrInvalidRequestRedirectURI("Http is only allowed for localhost uri. Please change your redirect uri try again. If you have any questions, you may contact the administrator of the application at:")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ValidateAuthReqResponseType(responseType oidc.ResponseType) error {
|
func ValidateAuthReqResponseType(client Client, responseType oidc.ResponseType) error {
|
||||||
switch responseType {
|
if responseType == "" {
|
||||||
case oidc.ResponseTypeCode,
|
return ErrInvalidRequest("The response type is missing in your request. If you have any questions, you may contact the administrator of the application.")
|
||||||
oidc.ResponseTypeIDToken,
|
|
||||||
oidc.ResponseTypeIDTokenOnly:
|
|
||||||
return nil
|
|
||||||
case "":
|
|
||||||
return ErrInvalidRequest("Unfortunately, the response type is missing in your request. Please ensure the response type is complete and accurate, and try again. If you have any questions, you may contact the administrator of the application.")
|
|
||||||
default:
|
|
||||||
return ErrInvalidRequest("Unfortunately, the response type provided in your request is invalid. Please ensure the response type is valid, and try again. If you have any questions, you may contact the administrator of the application.")
|
|
||||||
}
|
}
|
||||||
|
if !ContainsResponseType(client.ResponseTypes(), responseType) {
|
||||||
|
return ErrInvalidRequest("The requested response type is missing in the client configuration. If you have any questions, you may contact the administrator of the application.")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ValidateAuthReqIDTokenHint(ctx context.Context, idTokenHint string, verifier rp.Verifier) (string, error) {
|
func ValidateAuthReqIDTokenHint(ctx context.Context, idTokenHint string, verifier rp.Verifier) (string, error) {
|
||||||
|
@ -154,7 +154,7 @@ func ValidateAuthReqIDTokenHint(ctx context.Context, idTokenHint string, verifie
|
||||||
}
|
}
|
||||||
claims, err := verifier.VerifyIdToken(ctx, idTokenHint)
|
claims, err := verifier.VerifyIdToken(ctx, idTokenHint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", ErrInvalidRequest("Unfortunately, the id_token_hint is invalid. Please ensure the id_token_hint is complete and accurate, and try again. If you have any questions, you may contact the administrator of the application.")
|
return "", ErrInvalidRequest("The id_token_hint is invalid. If you have any questions, you may contact the administrator of the application.")
|
||||||
}
|
}
|
||||||
return claims.Subject, nil
|
return claims.Subject, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -158,27 +158,27 @@ func TestValidateAuthRequest(t *testing.T) {
|
||||||
// }
|
// }
|
||||||
{
|
{
|
||||||
"scope missing fails",
|
"scope missing fails",
|
||||||
args{&oidc.AuthRequest{}, nil, nil},
|
args{&oidc.AuthRequest{}, mock.NewMockStorageExpectValidClientID(t), nil},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"scope openid missing fails",
|
"scope openid missing fails",
|
||||||
args{&oidc.AuthRequest{Scopes: []string{"profile"}}, nil, nil},
|
args{&oidc.AuthRequest{Scopes: []string{"profile"}}, mock.NewMockStorageExpectValidClientID(t), nil},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"response_type missing fails",
|
"response_type missing fails",
|
||||||
args{&oidc.AuthRequest{Scopes: []string{"openid"}}, nil, nil},
|
args{&oidc.AuthRequest{Scopes: []string{"openid"}}, mock.NewMockStorageExpectValidClientID(t), nil},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"client_id missing fails",
|
"client_id missing fails",
|
||||||
args{&oidc.AuthRequest{Scopes: []string{"openid"}, ResponseType: oidc.ResponseTypeCode}, nil, nil},
|
args{&oidc.AuthRequest{Scopes: []string{"openid"}, ResponseType: oidc.ResponseTypeCode}, mock.NewMockStorageExpectValidClientID(t), nil},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"redirect_uri missing fails",
|
"redirect_uri missing fails",
|
||||||
args{&oidc.AuthRequest{Scopes: []string{"openid"}, ResponseType: oidc.ResponseTypeCode, ClientID: "client_id"}, nil, nil},
|
args{&oidc.AuthRequest{Scopes: []string{"openid"}, ResponseType: oidc.ResponseTypeCode, ClientID: "client_id"}, mock.NewMockStorageExpectValidClientID(t), nil},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -223,9 +223,8 @@ func TestValidateAuthReqScopes(t *testing.T) {
|
||||||
func TestValidateAuthReqRedirectURI(t *testing.T) {
|
func TestValidateAuthReqRedirectURI(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
uri string
|
uri string
|
||||||
clientID string
|
client op.Client
|
||||||
responseType oidc.ResponseType
|
responseType oidc.ResponseType
|
||||||
storage op.OPStorage
|
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -234,68 +233,106 @@ func TestValidateAuthReqRedirectURI(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"empty fails",
|
"empty fails",
|
||||||
args{"", "", oidc.ResponseTypeCode, nil},
|
args{"",
|
||||||
|
mock.NewClientWithConfig(t, []string{"https://registered.com/callback"}, op.ApplicationTypeWeb, nil, false),
|
||||||
|
oidc.ResponseTypeCode},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"unregistered fails",
|
"unregistered fails",
|
||||||
args{"https://unregistered.com/callback", "web_client", oidc.ResponseTypeCode, mock.NewMockStorageExpectValidClientID(t)},
|
args{"https://unregistered.com/callback",
|
||||||
true,
|
mock.NewClientWithConfig(t, []string{"https://registered.com/callback"}, op.ApplicationTypeWeb, nil, false),
|
||||||
},
|
oidc.ResponseTypeCode},
|
||||||
{
|
|
||||||
"storage error fails",
|
|
||||||
args{"https://registered.com/callback", "non_client", oidc.ResponseTypeIDToken, mock.NewMockStorageExpectInvalidClientID(t)},
|
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code flow registered http not confidential fails",
|
"code flow registered http not confidential fails",
|
||||||
args{"http://registered.com/callback", "useragent_client", oidc.ResponseTypeCode, mock.NewMockStorageExpectValidClientID(t)},
|
args{"http://registered.com/callback",
|
||||||
|
mock.NewClientWithConfig(t, []string{"http://registered.com/callback"}, op.ApplicationTypeUserAgent, nil, false),
|
||||||
|
oidc.ResponseTypeCode},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code flow registered http confidential ok",
|
"code flow registered http confidential ok",
|
||||||
args{"http://registered.com/callback", "web_client", oidc.ResponseTypeCode, mock.NewMockStorageExpectValidClientID(t)},
|
args{"http://registered.com/callback",
|
||||||
|
mock.NewClientWithConfig(t, []string{"http://registered.com/callback"}, op.ApplicationTypeWeb, nil, false),
|
||||||
|
oidc.ResponseTypeCode},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code flow registered custom not native fails",
|
"code flow registered custom not native fails",
|
||||||
args{"custom://callback", "useragent_client", oidc.ResponseTypeCode, mock.NewMockStorageExpectValidClientID(t)},
|
args{"custom://callback",
|
||||||
|
mock.NewClientWithConfig(t, []string{"custom://callback"}, op.ApplicationTypeUserAgent, nil, false),
|
||||||
|
oidc.ResponseTypeCode},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code flow registered custom native ok",
|
"code flow registered custom native ok",
|
||||||
args{"http://registered.com/callback", "native_client", oidc.ResponseTypeCode, mock.NewMockStorageExpectValidClientID(t)},
|
args{"custom://callback",
|
||||||
|
mock.NewClientWithConfig(t, []string{"custom://callback"}, op.ApplicationTypeNative, nil, false),
|
||||||
|
oidc.ResponseTypeCode},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code flow dev mode http ok",
|
||||||
|
args{"http://registered.com/callback",
|
||||||
|
mock.NewClientWithConfig(t, []string{"http://registered.com/callback"}, op.ApplicationTypeNative, nil, true),
|
||||||
|
oidc.ResponseTypeCode},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"implicit flow registered ok",
|
"implicit flow registered ok",
|
||||||
args{"https://registered.com/callback", "useragent_client", oidc.ResponseTypeIDToken, mock.NewMockStorageExpectValidClientID(t)},
|
args{"https://registered.com/callback",
|
||||||
|
mock.NewClientWithConfig(t, []string{"https://registered.com/callback"}, op.ApplicationTypeUserAgent, nil, false),
|
||||||
|
oidc.ResponseTypeIDToken},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"implicit flow unregistered fails",
|
||||||
|
args{"https://unregistered.com/callback",
|
||||||
|
mock.NewClientWithConfig(t, []string{"https://registered.com/callback"}, op.ApplicationTypeUserAgent, nil, false),
|
||||||
|
oidc.ResponseTypeIDToken},
|
||||||
|
true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"implicit flow registered http localhost native ok",
|
"implicit flow registered http localhost native ok",
|
||||||
args{"http://localhost:9999/callback", "native_client", oidc.ResponseTypeIDToken, mock.NewMockStorageExpectValidClientID(t)},
|
args{"http://localhost:9999/callback",
|
||||||
|
mock.NewClientWithConfig(t, []string{"http://localhost:9999/callback"}, op.ApplicationTypeNative, nil, false),
|
||||||
|
oidc.ResponseTypeIDToken},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"implicit flow registered http localhost user agent fails",
|
"implicit flow registered http localhost user agent fails",
|
||||||
args{"http://localhost:9999/callback", "useragent_client", oidc.ResponseTypeIDToken, mock.NewMockStorageExpectValidClientID(t)},
|
args{"http://localhost:9999/callback",
|
||||||
|
mock.NewClientWithConfig(t, []string{"http://localhost:9999/callback"}, op.ApplicationTypeUserAgent, nil, false),
|
||||||
|
oidc.ResponseTypeIDToken},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"implicit flow http non localhost fails",
|
"implicit flow http non localhost fails",
|
||||||
args{"http://registered.com/callback", "native_client", oidc.ResponseTypeIDToken, mock.NewMockStorageExpectValidClientID(t)},
|
args{"http://registered.com/callback",
|
||||||
|
mock.NewClientWithConfig(t, []string{"http://registered.com/callback"}, op.ApplicationTypeNative, nil, false),
|
||||||
|
oidc.ResponseTypeIDToken},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"implicit flow custom fails",
|
"implicit flow custom fails",
|
||||||
args{"custom://callback", "native_client", oidc.ResponseTypeIDToken, mock.NewMockStorageExpectValidClientID(t)},
|
args{"custom://callback",
|
||||||
|
mock.NewClientWithConfig(t, []string{"custom://callback"}, op.ApplicationTypeNative, nil, false),
|
||||||
|
oidc.ResponseTypeIDToken},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"implicit flow dev mode http ok",
|
||||||
|
args{"http://registered.com/callback",
|
||||||
|
mock.NewClientWithConfig(t, []string{"http://registered.com/callback"}, op.ApplicationTypeNative, nil, true),
|
||||||
|
oidc.ResponseTypeIDToken},
|
||||||
|
false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
if err := op.ValidateAuthReqRedirectURI(nil, tt.args.uri, tt.args.clientID, tt.args.responseType, tt.args.storage); (err != nil) != tt.wantErr {
|
if err := op.ValidateAuthReqRedirectURI(tt.args.client, tt.args.uri, tt.args.responseType); (err != nil) != tt.wantErr {
|
||||||
t.Errorf("ValidateRedirectURI() error = %v, wantErr %v", err.Error(), tt.wantErr)
|
t.Errorf("ValidateRedirectURI() error = %v, wantErr %v", err.Error(), tt.wantErr)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -305,107 +342,36 @@ func TestValidateAuthReqRedirectURI(t *testing.T) {
|
||||||
func TestValidateAuthReqResponseType(t *testing.T) {
|
func TestValidateAuthReqResponseType(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
responseType oidc.ResponseType
|
responseType oidc.ResponseType
|
||||||
}
|
client op.Client
|
||||||
type res struct {
|
|
||||||
err bool
|
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
args args
|
args args
|
||||||
res res
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"code no error",
|
"empty response type",
|
||||||
args{"code"},
|
args{"",
|
||||||
res{false},
|
mock.NewClientWithConfig(t, nil, op.ApplicationTypeNative, []oidc.ResponseType{oidc.ResponseTypeCode}, true)},
|
||||||
},
|
|
||||||
{
|
|
||||||
"id_token token no error",
|
|
||||||
args{"id_token token"},
|
|
||||||
res{false},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id_token no error",
|
|
||||||
args{"id_token"},
|
|
||||||
res{false},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"no response_type error",
|
|
||||||
args{},
|
|
||||||
res{true},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"invalid response_type error",
|
|
||||||
args{"invalid"},
|
|
||||||
res{true},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if err := op.ValidateAuthReqResponseType(tt.args.responseType); (err != nil) != tt.res.err {
|
|
||||||
t.Errorf("ValidateAuthReqResponseType() error = %v, wantErr %v", err, tt.res.err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidateAuthReqIDTokenHint(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
idTokenHint string
|
|
||||||
verifier rp.Verifier
|
|
||||||
}
|
|
||||||
type res struct {
|
|
||||||
userID string
|
|
||||||
err bool
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
res res
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"no id_token_hint, no id and ok",
|
|
||||||
args{
|
|
||||||
"",
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
res{
|
|
||||||
"",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"invalid id_token_hint, no id and error",
|
|
||||||
args{
|
|
||||||
"invalid",
|
|
||||||
rp_mock.NewMockVerifierExpectInvalid(t),
|
|
||||||
},
|
|
||||||
res{
|
|
||||||
"",
|
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"response type missing in client config",
|
||||||
|
args{oidc.ResponseTypeIDToken,
|
||||||
|
mock.NewClientWithConfig(t, nil, op.ApplicationTypeNative, []oidc.ResponseType{oidc.ResponseTypeCode}, true)},
|
||||||
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"no id_token_hint ok",
|
"valid response type",
|
||||||
args{
|
args{oidc.ResponseTypeCode,
|
||||||
"valid",
|
mock.NewClientWithConfig(t, nil, op.ApplicationTypeNative, []oidc.ResponseType{oidc.ResponseTypeCode}, true)},
|
||||||
rp_mock.NewMockVerifierExpectValid(t),
|
|
||||||
},
|
|
||||||
res{
|
|
||||||
"id",
|
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := op.ValidateAuthReqIDTokenHint(nil, tt.args.idTokenHint, tt.args.verifier)
|
if err := op.ValidateAuthReqResponseType(tt.args.client, tt.args.responseType); (err != nil) != tt.wantErr {
|
||||||
if (err != nil) != tt.res.err {
|
t.Errorf("ValidateAuthReqScopes() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
t.Errorf("ValidateAuthReqIDTokenHint() error = %v, wantErr %v", err, tt.res.err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if got != tt.res.userID {
|
|
||||||
t.Errorf("ValidateAuthReqIDTokenHint() got = %v, want %v", got, tt.res.userID)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package op
|
package op
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"github.com/caos/oidc/pkg/oidc"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ApplicationTypeWeb ApplicationType = iota
|
ApplicationTypeWeb ApplicationType = iota
|
||||||
|
@ -16,16 +19,27 @@ type Client interface {
|
||||||
RedirectURIs() []string
|
RedirectURIs() []string
|
||||||
PostLogoutRedirectURIs() []string
|
PostLogoutRedirectURIs() []string
|
||||||
ApplicationType() ApplicationType
|
ApplicationType() ApplicationType
|
||||||
GetAuthMethod() AuthMethod
|
AuthMethod() AuthMethod
|
||||||
|
ResponseTypes() []oidc.ResponseType
|
||||||
LoginURL(string) string
|
LoginURL(string) string
|
||||||
AccessTokenType() AccessTokenType
|
AccessTokenType() AccessTokenType
|
||||||
IDTokenLifetime() time.Duration
|
IDTokenLifetime() time.Duration
|
||||||
|
DevMode() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsConfidentialType(c Client) bool {
|
func IsConfidentialType(c Client) bool {
|
||||||
return c.ApplicationType() == ApplicationTypeWeb
|
return c.ApplicationType() == ApplicationTypeWeb
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ContainsResponseType(types []oidc.ResponseType, responseType oidc.ResponseType) bool {
|
||||||
|
for _, t := range types {
|
||||||
|
if t == responseType {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type ApplicationType int
|
type ApplicationType int
|
||||||
|
|
||||||
type AuthMethod string
|
type AuthMethod string
|
||||||
|
|
|
@ -16,6 +16,7 @@ type Configuration interface {
|
||||||
KeysEndpoint() Endpoint
|
KeysEndpoint() Endpoint
|
||||||
|
|
||||||
AuthMethodPostSupported() bool
|
AuthMethodPostSupported() bool
|
||||||
|
CodeMethodS256Supported() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func ValidateIssuer(issuer string) error {
|
func ValidateIssuer(issuer string) error {
|
||||||
|
|
|
@ -27,6 +27,8 @@ const (
|
||||||
AuthMethodBasic AuthMethod = "client_secret_basic"
|
AuthMethodBasic AuthMethod = "client_secret_basic"
|
||||||
AuthMethodPost = "client_secret_post"
|
AuthMethodPost = "client_secret_post"
|
||||||
AuthMethodNone = "none"
|
AuthMethodNone = "none"
|
||||||
|
|
||||||
|
CodeMethodS256 = "S256"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -50,7 +52,7 @@ type DefaultOP struct {
|
||||||
http http.Handler
|
http http.Handler
|
||||||
decoder *schema.Decoder
|
decoder *schema.Decoder
|
||||||
encoder *schema.Encoder
|
encoder *schema.Encoder
|
||||||
interceptor HttpInterceptor
|
interceptors []HttpInterceptor
|
||||||
retry func(int) (bool, int)
|
retry func(int) (bool, int)
|
||||||
timer <-chan time.Time
|
timer <-chan time.Time
|
||||||
}
|
}
|
||||||
|
@ -59,6 +61,7 @@ type Config struct {
|
||||||
Issuer string
|
Issuer string
|
||||||
CryptoKey [32]byte
|
CryptoKey [32]byte
|
||||||
DefaultLogoutRedirectURI string
|
DefaultLogoutRedirectURI string
|
||||||
|
CodeMethodS256 bool
|
||||||
// ScopesSupported: oidc.SupportedScopes,
|
// ScopesSupported: oidc.SupportedScopes,
|
||||||
// ResponseTypesSupported: responseTypes,
|
// ResponseTypesSupported: responseTypes,
|
||||||
// GrantTypesSupported: oidc.SupportedGrantTypes,
|
// GrantTypesSupported: oidc.SupportedGrantTypes,
|
||||||
|
@ -130,9 +133,9 @@ func WithCustomKeysEndpoint(endpoint Endpoint) DefaultOPOpts {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithHttpInterceptor(h HttpInterceptor) DefaultOPOpts {
|
func WithHttpInterceptors(interceptors ...HttpInterceptor) DefaultOPOpts {
|
||||||
return func(o *DefaultOP) error {
|
return func(o *DefaultOP) error {
|
||||||
o.interceptor = h
|
o.interceptors = append(o.interceptors, interceptors...)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -183,7 +186,7 @@ func NewDefaultOP(ctx context.Context, config *Config, storage Storage, opOpts .
|
||||||
|
|
||||||
p.verifier = rp.NewDefaultVerifier(config.Issuer, "", p, rp.WithIgnoreAudience(), rp.WithIgnoreExpiration())
|
p.verifier = rp.NewDefaultVerifier(config.Issuer, "", p, rp.WithIgnoreAudience(), rp.WithIgnoreExpiration())
|
||||||
|
|
||||||
p.http = CreateRouter(p, p.interceptor)
|
p.http = CreateRouter(p, p.interceptors...)
|
||||||
|
|
||||||
p.decoder = schema.NewDecoder()
|
p.decoder = schema.NewDecoder()
|
||||||
p.decoder.IgnoreUnknownKeys(true)
|
p.decoder.IgnoreUnknownKeys(true)
|
||||||
|
@ -223,6 +226,10 @@ func (p *DefaultOP) AuthMethodPostSupported() bool {
|
||||||
return true //TODO: config
|
return true //TODO: config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *DefaultOP) CodeMethodS256Supported() bool {
|
||||||
|
return p.config.CodeMethodS256
|
||||||
|
}
|
||||||
|
|
||||||
func (p *DefaultOP) HttpHandler() http.Handler {
|
func (p *DefaultOP) HttpHandler() http.Handler {
|
||||||
return p.http
|
return p.http
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ func CreateDiscoveryConfig(c Configuration, s Signer) *oidc.DiscoveryConfigurati
|
||||||
IDTokenSigningAlgValuesSupported: SigAlgorithms(s),
|
IDTokenSigningAlgValuesSupported: SigAlgorithms(s),
|
||||||
SubjectTypesSupported: SubjectTypes(c),
|
SubjectTypesSupported: SubjectTypes(c),
|
||||||
TokenEndpointAuthMethodsSupported: AuthMethods(c),
|
TokenEndpointAuthMethodsSupported: AuthMethods(c),
|
||||||
|
CodeChallengeMethodsSupported: CodeChallengeMethods(c),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,3 +118,11 @@ func AuthMethods(c Configuration) []string {
|
||||||
}
|
}
|
||||||
return authMethods
|
return authMethods
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CodeChallengeMethods(c Configuration) []string {
|
||||||
|
codeMethods := make([]string, 0, 1)
|
||||||
|
if c.CodeMethodS256Supported() {
|
||||||
|
codeMethods = append(codeMethods, CodeMethodS256)
|
||||||
|
}
|
||||||
|
return codeMethods
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package mock
|
package mock
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/caos/oidc/pkg/oidc"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
@ -27,3 +28,13 @@ func NewClientExpectAny(t *testing.T, appType op.ApplicationType) op.Client {
|
||||||
})
|
})
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewClientWithConfig(t *testing.T, uri []string, appType op.ApplicationType, responseTypes []oidc.ResponseType, devMode bool) op.Client {
|
||||||
|
c := NewClient(t)
|
||||||
|
m := c.(*MockClient)
|
||||||
|
m.EXPECT().RedirectURIs().AnyTimes().Return(uri)
|
||||||
|
m.EXPECT().ApplicationType().AnyTimes().Return(appType)
|
||||||
|
m.EXPECT().ResponseTypes().AnyTimes().Return(responseTypes)
|
||||||
|
m.EXPECT().DevMode().AnyTimes().Return(devMode)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
package mock
|
package mock
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
oidc "github.com/caos/oidc/pkg/oidc"
|
||||||
op "github.com/caos/oidc/pkg/op"
|
op "github.com/caos/oidc/pkg/op"
|
||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
@ -62,18 +63,32 @@ func (mr *MockClientMockRecorder) ApplicationType() *gomock.Call {
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ApplicationType", reflect.TypeOf((*MockClient)(nil).ApplicationType))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ApplicationType", reflect.TypeOf((*MockClient)(nil).ApplicationType))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAuthMethod mocks base method
|
// AuthMethod mocks base method
|
||||||
func (m *MockClient) GetAuthMethod() op.AuthMethod {
|
func (m *MockClient) AuthMethod() op.AuthMethod {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "GetAuthMethod")
|
ret := m.ctrl.Call(m, "AuthMethod")
|
||||||
ret0, _ := ret[0].(op.AuthMethod)
|
ret0, _ := ret[0].(op.AuthMethod)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAuthMethod indicates an expected call of GetAuthMethod
|
// AuthMethod indicates an expected call of AuthMethod
|
||||||
func (mr *MockClientMockRecorder) GetAuthMethod() *gomock.Call {
|
func (mr *MockClientMockRecorder) AuthMethod() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAuthMethod", reflect.TypeOf((*MockClient)(nil).GetAuthMethod))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthMethod", reflect.TypeOf((*MockClient)(nil).AuthMethod))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DevMode mocks base method
|
||||||
|
func (m *MockClient) DevMode() bool {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "DevMode")
|
||||||
|
ret0, _ := ret[0].(bool)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// DevMode indicates an expected call of DevMode
|
||||||
|
func (mr *MockClientMockRecorder) DevMode() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DevMode", reflect.TypeOf((*MockClient)(nil).DevMode))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetID mocks base method
|
// GetID mocks base method
|
||||||
|
@ -145,3 +160,17 @@ func (mr *MockClientMockRecorder) RedirectURIs() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RedirectURIs", reflect.TypeOf((*MockClient)(nil).RedirectURIs))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RedirectURIs", reflect.TypeOf((*MockClient)(nil).RedirectURIs))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResponseTypes mocks base method
|
||||||
|
func (m *MockClient) ResponseTypes() []oidc.ResponseType {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "ResponseTypes")
|
||||||
|
ret0, _ := ret[0].([]oidc.ResponseType)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResponseTypes indicates an expected call of ResponseTypes
|
||||||
|
func (mr *MockClientMockRecorder) ResponseTypes() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResponseTypes", reflect.TypeOf((*MockClient)(nil).ResponseTypes))
|
||||||
|
}
|
||||||
|
|
|
@ -61,6 +61,20 @@ func (mr *MockConfigurationMockRecorder) AuthorizationEndpoint() *gomock.Call {
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthorizationEndpoint", reflect.TypeOf((*MockConfiguration)(nil).AuthorizationEndpoint))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthorizationEndpoint", reflect.TypeOf((*MockConfiguration)(nil).AuthorizationEndpoint))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CodeMethodS256Supported mocks base method
|
||||||
|
func (m *MockConfiguration) CodeMethodS256Supported() bool {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "CodeMethodS256Supported")
|
||||||
|
ret0, _ := ret[0].(bool)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// CodeMethodS256Supported indicates an expected call of CodeMethodS256Supported
|
||||||
|
func (mr *MockConfigurationMockRecorder) CodeMethodS256Supported() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CodeMethodS256Supported", reflect.TypeOf((*MockConfiguration)(nil).CodeMethodS256Supported))
|
||||||
|
}
|
||||||
|
|
||||||
// EndSessionEndpoint mocks base method
|
// EndSessionEndpoint mocks base method
|
||||||
func (m *MockConfiguration) EndSessionEndpoint() op.Endpoint {
|
func (m *MockConfiguration) EndSessionEndpoint() op.Endpoint {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
@ -103,20 +117,6 @@ func (mr *MockConfigurationMockRecorder) KeysEndpoint() *gomock.Call {
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeysEndpoint", reflect.TypeOf((*MockConfiguration)(nil).KeysEndpoint))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeysEndpoint", reflect.TypeOf((*MockConfiguration)(nil).KeysEndpoint))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Port mocks base method
|
|
||||||
func (m *MockConfiguration) Port() string {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Port")
|
|
||||||
ret0, _ := ret[0].(string)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Port indicates an expected call of Port
|
|
||||||
func (mr *MockConfigurationMockRecorder) Port() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Port", reflect.TypeOf((*MockConfiguration)(nil).Port))
|
|
||||||
}
|
|
||||||
|
|
||||||
// TokenEndpoint mocks base method
|
// TokenEndpoint mocks base method
|
||||||
func (m *MockConfiguration) TokenEndpoint() op.Endpoint {
|
func (m *MockConfiguration) TokenEndpoint() op.Endpoint {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
|
|
@ -184,18 +184,18 @@ func (mr *MockStorageMockRecorder) GetUserinfoFromScopes(arg0, arg1, arg2 interf
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserinfoFromToken mocks base method
|
// GetUserinfoFromToken mocks base method
|
||||||
func (m *MockStorage) GetUserinfoFromToken(arg0 context.Context, arg1 string) (*oidc.Userinfo, error) {
|
func (m *MockStorage) GetUserinfoFromToken(arg0 context.Context, arg1, arg2 string) (*oidc.Userinfo, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "GetUserinfoFromToken", arg0, arg1)
|
ret := m.ctrl.Call(m, "GetUserinfoFromToken", arg0, arg1, arg2)
|
||||||
ret0, _ := ret[0].(*oidc.Userinfo)
|
ret0, _ := ret[0].(*oidc.Userinfo)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserinfoFromToken indicates an expected call of GetUserinfoFromToken
|
// GetUserinfoFromToken indicates an expected call of GetUserinfoFromToken
|
||||||
func (mr *MockStorageMockRecorder) GetUserinfoFromToken(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockStorageMockRecorder) GetUserinfoFromToken(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserinfoFromToken", reflect.TypeOf((*MockStorage)(nil).GetUserinfoFromToken), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserinfoFromToken", reflect.TypeOf((*MockStorage)(nil).GetUserinfoFromToken), arg0, arg1, arg2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Health mocks base method
|
// Health mocks base method
|
||||||
|
|
|
@ -3,6 +3,7 @@ package mock
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/caos/oidc/pkg/oidc"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -66,21 +67,25 @@ func ExpectValidClientID(s op.Storage) {
|
||||||
var appType op.ApplicationType
|
var appType op.ApplicationType
|
||||||
var authMethod op.AuthMethod
|
var authMethod op.AuthMethod
|
||||||
var accessTokenType op.AccessTokenType
|
var accessTokenType op.AccessTokenType
|
||||||
|
var responseTypes []oidc.ResponseType
|
||||||
switch id {
|
switch id {
|
||||||
case "web_client":
|
case "web_client":
|
||||||
appType = op.ApplicationTypeWeb
|
appType = op.ApplicationTypeWeb
|
||||||
authMethod = op.AuthMethodBasic
|
authMethod = op.AuthMethodBasic
|
||||||
accessTokenType = op.AccessTokenTypeBearer
|
accessTokenType = op.AccessTokenTypeBearer
|
||||||
|
responseTypes = []oidc.ResponseType{oidc.ResponseTypeCode}
|
||||||
case "native_client":
|
case "native_client":
|
||||||
appType = op.ApplicationTypeNative
|
appType = op.ApplicationTypeNative
|
||||||
authMethod = op.AuthMethodNone
|
authMethod = op.AuthMethodNone
|
||||||
accessTokenType = op.AccessTokenTypeBearer
|
accessTokenType = op.AccessTokenTypeBearer
|
||||||
|
responseTypes = []oidc.ResponseType{oidc.ResponseTypeCode}
|
||||||
case "useragent_client":
|
case "useragent_client":
|
||||||
appType = op.ApplicationTypeUserAgent
|
appType = op.ApplicationTypeUserAgent
|
||||||
authMethod = op.AuthMethodBasic
|
authMethod = op.AuthMethodBasic
|
||||||
accessTokenType = op.AccessTokenTypeJWT
|
accessTokenType = op.AccessTokenTypeJWT
|
||||||
|
responseTypes = []oidc.ResponseType{oidc.ResponseTypeIDToken}
|
||||||
}
|
}
|
||||||
return &ConfClient{id: id, appType: appType, authMethod: authMethod, accessTokenType: accessTokenType}, nil
|
return &ConfClient{id: id, appType: appType, authMethod: authMethod, accessTokenType: accessTokenType, responseTypes: responseTypes}, nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,6 +121,8 @@ type ConfClient struct {
|
||||||
appType op.ApplicationType
|
appType op.ApplicationType
|
||||||
authMethod op.AuthMethod
|
authMethod op.AuthMethod
|
||||||
accessTokenType op.AccessTokenType
|
accessTokenType op.AccessTokenType
|
||||||
|
responseTypes []oidc.ResponseType
|
||||||
|
devMode bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConfClient) RedirectURIs() []string {
|
func (c *ConfClient) RedirectURIs() []string {
|
||||||
|
@ -138,7 +145,7 @@ func (c *ConfClient) ApplicationType() op.ApplicationType {
|
||||||
return c.appType
|
return c.appType
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConfClient) GetAuthMethod() op.AuthMethod {
|
func (c *ConfClient) AuthMethod() op.AuthMethod {
|
||||||
return c.authMethod
|
return c.authMethod
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,3 +162,9 @@ func (c *ConfClient) IDTokenLifetime() time.Duration {
|
||||||
func (c *ConfClient) AccessTokenType() op.AccessTokenType {
|
func (c *ConfClient) AccessTokenType() op.AccessTokenType {
|
||||||
return c.accessTokenType
|
return c.accessTokenType
|
||||||
}
|
}
|
||||||
|
func (c *ConfClient) ResponseTypes() []oidc.ResponseType {
|
||||||
|
return c.responseTypes
|
||||||
|
}
|
||||||
|
func (c *ConfClient) DevMode() bool {
|
||||||
|
return c.devMode
|
||||||
|
}
|
||||||
|
|
44
pkg/op/op.go
44
pkg/op/op.go
|
@ -27,28 +27,44 @@ type OpenIDProvider interface {
|
||||||
HttpHandler() http.Handler
|
HttpHandler() http.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
type HttpInterceptor func(http.HandlerFunc) http.HandlerFunc
|
type HttpInterceptor func(http.Handler) http.Handler
|
||||||
|
|
||||||
var DefaultInterceptor = func(h http.HandlerFunc) http.HandlerFunc {
|
var allowAllOrigins = func(_ string) bool {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return true
|
||||||
h(w, r)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateRouter(o OpenIDProvider, h HttpInterceptor) *mux.Router {
|
func CreateRouter(o OpenIDProvider, interceptors ...HttpInterceptor) *mux.Router {
|
||||||
if h == nil {
|
intercept := buildInterceptor(interceptors...)
|
||||||
h = DefaultInterceptor
|
|
||||||
}
|
|
||||||
router := mux.NewRouter()
|
router := mux.NewRouter()
|
||||||
router.Use(handlers.CORS())
|
router.Use(handlers.CORS(
|
||||||
|
handlers.AllowCredentials(),
|
||||||
|
handlers.AllowedHeaders([]string{"authorization", "content-type"}),
|
||||||
|
handlers.AllowedOriginValidator(allowAllOrigins),
|
||||||
|
))
|
||||||
router.HandleFunc(healthzEndpoint, Healthz)
|
router.HandleFunc(healthzEndpoint, Healthz)
|
||||||
router.HandleFunc(readinessEndpoint, o.HandleReady)
|
router.HandleFunc(readinessEndpoint, o.HandleReady)
|
||||||
router.HandleFunc(oidc.DiscoveryEndpoint, o.HandleDiscovery)
|
router.HandleFunc(oidc.DiscoveryEndpoint, o.HandleDiscovery)
|
||||||
router.HandleFunc(o.AuthorizationEndpoint().Relative(), h(o.HandleAuthorize))
|
router.Handle(o.AuthorizationEndpoint().Relative(), intercept(o.HandleAuthorize))
|
||||||
router.HandleFunc(o.AuthorizationEndpoint().Relative()+"/{id}", h(o.HandleAuthorizeCallback))
|
router.Handle(o.AuthorizationEndpoint().Relative()+"/{id}", intercept(o.HandleAuthorizeCallback))
|
||||||
router.HandleFunc(o.TokenEndpoint().Relative(), h(o.HandleExchange))
|
router.Handle(o.TokenEndpoint().Relative(), intercept(o.HandleExchange))
|
||||||
router.HandleFunc(o.UserinfoEndpoint().Relative(), o.HandleUserinfo)
|
router.HandleFunc(o.UserinfoEndpoint().Relative(), o.HandleUserinfo)
|
||||||
router.HandleFunc(o.EndSessionEndpoint().Relative(), h(o.HandleEndSession))
|
router.Handle(o.EndSessionEndpoint().Relative(), intercept(o.HandleEndSession))
|
||||||
router.HandleFunc(o.KeysEndpoint().Relative(), o.HandleKeys)
|
router.HandleFunc(o.KeysEndpoint().Relative(), o.HandleKeys)
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildInterceptor(interceptors ...HttpInterceptor) func(http.HandlerFunc) http.Handler {
|
||||||
|
return func(handlerFunc http.HandlerFunc) http.Handler {
|
||||||
|
handler := handlerFuncToHandler(handlerFunc)
|
||||||
|
for i := len(interceptors) - 1; i >= 0; i-- {
|
||||||
|
handler = interceptors[i](handler)
|
||||||
|
}
|
||||||
|
return handler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handlerFuncToHandler(handlerFunc http.HandlerFunc) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
handlerFunc(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ type OPStorage interface {
|
||||||
GetClientByClientID(context.Context, string) (Client, error)
|
GetClientByClientID(context.Context, string) (Client, error)
|
||||||
AuthorizeClientIDSecret(context.Context, string, string) error
|
AuthorizeClientIDSecret(context.Context, string, string) error
|
||||||
GetUserinfoFromScopes(context.Context, string, []string) (*oidc.Userinfo, error)
|
GetUserinfoFromScopes(context.Context, string, []string) (*oidc.Userinfo, error)
|
||||||
GetUserinfoFromToken(context.Context, string) (*oidc.Userinfo, error)
|
GetUserinfoFromToken(context.Context, string, string) (*oidc.Userinfo, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Storage interface {
|
type Storage interface {
|
||||||
|
|
|
@ -78,11 +78,11 @@ func AuthorizeClient(ctx context.Context, tokenReq *oidc.AccessTokenRequest, exc
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if client.GetAuthMethod() == AuthMethodNone {
|
if client.AuthMethod() == AuthMethodNone {
|
||||||
authReq, err := AuthorizeCodeChallenge(ctx, tokenReq, exchanger)
|
authReq, err := AuthorizeCodeChallenge(ctx, tokenReq, exchanger)
|
||||||
return authReq, client, err
|
return authReq, client, err
|
||||||
}
|
}
|
||||||
if client.GetAuthMethod() == AuthMethodPost && !exchanger.AuthMethodPostSupported() {
|
if client.AuthMethod() == AuthMethodPost && !exchanger.AuthMethodPostSupported() {
|
||||||
return nil, nil, errors.New("basic not supported")
|
return nil, nil, errors.New("basic not supported")
|
||||||
}
|
}
|
||||||
err = AuthorizeClientIDSecret(ctx, tokenReq.ClientID, tokenReq.ClientSecret, exchanger.Storage())
|
err = AuthorizeClientIDSecret(ctx, tokenReq.ClientID, tokenReq.ClientSecret, exchanger.Storage())
|
||||||
|
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gorilla/schema"
|
||||||
|
|
||||||
"github.com/caos/oidc/pkg/oidc"
|
"github.com/caos/oidc/pkg/oidc"
|
||||||
"github.com/caos/oidc/pkg/utils"
|
"github.com/caos/oidc/pkg/utils"
|
||||||
)
|
)
|
||||||
|
@ -26,8 +28,9 @@ func Userinfo(w http.ResponseWriter, r *http.Request, userinfoProvider UserinfoP
|
||||||
http.Error(w, "access token missing", http.StatusUnauthorized)
|
http.Error(w, "access token missing", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
info, err := userinfoProvider.Storage().GetUserinfoFromToken(r.Context(), tokenID)
|
info, err := userinfoProvider.Storage().GetUserinfoFromToken(r.Context(), tokenID, r.Header.Get("origin"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
utils.MarshalJSON(w, err)
|
utils.MarshalJSON(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue