feat(oidc): pairwise subject identifiers (#3116)

Allows configuring clients with a sector identifier to allow pairwise subject types.
This commit is contained in:
James Elliott 2022-04-07 16:13:01 +10:00 committed by GitHub
parent 0a970aef8a
commit 8bb8207808
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 207 additions and 46 deletions

View File

@ -799,6 +799,11 @@ notifier:
## The client secret is a shared secret between Authelia and the consumer of this client. ## The client secret is a shared secret between Authelia and the consumer of this client.
# secret: this_is_a_secret # secret: this_is_a_secret
## Sector Identifiers are occasionally used to generate pairwise subject identifiers. In most cases this is not
## necessary. Read the documentation for more information.
## The subject identifier must be the host component of a URL, which is a domain name with an optional port.
# sector_identifier: example.com
## Sets the client to public. This should typically not be set, please see the documentation for usage. ## Sets the client to public. This should typically not be set, please see the documentation for usage.
# public: false # public: false

View File

@ -48,6 +48,7 @@ identity_providers:
- id: myapp - id: myapp
description: My Application description: My Application
secret: this_is_a_secret secret: this_is_a_secret
sector_identifier: ''
public: false public: false
authorization_policy: two_factor authorization_policy: two_factor
audience: [] audience: []
@ -73,7 +74,6 @@ identity_providers:
## Options ## Options
### hmac_secret ### hmac_secret
<div markdown="1"> <div markdown="1">
type: string type: string
{: .label .label-config .label-purple } {: .label .label-config .label-purple }
@ -87,7 +87,6 @@ byte string for the purpose of meeting the required format. You must [generate t
Should be defined using a [secret](../secrets.md) which is the recommended for containerized deployments. Should be defined using a [secret](../secrets.md) which is the recommended for containerized deployments.
### issuer_private_key ### issuer_private_key
<div markdown="1"> <div markdown="1">
type: string type: string
{: .label .label-config .label-purple } {: .label .label-config .label-purple }
@ -104,7 +103,6 @@ private key into your configuration.
Should be defined using a [secret](../secrets.md) which is the recommended for containerized deployments. Should be defined using a [secret](../secrets.md) which is the recommended for containerized deployments.
### access_token_lifespan ### access_token_lifespan
<div markdown="1"> <div markdown="1">
type: duration type: duration
{: .label .label-config .label-purple } {: .label .label-config .label-purple }
@ -118,7 +116,6 @@ The maximum lifetime of an access token. It's generally recommended keeping this
For more information read these docs about [token lifespan]. For more information read these docs about [token lifespan].
### authorize_code_lifespan ### authorize_code_lifespan
<div markdown="1"> <div markdown="1">
type: duration type: duration
{: .label .label-config .label-purple } {: .label .label-config .label-purple }
@ -132,7 +129,6 @@ The maximum lifetime of an authorize code. This can be rather short, as the auth
obtain the other token types. For more information read these docs about [token lifespan]. obtain the other token types. For more information read these docs about [token lifespan].
### id_token_lifespan ### id_token_lifespan
<div markdown="1"> <div markdown="1">
type: duration type: duration
{: .label .label-config .label-purple } {: .label .label-config .label-purple }
@ -145,7 +141,6 @@ required: no
The maximum lifetime of an ID token. For more information read these docs about [token lifespan]. The maximum lifetime of an ID token. For more information read these docs about [token lifespan].
### refresh_token_lifespan ### refresh_token_lifespan
<div markdown="1"> <div markdown="1">
type: string type: string
{: .label .label-config .label-purple } {: .label .label-config .label-purple }
@ -165,7 +160,6 @@ A good starting point is 50% more or 30 minutes more (which ever is less) time t
token lifespan is 90 minutes. token lifespan is 90 minutes.
### enable_client_debug_messages ### enable_client_debug_messages
<div markdown="1"> <div markdown="1">
type: boolean type: boolean
{: .label .label-config .label-purple } {: .label .label-config .label-purple }
@ -178,7 +172,6 @@ required: no
Allows additional debug messages to be sent to the clients. Allows additional debug messages to be sent to the clients.
### minimum_parameter_entropy ### minimum_parameter_entropy
<div markdown="1"> <div markdown="1">
type: integer type: integer
{: .label .label-config .label-purple } {: .label .label-config .label-purple }
@ -305,7 +298,6 @@ have the scheme http or https and do not have the hostname of localhost.
A list of clients to configure. The options for each client are described below. A list of clients to configure. The options for each client are described below.
#### id #### id
<div markdown="1"> <div markdown="1">
type: string type: string
{: .label .label-config .label-purple } {: .label .label-config .label-purple }
@ -317,7 +309,6 @@ The Client ID for this client. It must exactly match the Client ID configured in
consuming this client. consuming this client.
#### description #### description
<div markdown="1"> <div markdown="1">
type: string type: string
{: .label .label-config .label-purple } {: .label .label-config .label-purple }
@ -330,7 +321,6 @@ required: no
A friendly description for this client shown in the UI. This defaults to the same as the ID. A friendly description for this client shown in the UI. This defaults to the same as the ID.
#### secret #### secret
<div markdown="1"> <div markdown="1">
type: string type: string
{: .label .label-config .label-purple } {: .label .label-config .label-purple }
@ -345,8 +335,45 @@ You must [generate this option yourself](#generating-a-random-secret).
This must be provided when the client is a confidential client type, and must be blank when using the public client This must be provided when the client is a confidential client type, and must be blank when using the public client
type. To set the client type to public see the [public](#public) configuration option. type. To set the client type to public see the [public](#public) configuration option.
#### public #### sector_identifier
<div markdown="1">
type: string
{: .label .label-config .label-purple }
default: ''
{: .label .label-config .label-blue }
required: no
{: .label .label-config .label-red }
</div>
_**Important Note:** because adjusting this option will inevitably change the `sub` claim of all tokens generated for
the specified client, changing this should cause the relying party to detect all future authorizations as completely new
users._
Must be an empty string or the host component of a URL. This is commonly just the domain name, but may also include a
port.
Authelia utilizes UUID version 4 subject identifiers. By default the public subject identifier type is utilized for all
clients. This means the subject identifiers will be the same for all clients. This configuration option enables pairwise
for this client, and configures the sector identifier utilized for both the storage and the lookup of the subject
identifier.
1. All clients who do not have this configured will generate the same subject identifier for a particular user regardless
of which client obtains the ID token.
2. All clients which have the same sector identifier will:
1. have the same subject identifier for a particular user when compared to clients with the same sector identifier.
2. have a completely different subject identifier for a particular user whe compared to:
1. any client with the public subject identifier type.
2. any client with a differing sector identifier.
In specific but limited scenarios this option is beneficial for privacy reasons. In particular this is useful when the
party utilizing the _Authelia_ [OpenID Connect] Authorization Server is foreign and not controlled by the user. It would
prevent the third party utilizing the subject identifier with another third party in order to track the user.
Keep in mind depending on the other claims they may still be able to perform this tracking and it is not a silver bullet.
There are very few benefits when utilizing this in a homelab or business where no third party is utilizing
the server.
#### public
<div markdown="1"> <div markdown="1">
type: bool type: bool
{: .label .label-config .label-purple } {: .label .label-config .label-purple }
@ -364,7 +391,6 @@ blank string.
In addition to the standard rules for redirect URIs, public clients can use the `urn:ietf:wg:oauth:2.0:oob` redirect URI. In addition to the standard rules for redirect URIs, public clients can use the `urn:ietf:wg:oauth:2.0:oob` redirect URI.
#### authorization_policy #### authorization_policy
<div markdown="1"> <div markdown="1">
type: string type: string
{: .label .label-config .label-purple } {: .label .label-config .label-purple }
@ -377,7 +403,6 @@ required: no
The authorization policy for this client: either `one_factor` or `two_factor`. The authorization policy for this client: either `one_factor` or `two_factor`.
#### audience #### audience
<div markdown="1"> <div markdown="1">
type: list(string) type: list(string)
{: .label .label-config .label-purple } {: .label .label-config .label-purple }
@ -388,7 +413,6 @@ required: no
A list of audiences this client is allowed to request. A list of audiences this client is allowed to request.
#### scopes #### scopes
<div markdown="1"> <div markdown="1">
type: list(string) type: list(string)
{: .label .label-config .label-purple } {: .label .label-config .label-purple }
@ -403,7 +427,6 @@ information. The documentation for the application you want to use with Authelia
you with the scopes to allow. you with the scopes to allow.
#### redirect_uris #### redirect_uris
<div markdown="1"> <div markdown="1">
type: list(string) type: list(string)
{: .label .label-config .label-purple } {: .label .label-config .label-purple }
@ -425,7 +448,6 @@ their redirect URIs are as follows:
4. The client can ignore rule 3 and use `urn:ietf:wg:oauth:2.0:oob` if it is a [public](#public) client type. 4. The client can ignore rule 3 and use `urn:ietf:wg:oauth:2.0:oob` if it is a [public](#public) client type.
#### grant_types #### grant_types
<div markdown="1"> <div markdown="1">
type: list(string) type: list(string)
{: .label .label-config .label-purple } {: .label .label-config .label-purple }
@ -440,7 +462,6 @@ know what you're doing_. Valid options are: `implicit`, `refresh_token`, `author
`client_credentials`. `client_credentials`.
#### response_types #### response_types
<div markdown="1"> <div markdown="1">
type: list(string) type: list(string)
{: .label .label-config .label-purple } {: .label .label-config .label-purple }
@ -455,7 +476,6 @@ know what you're doing_. Valid options are: `code`, `code id_token`, `id_token`,
`token id_token code`. `token id_token code`.
#### response_modes #### response_modes
<div markdown="1"> <div markdown="1">
type: list(string) type: list(string)
{: .label .label-config .label-purple } {: .label .label-config .label-purple }
@ -469,7 +489,6 @@ A list of response modes this client can return. It is recommended that this isn
know what you're doing. Potential values are `form_post`, `query`, and `fragment`. know what you're doing. Potential values are `form_post`, `query`, and `fragment`.
#### userinfo_signing_algorithm #### userinfo_signing_algorithm
<div markdown="1"> <div markdown="1">
type: string type: string
{: .label .label-config .label-purple } {: .label .label-config .label-purple }

View File

@ -799,6 +799,11 @@ notifier:
## The client secret is a shared secret between Authelia and the consumer of this client. ## The client secret is a shared secret between Authelia and the consumer of this client.
# secret: this_is_a_secret # secret: this_is_a_secret
## Sector Identifiers are occasionally used to generate pairwise subject identifiers. In most cases this is not
## necessary. Read the documentation for more information.
## The subject identifier must be the host component of a URL, which is a domain name with an optional port.
# sector_identifier: example.com
## Sets the client to public. This should typically not be set, please see the documentation for usage. ## Sets the client to public. This should typically not be set, please see the documentation for usage.
# public: false # public: false

View File

@ -41,10 +41,11 @@ type OpenIDConnectCORSConfiguration struct {
// OpenIDConnectClientConfiguration configuration for an OpenID Connect client. // OpenIDConnectClientConfiguration configuration for an OpenID Connect client.
type OpenIDConnectClientConfiguration struct { type OpenIDConnectClientConfiguration struct {
ID string `koanf:"id"` ID string `koanf:"id"`
Description string `koanf:"description"` Description string `koanf:"description"`
Secret string `koanf:"secret"` Secret string `koanf:"secret"`
Public bool `koanf:"public"` SectorIdentifier url.URL `koanf:"sector_identifier"`
Public bool `koanf:"public"`
Policy string `koanf:"authorization_policy"` Policy string `koanf:"authorization_policy"`

View File

@ -155,6 +155,12 @@ const (
"'%s' but one option is configured as '%s'" "'%s' but one option is configured as '%s'"
errFmtOIDCClientInvalidUserinfoAlgorithm = "identity_providers: oidc: client '%s': option " + errFmtOIDCClientInvalidUserinfoAlgorithm = "identity_providers: oidc: client '%s': option " +
"'userinfo_signing_algorithm' must be one of '%s' but it is configured as '%s'" "'userinfo_signing_algorithm' must be one of '%s' but it is configured as '%s'"
errFmtOIDCClientInvalidSectorIdentifier = "identity_providers: oidc: client '%s': option " +
"'sector_identifier' with value '%s': must be a URL with only the host component for example '%s' but it has a %s with the value '%s'"
errFmtOIDCClientInvalidSectorIdentifierWithoutValue = "identity_providers: oidc: client '%s': option " +
"'sector_identifier' with value '%s': must be a URL with only the host component for example '%s' but it has a %s"
errFmtOIDCClientInvalidSectorIdentifierHost = "identity_providers: oidc: client '%s': option " +
"'sector_identifier' with value '%s': must be a URL with only the host component but appears to be invalid"
errFmtOIDCServerInsecureParameterEntropy = "openid connect provider: SECURITY ISSUE - minimum parameter entropy is " + errFmtOIDCServerInsecureParameterEntropy = "openid connect provider: SECURITY ISSUE - minimum parameter entropy is " +
"configured to an unsafe value, it should be above 8 but it's configured to %d" "configured to an unsafe value, it should be above 8 but it's configured to %d"
) )
@ -482,8 +488,9 @@ var ValidKeys = []string{
"identity_providers.oidc.clients", "identity_providers.oidc.clients",
"identity_providers.oidc.clients[].id", "identity_providers.oidc.clients[].id",
"identity_providers.oidc.clients[].description", "identity_providers.oidc.clients[].description",
"identity_providers.oidc.clients[].public",
"identity_providers.oidc.clients[].secret", "identity_providers.oidc.clients[].secret",
"identity_providers.oidc.clients[].sector_identifier",
"identity_providers.oidc.clients[].public",
"identity_providers.oidc.clients[].redirect_uris", "identity_providers.oidc.clients[].redirect_uris",
"identity_providers.oidc.clients[].authorization_policy", "identity_providers.oidc.clients[].authorization_policy",
"identity_providers.oidc.clients[].scopes", "identity_providers.oidc.clients[].scopes",

View File

@ -151,6 +151,7 @@ func validateOIDCClients(config *schema.OpenIDConnectConfiguration, validator *s
validator.Push(fmt.Errorf(errFmtOIDCClientInvalidPolicy, client.ID, client.Policy)) validator.Push(fmt.Errorf(errFmtOIDCClientInvalidPolicy, client.ID, client.Policy))
} }
validateOIDCClientSectorIdentifier(client, validator)
validateOIDCClientScopes(c, config, validator) validateOIDCClientScopes(c, config, validator)
validateOIDCClientGrantTypes(c, config, validator) validateOIDCClientGrantTypes(c, config, validator)
validateOIDCClientResponseTypes(c, config, validator) validateOIDCClientResponseTypes(c, config, validator)
@ -168,6 +169,38 @@ func validateOIDCClients(config *schema.OpenIDConnectConfiguration, validator *s
} }
} }
func validateOIDCClientSectorIdentifier(client schema.OpenIDConnectClientConfiguration, validator *schema.StructValidator) {
if client.SectorIdentifier.String() != "" {
if client.SectorIdentifier.Scheme != "" {
validator.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "scheme", client.SectorIdentifier.Scheme))
if client.SectorIdentifier.Path != "" {
validator.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "path", client.SectorIdentifier.Path))
}
if client.SectorIdentifier.RawQuery != "" {
validator.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "query", client.SectorIdentifier.RawQuery))
}
if client.SectorIdentifier.Fragment != "" {
validator.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "fragment", client.SectorIdentifier.Fragment))
}
if client.SectorIdentifier.User != nil {
if client.SectorIdentifier.User.Username() != "" {
validator.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "username", client.SectorIdentifier.User.Username()))
}
if _, set := client.SectorIdentifier.User.Password(); set {
validator.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifierWithoutValue, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "password"))
}
}
} else if client.SectorIdentifier.Host == "" {
validator.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifierHost, client.ID, client.SectorIdentifier.String()))
}
}
}
func validateOIDCClientScopes(c int, configuration *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) { func validateOIDCClientScopes(c int, configuration *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) {
if len(configuration.Clients[c].Scopes) == 0 { if len(configuration.Clients[c].Scopes) == 0 {
configuration.Clients[c].Scopes = schema.DefaultOpenIDConnectClientConfiguration.Scopes configuration.Clients[c].Scopes = schema.DefaultOpenIDConnectClientConfiguration.Scopes

View File

@ -3,6 +3,7 @@ package validator
import ( import (
"errors" "errors"
"fmt" "fmt"
"net/url"
"testing" "testing"
"time" "time"
@ -151,13 +152,22 @@ func TestShouldRaiseErrorWhenOIDCServerNoClients(t *testing.T) {
} }
func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
mustParseURL := func(u string) url.URL {
out, err := url.Parse(u)
if err != nil {
panic(err)
}
return *out
}
testCases := []struct { testCases := []struct {
Name string Name string
Clients []schema.OpenIDConnectClientConfiguration Clients []schema.OpenIDConnectClientConfiguration
Errors []error Errors []string
}{ }{
{ {
Name: "empty", Name: "EmptyIDAndSecret",
Clients: []schema.OpenIDConnectClientConfiguration{ Clients: []schema.OpenIDConnectClientConfiguration{
{ {
ID: "", ID: "",
@ -166,13 +176,13 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
RedirectURIs: []string{}, RedirectURIs: []string{},
}, },
}, },
Errors: []error{ Errors: []string{
fmt.Errorf(errFmtOIDCClientInvalidSecret, ""), fmt.Sprintf(errFmtOIDCClientInvalidSecret, ""),
errors.New(errFmtOIDCClientsWithEmptyID), errFmtOIDCClientsWithEmptyID,
}, },
}, },
{ {
Name: "client-1", Name: "InvalidPolicy",
Clients: []schema.OpenIDConnectClientConfiguration{ Clients: []schema.OpenIDConnectClientConfiguration{
{ {
ID: "client-1", ID: "client-1",
@ -183,10 +193,10 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
}, },
}, },
}, },
Errors: []error{fmt.Errorf(errFmtOIDCClientInvalidPolicy, "client-1", "a-policy")}, Errors: []string{fmt.Sprintf(errFmtOIDCClientInvalidPolicy, "client-1", "a-policy")},
}, },
{ {
Name: "client-duplicate", Name: "ClientIDDuplicated",
Clients: []schema.OpenIDConnectClientConfiguration{ Clients: []schema.OpenIDConnectClientConfiguration{
{ {
ID: "client-x", ID: "client-x",
@ -201,10 +211,10 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
RedirectURIs: []string{}, RedirectURIs: []string{},
}, },
}, },
Errors: []error{errors.New(errFmtOIDCClientsDuplicateID)}, Errors: []string{errFmtOIDCClientsDuplicateID},
}, },
{ {
Name: "client-check-uri-parse", Name: "RedirectURIInvalid",
Clients: []schema.OpenIDConnectClientConfiguration{ Clients: []schema.OpenIDConnectClientConfiguration{
{ {
ID: "client-check-uri-parse", ID: "client-check-uri-parse",
@ -215,12 +225,12 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
}, },
}, },
}, },
Errors: []error{ Errors: []string{
fmt.Errorf(errFmtOIDCClientRedirectURICantBeParsed, "client-check-uri-parse", "http://abc@%two", errors.New("parse \"http://abc@%two\": invalid URL escape \"%tw\"")), fmt.Sprintf(errFmtOIDCClientRedirectURICantBeParsed, "client-check-uri-parse", "http://abc@%two", errors.New("parse \"http://abc@%two\": invalid URL escape \"%tw\"")),
}, },
}, },
{ {
Name: "client-check-uri-abs", Name: "RedirectURINotAbsolute",
Clients: []schema.OpenIDConnectClientConfiguration{ Clients: []schema.OpenIDConnectClientConfiguration{
{ {
ID: "client-check-uri-abs", ID: "client-check-uri-abs",
@ -231,8 +241,47 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
}, },
}, },
}, },
Errors: []error{ Errors: []string{
fmt.Errorf(errFmtOIDCClientRedirectURIAbsolute, "client-check-uri-abs", "google.com"), fmt.Sprintf(errFmtOIDCClientRedirectURIAbsolute, "client-check-uri-abs", "google.com"),
},
},
{
Name: "InvalidSectorIdentifierInvalidURL",
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "client-invalid-sector",
Secret: "a-secret",
Policy: policyTwoFactor,
RedirectURIs: []string{
"https://google.com",
},
SectorIdentifier: mustParseURL("https://user:pass@example.com/path?query=abc#fragment"),
},
},
Errors: []string{
fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", "example.com", "scheme", "https"),
fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", "example.com", "path", "/path"),
fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", "example.com", "query", "query=abc"),
fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", "example.com", "fragment", "fragment"),
fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", "example.com", "username", "user"),
fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifierWithoutValue, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", "example.com", "password"),
},
},
{
Name: "InvalidSectorIdentifierInvalidHost",
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "client-invalid-sector",
Secret: "a-secret",
Policy: policyTwoFactor,
RedirectURIs: []string{
"https://google.com",
},
SectorIdentifier: mustParseURL("example.com/path?query=abc#fragment"),
},
},
Errors: []string{
fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifierHost, "client-invalid-sector", "example.com/path?query=abc#fragment"),
}, },
}, },
} }
@ -250,7 +299,14 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
ValidateIdentityProviders(config, validator) ValidateIdentityProviders(config, validator)
assert.ElementsMatch(t, validator.Errors(), tc.Errors) errs := validator.Errors()
require.Len(t, errs, len(tc.Errors))
for i, errStr := range tc.Errors {
t.Run(fmt.Sprintf("Error%d", i+1), func(t *testing.T) {
assert.EqualError(t, errs[i], errStr)
})
}
}) })
} }
} }

View File

@ -12,10 +12,11 @@ import (
// NewClient creates a new Client. // NewClient creates a new Client.
func NewClient(config schema.OpenIDConnectClientConfiguration) (client *Client) { func NewClient(config schema.OpenIDConnectClientConfiguration) (client *Client) {
client = &Client{ client = &Client{
ID: config.ID, ID: config.ID,
Description: config.Description, Description: config.Description,
Secret: []byte(config.Secret), Secret: []byte(config.Secret),
Public: config.Public, SectorIdentifier: config.SectorIdentifier.String(),
Public: config.Public,
Policy: authorization.PolicyToLevel(config.Policy), Policy: authorization.PolicyToLevel(config.Policy),

View File

@ -1,6 +1,7 @@
package oidc package oidc
import ( import (
"net/url"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -27,6 +28,39 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_BadIssuerKey(t *testing.
assert.Error(t, err, "abc") assert.Error(t, err, "abc")
} }
func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing.T) {
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
IssuerPrivateKey: exampleIssuerPrivateKey,
EnablePKCEPlainChallenge: true,
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "a-client",
Secret: "a-client-secret",
SectorIdentifier: url.URL{Host: "google.com"},
Policy: "one_factor",
RedirectURIs: []string{
"https://google.com",
},
},
},
}, nil)
assert.NoError(t, err)
assert.True(t, provider.Pairwise())
disco := provider.GetOpenIDConnectWellKnownConfiguration("https://example.com")
assert.Len(t, disco.SubjectTypesSupported, 2)
assert.Contains(t, disco.SubjectTypesSupported, "public")
assert.Contains(t, disco.SubjectTypesSupported, "pairwise")
assert.Len(t, disco.CodeChallengeMethodsSupported, 2)
assert.Contains(t, disco.CodeChallengeMethodsSupported, "S256")
assert.Contains(t, disco.CodeChallengeMethodsSupported, "S256")
}
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *testing.T) { func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *testing.T) {
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{ provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
IssuerPrivateKey: exampleIssuerPrivateKey, IssuerPrivateKey: exampleIssuerPrivateKey,

View File

@ -97,9 +97,9 @@ type OpenIDConnectStore struct {
// Client represents the client internally. // Client represents the client internally.
type Client struct { type Client struct {
ID string ID string
SectorIdentifier string
Description string Description string
Secret []byte Secret []byte
SectorIdentifier string
Public bool Public bool
Policy authorization.Level Policy authorization.Level