authelia/internal/configuration/validator/identity_providers_test.go
James Elliott ef549f851d
feat(oidc): add additional config options, accurate token times, and refactoring (#1991)
* This gives admins more control over their OIDC installation exposing options that had defaults before. Things like lifespans for authorize codes, access tokens, id tokens, refresh tokens, a option to enable the debug client messages, minimum parameter entropy. It also allows admins to configure the response modes.
* Additionally this records specific values about a users session indicating when they performed a specific authz factor so this is represented in the token accurately. 
* Lastly we also implemented a OIDC key manager which calculates the kid for jwk's using the SHA1 digest instead of being static, or more specifically the first 7 chars. As per https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key#section-8.1.1 the kid should not exceed 8 chars. While it's allowed to exceed 8 chars, it must only be done so with a compelling reason, which we do not have.
2021-07-04 09:44:30 +10:00

316 lines
11 KiB
Go

package validator
import (
"errors"
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/authelia/authelia/internal/configuration/schema"
)
func TestShouldRaiseErrorWhenInvalidOIDCServerConfiguration(t *testing.T) {
validator := schema.NewStructValidator()
config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "abc",
IssuerPrivateKey: "",
},
}
ValidateIdentityProviders(config, validator)
require.Len(t, validator.Errors(), 2)
assert.EqualError(t, validator.Errors()[0], "OIDC Server issuer private key must be provided")
assert.EqualError(t, validator.Errors()[1], "OIDC Server has no clients defined")
}
func TestShouldRaiseErrorWhenOIDCServerIssuerPrivateKeyPathInvalid(t *testing.T) {
validator := schema.NewStructValidator()
config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
IssuerPrivateKey: "key-material",
},
}
ValidateIdentityProviders(config, validator)
require.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], "OIDC Server has no clients defined")
}
func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
validator := schema.NewStructValidator()
config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
IssuerPrivateKey: "key-material",
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "",
Secret: "",
Policy: "",
RedirectURIs: []string{
"tcp://google.com",
},
},
{
ID: "a-client",
Secret: "a-secret",
Policy: "a-policy",
RedirectURIs: []string{
"https://google.com",
},
},
{
ID: "a-client",
Secret: "a-secret",
Policy: "a-policy",
RedirectURIs: []string{
"https://google.com",
},
},
{
ID: "client-check-uri-parse",
Secret: "a-secret",
Policy: twoFactorPolicy,
RedirectURIs: []string{
"http://abc@%two",
},
},
},
},
}
ValidateIdentityProviders(config, validator)
require.Len(t, validator.Errors(), 7)
assert.Equal(t, schema.DefaultOpenIDConnectClientConfiguration.Policy, config.OIDC.Clients[0].Policy)
assert.EqualError(t, validator.Errors()[0], fmt.Sprintf(errFmtOIDCServerClientInvalidSecret, ""))
assert.EqualError(t, validator.Errors()[1], fmt.Sprintf(errFmtOIDCServerClientRedirectURI, "", "tcp://google.com", "tcp"))
assert.EqualError(t, validator.Errors()[2], fmt.Sprintf(errFmtOIDCServerClientInvalidPolicy, "a-client", "a-policy"))
assert.EqualError(t, validator.Errors()[3], fmt.Sprintf(errFmtOIDCServerClientInvalidPolicy, "a-client", "a-policy"))
assert.EqualError(t, validator.Errors()[4], fmt.Sprintf(errFmtOIDCServerClientRedirectURICantBeParsed, "client-check-uri-parse", "http://abc@%two", errors.New("parse \"http://abc@%two\": invalid URL escape \"%tw\"")))
assert.EqualError(t, validator.Errors()[5], "OIDC Server has one or more clients with an empty ID")
assert.EqualError(t, validator.Errors()[6], "OIDC Server has clients with duplicate ID's")
}
func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadScopes(t *testing.T) {
validator := schema.NewStructValidator()
config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
IssuerPrivateKey: "key-material",
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "good_id",
Secret: "good_secret",
Policy: "two_factor",
Scopes: []string{"openid", "bad_scope"},
RedirectURIs: []string{
"https://google.com/callback",
},
},
},
},
}
ValidateIdentityProviders(config, validator)
require.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], "OIDC Client with ID 'good_id' has an invalid scope "+
"'bad_scope', must be one of: 'openid', 'email', 'profile', 'groups', 'offline_access'")
}
func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadGrantTypes(t *testing.T) {
validator := schema.NewStructValidator()
config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
IssuerPrivateKey: "key-material",
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "good_id",
Secret: "good_secret",
Policy: "two_factor",
GrantTypes: []string{"bad_grant_type"},
RedirectURIs: []string{
"https://google.com/callback",
},
},
},
},
}
ValidateIdentityProviders(config, validator)
require.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], "OIDC Client with ID 'good_id' has an invalid grant type "+
"'bad_grant_type', must be one of: 'implicit', 'refresh_token', 'authorization_code', "+
"'password', 'client_credentials'")
}
func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadResponseModes(t *testing.T) {
validator := schema.NewStructValidator()
config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
IssuerPrivateKey: "key-material",
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "good_id",
Secret: "good_secret",
Policy: "two_factor",
ResponseModes: []string{"bad_responsemode"},
RedirectURIs: []string{
"https://google.com/callback",
},
},
},
},
}
ValidateIdentityProviders(config, validator)
require.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], "OIDC Client with ID 'good_id' has an invalid response mode "+
"'bad_responsemode', must be one of: 'form_post', 'query', 'fragment'")
}
func TestValidateIdentityProvidersShouldRaiseWarningOnSecurityIssue(t *testing.T) {
validator := schema.NewStructValidator()
config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "abc",
IssuerPrivateKey: "abc",
MinimumParameterEntropy: 1,
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "good_id",
Secret: "good_secret",
Policy: "two_factor",
RedirectURIs: []string{
"https://google.com/callback",
},
},
},
},
}
ValidateIdentityProviders(config, validator)
assert.Len(t, validator.Errors(), 0)
require.Len(t, validator.Warnings(), 1)
assert.EqualError(t, validator.Warnings()[0], "SECURITY ISSUE: OIDC minimum parameter entropy is configured to an unsafe value, it should be above 8 but it's configured to 1.")
}
func TestValidateIdentityProvidersShouldSetDefaultValues(t *testing.T) {
validator := schema.NewStructValidator()
config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
IssuerPrivateKey: "../../../README.md",
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "a-client",
Secret: "a-client-secret",
RedirectURIs: []string{
"https://google.com",
},
},
{
ID: "b-client",
Description: "Normal Description",
Secret: "b-client-secret",
Policy: oneFactorPolicy,
RedirectURIs: []string{
"https://google.com",
},
Scopes: []string{
"groups",
},
GrantTypes: []string{
"refresh_token",
},
ResponseTypes: []string{
"token",
"code",
},
ResponseModes: []string{
"form_post",
"fragment",
},
},
},
},
}
ValidateIdentityProviders(config, validator)
assert.Len(t, validator.Warnings(), 0)
assert.Len(t, validator.Errors(), 0)
// Assert Clients[0] Policy is set to the default, and the default doesn't override Clients[1]'s Policy.
assert.Equal(t, config.OIDC.Clients[0].Policy, twoFactorPolicy)
assert.Equal(t, config.OIDC.Clients[1].Policy, oneFactorPolicy)
// Assert Clients[0] Description is set to the Clients[0] ID, and Clients[1]'s Description is not overridden.
assert.Equal(t, config.OIDC.Clients[0].ID, config.OIDC.Clients[0].Description)
assert.Equal(t, "Normal Description", config.OIDC.Clients[1].Description)
// Assert Clients[0] ends up configured with the default Scopes.
require.Len(t, config.OIDC.Clients[0].Scopes, 4)
assert.Equal(t, "openid", config.OIDC.Clients[0].Scopes[0])
assert.Equal(t, "groups", config.OIDC.Clients[0].Scopes[1])
assert.Equal(t, "profile", config.OIDC.Clients[0].Scopes[2])
assert.Equal(t, "email", config.OIDC.Clients[0].Scopes[3])
// Assert Clients[1] ends up configured with the configured Scopes and the openid Scope.
require.Len(t, config.OIDC.Clients[1].Scopes, 2)
assert.Equal(t, "groups", config.OIDC.Clients[1].Scopes[0])
assert.Equal(t, "openid", config.OIDC.Clients[1].Scopes[1])
// Assert Clients[0] ends up configured with the default GrantTypes.
require.Len(t, config.OIDC.Clients[0].GrantTypes, 2)
assert.Equal(t, "refresh_token", config.OIDC.Clients[0].GrantTypes[0])
assert.Equal(t, "authorization_code", config.OIDC.Clients[0].GrantTypes[1])
// Assert Clients[1] ends up configured with only the configured GrantTypes.
require.Len(t, config.OIDC.Clients[1].GrantTypes, 1)
assert.Equal(t, "refresh_token", config.OIDC.Clients[1].GrantTypes[0])
// Assert Clients[0] ends up configured with the default ResponseTypes.
require.Len(t, config.OIDC.Clients[0].ResponseTypes, 1)
assert.Equal(t, "code", config.OIDC.Clients[0].ResponseTypes[0])
// Assert Clients[1] ends up configured only with the configured ResponseTypes.
require.Len(t, config.OIDC.Clients[1].ResponseTypes, 2)
assert.Equal(t, "token", config.OIDC.Clients[1].ResponseTypes[0])
assert.Equal(t, "code", config.OIDC.Clients[1].ResponseTypes[1])
// Assert Clients[0] ends up configured with the default ResponseModes.
require.Len(t, config.OIDC.Clients[0].ResponseModes, 3)
assert.Equal(t, "form_post", config.OIDC.Clients[0].ResponseModes[0])
assert.Equal(t, "query", config.OIDC.Clients[0].ResponseModes[1])
assert.Equal(t, "fragment", config.OIDC.Clients[0].ResponseModes[2])
// Assert Clients[1] ends up configured only with the configured ResponseModes.
require.Len(t, config.OIDC.Clients[1].ResponseModes, 2)
assert.Equal(t, "form_post", config.OIDC.Clients[1].ResponseModes[0])
assert.Equal(t, "fragment", config.OIDC.Clients[1].ResponseModes[1])
assert.Equal(t, false, config.OIDC.EnableClientDebugMessages)
assert.Equal(t, time.Hour, config.OIDC.AccessTokenLifespan)
assert.Equal(t, time.Minute, config.OIDC.AuthorizeCodeLifespan)
assert.Equal(t, time.Hour, config.OIDC.IDTokenLifespan)
assert.Equal(t, time.Minute*90, config.OIDC.RefreshTokenLifespan)
}