authelia/internal/oidc/client_test.go

556 lines
18 KiB
Go

package oidc
import (
"fmt"
"testing"
"github.com/ory/fosite"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/square/go-jose.v2"
"github.com/authelia/authelia/v4/internal/authentication"
"github.com/authelia/authelia/v4/internal/authorization"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/model"
)
func TestNewClient(t *testing.T) {
config := schema.OpenIDConnectClientConfiguration{}
client := NewClient(config)
assert.Equal(t, "", client.GetID())
assert.Equal(t, "", client.GetDescription())
assert.Len(t, client.GetResponseModes(), 0)
assert.Len(t, client.GetResponseTypes(), 1)
assert.Equal(t, "", client.GetSectorIdentifier())
bclient, ok := client.(*BaseClient)
require.True(t, ok)
assert.Equal(t, "", bclient.UserinfoSigningAlgorithm)
assert.Equal(t, SigningAlgorithmNone, client.GetUserinfoSigningAlgorithm())
_, ok = client.(*FullClient)
assert.False(t, ok)
config = schema.OpenIDConnectClientConfiguration{
ID: myclient,
Description: myclientdesc,
Policy: twofactor,
Secret: MustDecodeSecret(badsecret),
RedirectURIs: []string{examplecom},
Scopes: schema.DefaultOpenIDConnectClientConfiguration.Scopes,
ResponseTypes: schema.DefaultOpenIDConnectClientConfiguration.ResponseTypes,
GrantTypes: schema.DefaultOpenIDConnectClientConfiguration.GrantTypes,
ResponseModes: schema.DefaultOpenIDConnectClientConfiguration.ResponseModes,
}
client = NewClient(config)
assert.Equal(t, myclient, client.GetID())
require.Len(t, client.GetResponseModes(), 1)
assert.Equal(t, fosite.ResponseModeFormPost, client.GetResponseModes()[0])
assert.Equal(t, authorization.TwoFactor, client.GetAuthorizationPolicy())
config = schema.OpenIDConnectClientConfiguration{
TokenEndpointAuthMethod: ClientAuthMethodClientSecretBasic,
}
client = NewClient(config)
fclient, ok := client.(*FullClient)
var niljwks *jose.JSONWebKeySet
require.True(t, ok)
assert.Equal(t, "", fclient.UserinfoSigningAlgorithm)
assert.Equal(t, ClientAuthMethodClientSecretBasic, fclient.TokenEndpointAuthMethod)
assert.Equal(t, ClientAuthMethodClientSecretBasic, fclient.GetTokenEndpointAuthMethod())
assert.Equal(t, SigningAlgorithmNone, client.GetUserinfoSigningAlgorithm())
assert.Equal(t, "", fclient.TokenEndpointAuthSigningAlgorithm)
assert.Equal(t, SigningAlgorithmRSAWithSHA256, fclient.GetTokenEndpointAuthSigningAlgorithm())
assert.Equal(t, "", fclient.RequestObjectSigningAlgorithm)
assert.Equal(t, "", fclient.GetRequestObjectSigningAlgorithm())
assert.Equal(t, "", fclient.JSONWebKeysURI)
assert.Equal(t, "", fclient.GetJSONWebKeysURI())
assert.Equal(t, niljwks, fclient.JSONWebKeys)
assert.Equal(t, niljwks, fclient.GetJSONWebKeys())
assert.Equal(t, []string(nil), fclient.RequestURIs)
assert.Equal(t, []string(nil), fclient.GetRequestURIs())
}
func TestBaseClient_ValidatePARPolicy(t *testing.T) {
testCases := []struct {
name string
client *BaseClient
have *fosite.Request
expected string
}{
{
"ShouldNotEnforcePAR",
&BaseClient{
EnforcePAR: false,
},
&fosite.Request{},
"",
},
{
"ShouldEnforcePARAndErrorWithoutCorrectRequestURI",
&BaseClient{
EnforcePAR: true,
},
&fosite.Request{
Form: map[string][]string{
FormParameterRequestURI: {"https://google.com"},
},
},
"invalid_request",
},
{
"ShouldEnforcePARAndErrorWithEmptyRequestURI",
&BaseClient{
EnforcePAR: true,
},
&fosite.Request{
Form: map[string][]string{
FormParameterRequestURI: {""},
},
},
"invalid_request",
},
{
"ShouldEnforcePARAndNotErrorWithCorrectRequestURI",
&BaseClient{
EnforcePAR: true,
},
&fosite.Request{
Form: map[string][]string{
FormParameterRequestURI: {urnPARPrefix + "abc"},
},
},
"",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := tc.client.ValidatePARPolicy(tc.have, urnPARPrefix)
switch tc.expected {
case "":
assert.NoError(t, err)
default:
assert.EqualError(t, err, tc.expected)
}
})
}
}
func TestIsAuthenticationLevelSufficient(t *testing.T) {
c := &FullClient{BaseClient: &BaseClient{}}
c.Policy = authorization.Bypass
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated))
assert.True(t, c.IsAuthenticationLevelSufficient(authentication.OneFactor))
assert.True(t, c.IsAuthenticationLevelSufficient(authentication.TwoFactor))
c.Policy = authorization.OneFactor
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated))
assert.True(t, c.IsAuthenticationLevelSufficient(authentication.OneFactor))
assert.True(t, c.IsAuthenticationLevelSufficient(authentication.TwoFactor))
c.Policy = authorization.TwoFactor
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated))
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.OneFactor))
assert.True(t, c.IsAuthenticationLevelSufficient(authentication.TwoFactor))
c.Policy = authorization.Denied
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated))
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.OneFactor))
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.TwoFactor))
}
func TestClient_GetConsentResponseBody(t *testing.T) {
c := &FullClient{BaseClient: &BaseClient{}}
consentRequestBody := c.GetConsentResponseBody(nil)
assert.Equal(t, "", consentRequestBody.ClientID)
assert.Equal(t, "", consentRequestBody.ClientDescription)
assert.Equal(t, []string(nil), consentRequestBody.Scopes)
assert.Equal(t, []string(nil), consentRequestBody.Audience)
c.ID = myclient
c.Description = myclientdesc
consent := &model.OAuth2ConsentSession{
RequestedAudience: []string{examplecom},
RequestedScopes: []string{ScopeOpenID, ScopeGroups},
}
expectedScopes := []string{ScopeOpenID, ScopeGroups}
expectedAudiences := []string{examplecom}
consentRequestBody = c.GetConsentResponseBody(consent)
assert.Equal(t, myclient, consentRequestBody.ClientID)
assert.Equal(t, myclientdesc, consentRequestBody.ClientDescription)
assert.Equal(t, expectedScopes, consentRequestBody.Scopes)
assert.Equal(t, expectedAudiences, consentRequestBody.Audience)
}
func TestClient_GetAudience(t *testing.T) {
c := &FullClient{BaseClient: &BaseClient{}}
audience := c.GetAudience()
assert.Len(t, audience, 0)
c.Audience = []string{examplecom}
audience = c.GetAudience()
require.Len(t, audience, 1)
assert.Equal(t, examplecom, audience[0])
}
func TestClient_GetScopes(t *testing.T) {
c := &FullClient{BaseClient: &BaseClient{}}
scopes := c.GetScopes()
assert.Len(t, scopes, 0)
c.Scopes = []string{ScopeOpenID}
scopes = c.GetScopes()
require.Len(t, scopes, 1)
assert.Equal(t, ScopeOpenID, scopes[0])
}
func TestClient_GetGrantTypes(t *testing.T) {
c := &FullClient{BaseClient: &BaseClient{}}
grantTypes := c.GetGrantTypes()
require.Len(t, grantTypes, 1)
assert.Equal(t, GrantTypeAuthorizationCode, grantTypes[0])
c.GrantTypes = []string{"device_code"}
grantTypes = c.GetGrantTypes()
require.Len(t, grantTypes, 1)
assert.Equal(t, "device_code", grantTypes[0])
}
func TestClient_Hashing(t *testing.T) {
c := &FullClient{BaseClient: &BaseClient{}}
hashedSecret := c.GetHashedSecret()
assert.Equal(t, []byte(nil), hashedSecret)
c.Secret = MustDecodeSecret(badsecret)
assert.True(t, c.Secret.MatchBytes([]byte("a_bad_secret")))
}
func TestClient_GetHashedSecret(t *testing.T) {
c := &FullClient{BaseClient: &BaseClient{}}
hashedSecret := c.GetHashedSecret()
assert.Equal(t, []byte(nil), hashedSecret)
c.Secret = MustDecodeSecret(badsecret)
hashedSecret = c.GetHashedSecret()
assert.Equal(t, []byte(badsecret), hashedSecret)
}
func TestClient_GetID(t *testing.T) {
c := &FullClient{BaseClient: &BaseClient{}}
id := c.GetID()
assert.Equal(t, "", id)
c.ID = myclient
id = c.GetID()
assert.Equal(t, myclient, id)
}
func TestClient_GetRedirectURIs(t *testing.T) {
c := &FullClient{BaseClient: &BaseClient{}}
redirectURIs := c.GetRedirectURIs()
require.Len(t, redirectURIs, 0)
c.RedirectURIs = []string{examplecom}
redirectURIs = c.GetRedirectURIs()
require.Len(t, redirectURIs, 1)
assert.Equal(t, examplecom, redirectURIs[0])
}
func TestClient_GetResponseModes(t *testing.T) {
c := &FullClient{BaseClient: &BaseClient{}}
responseModes := c.GetResponseModes()
require.Len(t, responseModes, 0)
c.ResponseModes = []fosite.ResponseModeType{
fosite.ResponseModeDefault, fosite.ResponseModeFormPost,
fosite.ResponseModeQuery, fosite.ResponseModeFragment,
}
responseModes = c.GetResponseModes()
require.Len(t, responseModes, 4)
assert.Equal(t, fosite.ResponseModeDefault, responseModes[0])
assert.Equal(t, fosite.ResponseModeFormPost, responseModes[1])
assert.Equal(t, fosite.ResponseModeQuery, responseModes[2])
assert.Equal(t, fosite.ResponseModeFragment, responseModes[3])
}
func TestClient_GetResponseTypes(t *testing.T) {
c := &FullClient{BaseClient: &BaseClient{}}
responseTypes := c.GetResponseTypes()
require.Len(t, responseTypes, 1)
assert.Equal(t, ResponseTypeAuthorizationCodeFlow, responseTypes[0])
c.ResponseTypes = []string{ResponseTypeAuthorizationCodeFlow, ResponseTypeImplicitFlowIDToken}
responseTypes = c.GetResponseTypes()
require.Len(t, responseTypes, 2)
assert.Equal(t, ResponseTypeAuthorizationCodeFlow, responseTypes[0])
assert.Equal(t, ResponseTypeImplicitFlowIDToken, responseTypes[1])
}
func TestNewClientPKCE(t *testing.T) {
testCases := []struct {
name string
have schema.OpenIDConnectClientConfiguration
expectedEnforcePKCE bool
expectedEnforcePKCEChallengeMethod bool
expected string
r *fosite.Request
err string
desc string
}{
{
"ShouldNotEnforcePKCEAndNotErrorOnNonPKCERequest",
schema.OpenIDConnectClientConfiguration{},
false,
false,
"",
&fosite.Request{},
"",
"",
},
{
"ShouldEnforcePKCEAndErrorOnNonPKCERequest",
schema.OpenIDConnectClientConfiguration{EnforcePKCE: true},
true,
false,
"",
&fosite.Request{},
"invalid_request",
"The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Clients must include a code_challenge when performing the authorize code flow, but it is missing. The server is configured in a way that enforces PKCE for this client.",
},
{
"ShouldEnforcePKCEAndNotErrorOnPKCERequest",
schema.OpenIDConnectClientConfiguration{EnforcePKCE: true},
true,
false,
"",
&fosite.Request{Form: map[string][]string{"code_challenge": {"abc"}}},
"",
"",
},
{"ShouldEnforcePKCEFromChallengeMethodAndErrorOnNonPKCERequest",
schema.OpenIDConnectClientConfiguration{PKCEChallengeMethod: "S256"},
true,
true,
"S256",
&fosite.Request{},
"invalid_request",
"The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Clients must include a code_challenge when performing the authorize code flow, but it is missing. The server is configured in a way that enforces PKCE for this client.",
},
{"ShouldEnforcePKCEFromChallengeMethodAndErrorOnInvalidChallengeMethod",
schema.OpenIDConnectClientConfiguration{PKCEChallengeMethod: "S256"},
true,
true,
"S256",
&fosite.Request{Form: map[string][]string{"code_challenge": {"abc"}}},
"invalid_request",
"The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Client must use code_challenge_method=S256, is not allowed. The server is configured in a way that enforces PKCE S256 as challenge method for this client.",
},
{"ShouldEnforcePKCEFromChallengeMethodAndNotErrorOnValidRequest",
schema.OpenIDConnectClientConfiguration{PKCEChallengeMethod: "S256"},
true,
true,
"S256",
&fosite.Request{Form: map[string][]string{"code_challenge": {"abc"}, "code_challenge_method": {"S256"}}},
"",
"",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
client := NewClient(tc.have)
assert.Equal(t, tc.expectedEnforcePKCE, client.GetPKCEEnforcement())
assert.Equal(t, tc.expectedEnforcePKCEChallengeMethod, client.GetPKCEChallengeMethodEnforcement())
assert.Equal(t, tc.expected, client.GetPKCEChallengeMethod())
if tc.r != nil {
err := client.ValidatePKCEPolicy(tc.r)
if tc.err != "" {
require.NotNil(t, err)
assert.EqualError(t, err, tc.err)
assert.Equal(t, tc.desc, fosite.ErrorToRFC6749Error(err).WithExposeDebug(true).GetDescription())
} else {
assert.NoError(t, err)
}
}
})
}
}
func TestNewClientPAR(t *testing.T) {
testCases := []struct {
name string
have schema.OpenIDConnectClientConfiguration
expected bool
r *fosite.Request
err string
desc string
}{
{
"ShouldNotEnforcEPARAndNotErrorOnNonPARRequest",
schema.OpenIDConnectClientConfiguration{},
false,
&fosite.Request{},
"",
"",
},
{
"ShouldEnforcePARAndErrorOnNonPARRequest",
schema.OpenIDConnectClientConfiguration{EnforcePAR: true},
true,
&fosite.Request{},
"invalid_request",
"The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Pushed Authorization Requests are enforced for this client but no such request was sent. The request_uri parameter was empty.",
},
{
"ShouldEnforcePARAndErrorOnNonPARRequest",
schema.OpenIDConnectClientConfiguration{EnforcePAR: true},
true,
&fosite.Request{Form: map[string][]string{FormParameterRequestURI: {"https://example.com"}}},
"invalid_request",
"The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Pushed Authorization Requests are enforced for this client but no such request was sent. The request_uri parameter 'https://example.com' is malformed."},
{
"ShouldEnforcePARAndNotErrorOnPARRequest",
schema.OpenIDConnectClientConfiguration{EnforcePAR: true},
true,
&fosite.Request{Form: map[string][]string{FormParameterRequestURI: {fmt.Sprintf("%sabc", urnPARPrefix)}}},
"",
"",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
client := NewClient(tc.have)
assert.Equal(t, tc.expected, client.GetPAREnforcement())
if tc.r != nil {
err := client.ValidatePARPolicy(tc.r, urnPARPrefix)
if tc.err != "" {
require.NotNil(t, err)
assert.EqualError(t, err, tc.err)
assert.Equal(t, tc.desc, fosite.ErrorToRFC6749Error(err).WithExposeDebug(true).GetDescription())
} else {
assert.NoError(t, err)
}
}
})
}
}
func TestNewClientResponseModes(t *testing.T) {
testCases := []struct {
name string
have schema.OpenIDConnectClientConfiguration
expected []fosite.ResponseModeType
r *fosite.AuthorizeRequest
err string
desc string
}{
{
"ShouldEnforceResponseModePolicyAndAllowDefaultModeQuery",
schema.OpenIDConnectClientConfiguration{ResponseModes: []string{ResponseModeQuery}},
[]fosite.ResponseModeType{fosite.ResponseModeQuery},
&fosite.AuthorizeRequest{DefaultResponseMode: fosite.ResponseModeQuery, ResponseMode: fosite.ResponseModeDefault, Request: fosite.Request{Form: map[string][]string{FormParameterResponseMode: nil}}},
"",
"",
},
{
"ShouldEnforceResponseModePolicyAndFailOnDefaultMode",
schema.OpenIDConnectClientConfiguration{ResponseModes: []string{ResponseModeFormPost}},
[]fosite.ResponseModeType{fosite.ResponseModeFormPost},
&fosite.AuthorizeRequest{DefaultResponseMode: fosite.ResponseModeQuery, ResponseMode: fosite.ResponseModeDefault, Request: fosite.Request{Form: map[string][]string{FormParameterResponseMode: nil}}},
"unsupported_response_mode",
"The authorization server does not support obtaining a response using this response mode. The request omitted the response_mode making the default response_mode 'query' based on the other authorization request parameters but registered OAuth 2.0 client doesn't support this response_mode",
},
{
"ShouldNotEnforceConfiguredResponseMode",
schema.OpenIDConnectClientConfiguration{ResponseModes: []string{ResponseModeFormPost}},
[]fosite.ResponseModeType{fosite.ResponseModeFormPost},
&fosite.AuthorizeRequest{DefaultResponseMode: fosite.ResponseModeQuery, ResponseMode: fosite.ResponseModeQuery, Request: fosite.Request{Form: map[string][]string{FormParameterResponseMode: {ResponseModeQuery}}}},
"",
"",
},
{
"ShouldNotEnforceUnconfiguredResponseMode",
schema.OpenIDConnectClientConfiguration{ResponseModes: []string{}},
[]fosite.ResponseModeType{},
&fosite.AuthorizeRequest{DefaultResponseMode: fosite.ResponseModeQuery, ResponseMode: fosite.ResponseModeDefault, Request: fosite.Request{Form: map[string][]string{FormParameterResponseMode: {ResponseModeQuery}}}},
"",
"",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
client := NewClient(tc.have)
assert.Equal(t, tc.expected, client.GetResponseModes())
if tc.r != nil {
err := client.ValidateResponseModePolicy(tc.r)
if tc.err != "" {
require.NotNil(t, err)
assert.EqualError(t, err, tc.err)
assert.Equal(t, tc.desc, fosite.ErrorToRFC6749Error(err).WithExposeDebug(true).GetDescription())
} else {
assert.NoError(t, err)
}
}
})
}
}
func TestClient_IsPublic(t *testing.T) {
c := &FullClient{BaseClient: &BaseClient{}}
assert.False(t, c.IsPublic())
c.Public = true
assert.True(t, c.IsPublic())
}
func MustDecodeSecret(value string) *schema.PasswordDigest {
if secret, err := schema.DecodePasswordDigest(value); err != nil {
panic(err)
} else {
return secret
}
}