-module(oidcc_provider_configuration).
-feature(maybe_expr, enable).
-include("internal/doc.hrl").
?MODULEDOC("""
Tooling to load and parse Openid Configuration.
## Records
To use the record, import the definition:
```erlang
-include_lib(["oidcc/include/oidcc_provider_configuration.hrl"]).
```
## Telemetry
See [`Oidcc.ProviderConfiguration`](`m:'Elixir.Oidcc.ProviderConfiguration'`).
""").
?MODULEDOC(#{since => <<"3.0.0">>}).
-include("oidcc_provider_configuration.hrl").
-export([decode_configuration/1]).
-export([decode_configuration/2]).
-export([load_configuration/1]).
-export([load_configuration/2]).
-export([load_jwks/2]).
-export_type([error/0]).
-export_type([opts/0]).
-export_type([quirks/0]).
-export_type([t/0]).
?DOC("""
Allow Specification Non-compliance.
## Exceptions
* `allow_unsafe_http` - Allow unsafe HTTP. Use this for development
providers and **never in production**.
* `document_overrides` - a map to merge with the real OIDD document,
in case the OP left out some values.
* `issuer_regex` - Optional regex pattern to match against the issuer claim
instead of requiring an exact match. This may be necessary for certain providers that do not
conform to the OpenID specification, such as Microsoft Entra ID where
the issuer is 'https://login.microsoftonline.com/{tenantid}/v2.0' in the
[OpenID configuration](https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration).
""").
?DOC(#{since => <<"3.1.0">>}).
-type quirks() :: #{
allow_unsafe_http => boolean(),
document_overrides => map(),
issuer_regex => binary()
}.
?DOC("""
Configure configuration loading / parsing.
## Parameters
* `fallback_expiry` - How long to keep configuration cached if the server doesn't specify expiry.
* `request_opts` - config for HTTP request.
""").
?DOC(#{since => <<"3.0.0">>}).
-type opts() :: #{
fallback_expiry => timeout(),
request_opts => oidcc_http_util:request_opts(),
quirks => quirks()
}.
?DOC("""
Record containing OpenID and OAuth 2.0 Configuration.
See:
* https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
* https://datatracker.ietf.org/doc/html/draft-jones-oauth-discovery-01#section-4.1
* https://openid.net/specs/openid-connect-rpinitiated-1_0.html#OPMetadata
All unrecognized fields are stored in `extra_fields`.
""").
?DOC(#{since => <<"3.0.0">>}).
-type t() ::
#oidcc_provider_configuration{
issuer :: uri_string:uri_string(),
issuer_regex :: binary() | undefined,
authorization_endpoint :: uri_string:uri_string(),
token_endpoint :: uri_string:uri_string() | undefined,
userinfo_endpoint :: uri_string:uri_string() | undefined,
jwks_uri :: uri_string:uri_string() | undefined,
registration_endpoint :: uri_string:uri_string() | undefined,
scopes_supported :: [binary()] | undefined,
response_types_supported :: [binary()],
response_modes_supported :: [binary()],
grant_types_supported :: [binary()],
acr_values_supported :: [binary()] | undefined,
subject_types_supported :: [pairwise | public],
id_token_signing_alg_values_supported :: [binary()],
id_token_encryption_alg_values_supported ::
[binary()] | undefined,
id_token_encryption_enc_values_supported ::
[binary()] | undefined,
userinfo_signing_alg_values_supported :: [binary()] | undefined,
userinfo_encryption_alg_values_supported ::
[binary()] | undefined,
userinfo_encryption_enc_values_supported ::
[binary()] | undefined,
request_object_signing_alg_values_supported ::
[binary()] | undefined,
request_object_encryption_alg_values_supported ::
[binary()] | undefined,
request_object_encryption_enc_values_supported ::
[binary()] | undefined,
token_endpoint_auth_methods_supported :: [binary()],
token_endpoint_auth_signing_alg_values_supported ::
[binary()] | undefined,
display_values_supported :: [binary()] | undefined,
claim_types_supported :: [normal | aggregated | distributed],
claims_supported :: [binary()] | undefined,
service_documentation :: uri_string:uri_string() | undefined,
claims_locales_supported :: [binary()] | undefined,
ui_locales_supported :: [binary()] | undefined,
claims_parameter_supported :: boolean(),
request_parameter_supported :: boolean(),
request_uri_parameter_supported :: boolean(),
require_request_uri_registration :: boolean(),
op_policy_uri :: uri_string:uri_string() | undefined,
op_tos_uri :: uri_string:uri_string() | undefined,
revocation_endpoint :: uri_string:uri_string() | undefined,
revocation_endpoint_auth_methods_supported :: [binary()],
revocation_endpoint_auth_signing_alg_values_supported ::
[binary()] | undefined,
introspection_endpoint :: uri_string:uri_string() | undefined,
introspection_endpoint_auth_methods_supported :: [binary()],
introspection_endpoint_auth_signing_alg_values_supported ::
[binary()] | undefined,
code_challenge_methods_supported :: [binary()] | undefined,
end_session_endpoint :: uri_string:uri_string() | undefined,
require_pushed_authorization_requests :: boolean(),
pushed_authorization_request_endpoint :: uri_string:uri_string() | undefined,
authorization_signing_alg_values_supported :: [binary()] | undefined,
authorization_encryption_alg_values_supported :: [binary()] | undefined,
authorization_encryption_enc_values_supported :: [binary()] | undefined,
authorization_response_iss_parameter_supported :: boolean(),
dpop_signing_alg_values_supported :: [binary()] | undefined,
require_signed_request_object :: boolean(),
mtls_endpoint_aliases :: #{binary() => uri_string:uri_string()},
extra_fields :: #{binary() => term()}
}.
?DOC(#{since => <<"3.0.0">>}).
-type error() ::
invalid_content_type
| {issuer_mismatch, Issuer :: binary()}
| oidcc_decode_util:error()
| oidcc_http_util:error().
-define(DEFAULT_CONFIG_EXPIRY, timer:minutes(15)).
-telemetry_event(#{
event => [oidcc, load_configuration, start],
description => <<"Emitted at the start of loading the provider configuration">>,
measurements => <<"#{system_time => non_neg_integer()}">>,
metadata => <<"#{issuer => uri_string:uri_string()}">>
}).
-telemetry_event(#{
event => [oidcc, load_configuration, stop],
description => <<"Emitted at the end of loading the provider configuration">>,
measurements => <<"#{duration => integer(), monotonic_time => integer()}">>,
metadata => <<"#{issuer => uri_string:uri_string()}">>
}).
-telemetry_event(#{
event => [oidcc, load_configuration, exception],
description => <<"Emitted at the end of loading the provider configuration">>,
measurements => <<"#{duration => integer(), monotonic_time => integer()}">>,
metadata => <<"#{issuer => uri_string:uri_string()}">>
}).
-telemetry_event(#{
event => [oidcc, load_jwks, start],
description => <<"Emitted at the start of loading the provider jwks">>,
measurements => <<"#{system_time => non_neg_integer()}">>,
metadata => <<"#{jwks_uri => uri_string:uri_string()}">>
}).
-telemetry_event(#{
event => [oidcc, load_jwks, stop],
description => <<"Emitted at the end of loading the provider jwks">>,
measurements => <<"#{duration => integer(), monotonic_time => integer()}">>,
metadata => <<"#{jwks_uri => uri_string:uri_string()}">>
}).
-telemetry_event(#{
event => [oidcc, load_jwks, exception],
description => <<"Emitted at the end of loading the provider jwks">>,
measurements => <<"#{duration => integer(), monotonic_time => integer()}">>,
metadata => <<"#{jwks_uri => uri_string:uri_string()}">>
}).
?DOC("""
Load OpenID Configuration into a `t:oidcc_provider_configuration:t/0` record.
## Examples
```erlang
{ok, #oidcc_provider_configuration{}} =
oidcc_provider_configuration:load_configuration("https://accounts.google.com").
```
""").
?DOC(#{since => <<"3.0.0">>}).
-spec load_configuration(Issuer, Opts) ->
{ok, {Configuration :: t(), Expiry :: pos_integer()}} | {error, error()}
when
Issuer :: uri_string:uri_string(),
Opts :: opts().
load_configuration(Issuer0, Opts) ->
Issuer = binary:list_to_bin([Issuer0]),
TelemetryOpts = #{topic => [oidcc, load_configuration], extra_meta => #{issuer => Issuer}},
RequestOpts = maps:get(request_opts, Opts, #{}),
RequestUrl = url_join(".well-known/openid-configuration", Issuer),
Request = {RequestUrl, []},
Quirks = maps:get(quirks, Opts, #{}),
% this quirk is deprecated, but we keep the support for backwards compatibility.
AllowIssuerMismatch = maps:get(allow_issuer_mismatch, Quirks, false),
DefaultExpiry = maps:get(fallback_expiry, Opts, ?DEFAULT_CONFIG_EXPIRY),
maybe
{ok, {{json, ConfigurationMap}, Headers}} ?=
oidcc_http_util:request(get, Request, TelemetryOpts, RequestOpts),
Expiry = oidcc_http_util:headers_to_cache_deadline(Headers, DefaultExpiry),
{ok,
#oidcc_provider_configuration{issuer = ConfigIssuer, issuer_regex = ConfigIssuerRegex} =
Configuration} ?=
decode_configuration(ConfigurationMap, #{quirks => Quirks}),
case ConfigIssuer of
Issuer ->
{ok, {Configuration, Expiry}};
_ when is_binary(ConfigIssuerRegex) ->
case re:run(Issuer, ConfigIssuerRegex, [{capture, none}]) of
match ->
{ok, {Configuration, Expiry}};
nomatch ->
{error, {issuer_mismatch, ConfigIssuer}}
end;
_DifferentIssuer when AllowIssuerMismatch -> {ok, {Configuration, Expiry}};
DifferentIssuer when not AllowIssuerMismatch ->
{error, {issuer_mismatch, DifferentIssuer}}
end
else
{error, Reason} ->
{error, Reason};
{ok, {{_Format, _Body}, _Headers}} ->
{error, invalid_content_type}
end.
?DOC("See `load_configuration/2`.").
?DOC(#{since => <<"3.1.0">>}).
-spec load_configuration(Issuer) ->
{ok, {Configuration :: t(), Expiry :: pos_integer()}} | {error, error()}
when
Issuer :: uri_string:uri_string().
load_configuration(Issuer) -> load_configuration(Issuer, #{}).
?DOC("""
Load JWKs into a `t:jose_jwk:key/0` record.
## Examples
```erlang
{ok, #jose_jwk{}} =
oidcc_provider_configuration:load_jwks("https://www.googleapis.com/oauth2/v3/certs").
```
""").
?DOC(#{since => <<"3.0.0">>}).
-spec load_jwks(JwksUri, Opts) ->
{ok, {Jwks :: jose_jwk:key(), Expiry :: pos_integer()}} | {error, term()}
when
JwksUri :: uri_string:uri_string(),
Opts :: opts().
load_jwks(JwksUri, Opts) ->
TelemetryOpts = #{topic => [oidcc, load_jwks], extra_meta => #{jwks_uri => JwksUri}},
RequestOpts = maps:get(request_opts, Opts, #{}),
DefaultExpiry = maps:get(fallback_expiry, Opts, ?DEFAULT_CONFIG_EXPIRY),
maybe
{ok, {{json, JwksBinary}, Headers}} ?=
oidcc_http_util:request(get, {JwksUri, []}, TelemetryOpts, RequestOpts),
Expiry = oidcc_http_util:headers_to_cache_deadline(Headers, DefaultExpiry),
Jwks = jose_jwk:from(JwksBinary),
{ok, {Jwks, Expiry}}
else
{error, Reason} -> {error, Reason};
{ok, {{_Format, _Body}, _Headers}} -> {error, invalid_content_type}
end.
?DOC("""
Decode JSON into a `t:oidcc_provider_configuration:t/0` record.
## Examples
```erlang
{ok, {{"HTTP/1.1",200,"OK"}, _Headers, Body}} =
httpc:request("https://accounts.google.com/.well-known/openid-configuration"),
{ok, DecodedJson} = your_json_lib:decode(Body),
{ok, #oidcc_provider_configuration{}} =
oidcc_provider_configuration:decode_configuration(DecodedJson).
```
""").
?DOC(#{since => <<"3.1.0">>}).
-spec decode_configuration(Configuration, Opts) -> {ok, t()} | {error, error()} when
Configuration :: map(), Opts :: opts().
decode_configuration(Configuration0, Opts) ->
Quirks = maps:get(quirks, Opts, #{}),
AllowUnsafeHttp = maps:get(allow_unsafe_http, Quirks, false),
IssuerRegex = maps:get(issuer_regex, Quirks, undefined),
DocumentOverrides = maps:get(document_overrides, Quirks, #{}),
Configuration = maps:merge(Configuration0, DocumentOverrides),
maybe
{ok, {
#{
issuer := Issuer,
authorization_endpoint := AuthorizationEndpoint,
authorization_endpoint := AuthorizationEndpoint,
token_endpoint := TokenEndpoint,
userinfo_endpoint := UserinfoEndpoint,
jwks_uri := JwksUri,
registration_endpoint := RegistrationEndpoint,
scopes_supported := ScopesSupported,
response_types_supported := ResponseTypesSupported,
response_modes_supported := ResponseModesSupported,
grant_types_supported := GrantTypesSupported,
acr_values_supported := AcrValuesSupported,
subject_types_supported := SubjectTypesSupported,
id_token_signing_alg_values_supported := IdTokenSigningAlgValuesSupported,
id_token_encryption_alg_values_supported := IdTokenEncryptionAlgValuesSupported,
id_token_encryption_enc_values_supported := IdTokenEncryptionEncValuesSupported,
userinfo_signing_alg_values_supported := UserinfoSigningAlgValuesSupported,
userinfo_encryption_alg_values_supported := UserinfoEncryptionAlgValuesSupported,
userinfo_encryption_enc_values_supported := UserinfoEncryptionEncValuesSupported,
request_object_signing_alg_values_supported :=
RequestObjectSigningAlgValuesSupported,
request_object_encryption_alg_values_supported :=
RequestObjectEncryptionAlgValuesSupported,
request_object_encryption_enc_values_supported :=
RequestObjectEncryptionEncValuesSupported,
token_endpoint_auth_methods_supported := TokenEndpointAuthMethodsSupported,
token_endpoint_auth_signing_alg_values_supported :=
TokenEndpointAuthSigningAlgValuesSupported,
display_values_supported := DisplayValuesSupported,
claim_types_supported := ClaimTypesSupported,
claims_supported := ClaimsSupported,
service_documentation := ServiceDocumentation,
claims_locales_supported := ClaimsLocalesSupported,
ui_locales_supported := UiLocalesSupported,
claims_parameter_supported := ClaimsParameterSupported,
request_parameter_supported := RequestParameterSupported,
request_uri_parameter_supported := RequestUriParameterSupported,
require_request_uri_registration := RequireRequestUriRegistration,
op_policy_uri := OpPolicyUri,
op_tos_uri := OpTosUri,
revocation_endpoint := RevocationEndpoint,
revocation_endpoint_auth_methods_supported :=
RevocationEndpointAuthMethodsSupported,
revocation_endpoint_auth_signing_alg_values_supported :=
RevocationEndpointAuthSigningAlgValuesSupported,
introspection_endpoint := IntrospectionEndpoint,
introspection_endpoint_auth_methods_supported :=
IntrospectionEndpointAuthMethodsSupported,
introspection_endpoint_auth_signing_alg_values_supported :=
IntrospectionEndpointAuthSigningAlgValuesSupported,
code_challenge_methods_supported := CodeChallengeMethodsSupported,
end_session_endpoint := EndSessionEndpoint,
require_pushed_authorization_requests := RequirePushedAuthorizationRequests,
pushed_authorization_request_endpoint := PushedAuthorizationRequestEndpoint,
authorization_signing_alg_values_supported :=
AuthorizationSigningAlgValuesSupported,
authorization_encryption_alg_values_supported :=
AuthorizationEncryptionAlgValuesSupported,
authorization_encryption_enc_values_supported :=
AuthorizationEncryptionEncValuesSupported,
authorization_response_iss_parameter_supported :=
AuthorizationResponseIssParameterSupported,
dpop_signing_alg_values_supported := DpopSigningAlgValuesSupported,
require_signed_request_object := RequireSignedRequestObject,
mtls_endpoint_aliases := MtlsEndpointAliases,
tls_client_certificate_bound_access_tokens := TlsClientCertificateBoundAccessTokens
},
ExtraFields
}} ?=
oidcc_decode_util:extract(
Configuration,
[
{required, issuer, fun oidcc_decode_util:parse_setting_uri/2},
{required, authorization_endpoint, fun oidcc_decode_util:parse_setting_uri/2},
{optional, token_endpoint, undefined,
fun oidcc_decode_util:parse_setting_uri/2},
{optional, userinfo_endpoint, undefined,
case AllowUnsafeHttp of
true -> fun oidcc_decode_util:parse_setting_uri/2;
false -> fun oidcc_decode_util:parse_setting_uri_https/2
end},
{required, jwks_uri, fun oidcc_decode_util:parse_setting_uri/2},
{optional, registration_endpoint, undefined,
fun oidcc_decode_util:parse_setting_uri/2},
{required, scopes_supported, fun parse_scopes_supported/2},
{required, response_types_supported,
fun oidcc_decode_util:parse_setting_binary_list/2},
{optional, response_modes_supported, [<<"query">>, <<"fragment">>],
fun oidcc_decode_util:parse_setting_binary_list/2},
{optional, grant_types_supported, [<<"authorization_code">>, <<"implicit">>],
fun oidcc_decode_util:parse_setting_binary_list/2},
{optional, acr_values_supported, undefined,
fun oidcc_decode_util:parse_setting_binary_list/2},
{required, subject_types_supported, fun parse_subject_types_supported/2},
{required, id_token_signing_alg_values_supported,
fun oidcc_decode_util:parse_setting_binary_list/2},
{optional, id_token_encryption_alg_values_supported, undefined,
fun oidcc_decode_util:parse_setting_binary_list/2},
{optional, id_token_encryption_enc_values_supported, undefined,
fun oidcc_decode_util:parse_setting_binary_list/2},
{optional, userinfo_signing_alg_values_supported, undefined,
fun oidcc_decode_util:parse_setting_binary_list/2},
{optional, userinfo_encryption_alg_values_supported, undefined,
fun oidcc_decode_util:parse_setting_binary_list/2},
{optional, userinfo_encryption_enc_values_supported, undefined,
fun oidcc_decode_util:parse_setting_binary_list/2},
{optional, request_object_signing_alg_values_supported, undefined,
fun oidcc_decode_util:parse_setting_binary_list/2},
{optional, request_object_encryption_alg_values_supported, undefined,
fun oidcc_decode_util:parse_setting_binary_list/2},
{optional, request_object_encryption_enc_values_supported, undefined,
fun oidcc_decode_util:parse_setting_binary_list/2},
{optional, token_endpoint_auth_methods_supported, [<<"client_secret_basic">>],
fun oidcc_decode_util:parse_setting_binary_list/2},
{optional, token_endpoint_auth_signing_alg_values_supported, undefined,
fun parse_token_signing_alg_values_no_none/2},
{optional, display_values_supported, undefined,
fun oidcc_decode_util:parse_setting_binary_list/2},
{optional, claim_types_supported, [normal], fun parse_claim_types_supported/2},
{optional, claims_supported, undefined,
fun oidcc_decode_util:parse_setting_binary_list/2},
{optional, service_documentation, undefined,
fun oidcc_decode_util:parse_setting_uri/2},
{optional, claims_locales_supported, undefined,
fun oidcc_decode_util:parse_setting_binary_list/2},
{optional, ui_locales_supported, undefined,
fun oidcc_decode_util:parse_setting_binary_list/2},
{optional, claims_parameter_supported, false,
fun oidcc_decode_util:parse_setting_boolean/2},
{optional, request_parameter_supported, false,
fun oidcc_decode_util:parse_setting_boolean/2},
{optional, request_uri_parameter_supported, true,
fun oidcc_decode_util:parse_setting_boolean/2},
{optional, require_request_uri_registration, false,
fun oidcc_decode_util:parse_setting_boolean/2},
{optional, op_policy_uri, undefined, fun oidcc_decode_util:parse_setting_uri/2},
{optional, op_tos_uri, undefined, fun oidcc_decode_util:parse_setting_uri/2},
{optional, revocation_endpoint, undefined,
fun oidcc_decode_util:parse_setting_uri/2},
{optional, revocation_endpoint_auth_methods_supported,
[<<"client_secret_basic">>],
fun oidcc_decode_util:parse_setting_binary_list/2},
{optional, revocation_endpoint_auth_signing_alg_values_supported, undefined,
fun parse_token_signing_alg_values_no_none/2},
{optional, introspection_endpoint, undefined,
fun oidcc_decode_util:parse_setting_uri/2},
{optional, introspection_endpoint_auth_methods_supported,
[<<"client_secret_basic">>],
fun oidcc_decode_util:parse_setting_binary_list/2},
{optional, introspection_endpoint_auth_signing_alg_values_supported, undefined,
fun parse_token_signing_alg_values_no_none/2},
{optional, code_challenge_methods_supported, undefined,
fun oidcc_decode_util:parse_setting_binary_list/2},
{optional, end_session_endpoint, undefined,
case AllowUnsafeHttp of
true -> fun oidcc_decode_util:parse_setting_uri/2;
false -> fun oidcc_decode_util:parse_setting_uri_https/2
end},
{optional, require_pushed_authorization_requests, false,
fun oidcc_decode_util:parse_setting_boolean/2},
{optional, pushed_authorization_request_endpoint, undefined,
case AllowUnsafeHttp of
true -> fun oidcc_decode_util:parse_setting_uri/2;
false -> fun oidcc_decode_util:parse_setting_uri_https/2
end},
{optional, authorization_signing_alg_values_supported, undefined,
fun parse_token_signing_alg_values_no_none/2},
{optional, authorization_encryption_alg_values_supported, undefined,
fun oidcc_decode_util:parse_setting_binary_list/2},
{optional, authorization_encryption_enc_values_supported, undefined,
fun oidcc_decode_util:parse_setting_binary_list/2},
{optional, authorization_response_iss_parameter_supported, false,
fun oidcc_decode_util:parse_setting_boolean/2},
{optional, dpop_signing_alg_values_supported, undefined,
fun parse_token_signing_alg_values_no_none/2},
{optional, require_signed_request_object, false,
fun oidcc_decode_util:parse_setting_boolean/2},
{optional, mtls_endpoint_aliases, #{},
case AllowUnsafeHttp of
true -> fun oidcc_decode_util:parse_setting_uri_map/2;
false -> fun oidcc_decode_util:parse_setting_uri_https_map/2
end},
{optional, tls_client_certificate_bound_access_tokens, false,
fun oidcc_decode_util:parse_setting_boolean/2}
],
#{}
),
{ok, #oidcc_provider_configuration{
issuer = Issuer,
issuer_regex = IssuerRegex,
authorization_endpoint = AuthorizationEndpoint,
token_endpoint = TokenEndpoint,
userinfo_endpoint = UserinfoEndpoint,
jwks_uri = JwksUri,
registration_endpoint = RegistrationEndpoint,
scopes_supported = ScopesSupported,
response_types_supported = ResponseTypesSupported,
response_modes_supported = ResponseModesSupported,
grant_types_supported = GrantTypesSupported,
acr_values_supported = AcrValuesSupported,
subject_types_supported = SubjectTypesSupported,
id_token_signing_alg_values_supported =
IdTokenSigningAlgValuesSupported,
id_token_encryption_alg_values_supported =
IdTokenEncryptionAlgValuesSupported,
id_token_encryption_enc_values_supported =
IdTokenEncryptionEncValuesSupported,
userinfo_signing_alg_values_supported =
UserinfoSigningAlgValuesSupported,
userinfo_encryption_alg_values_supported =
UserinfoEncryptionAlgValuesSupported,
userinfo_encryption_enc_values_supported =
UserinfoEncryptionEncValuesSupported,
request_object_signing_alg_values_supported =
RequestObjectSigningAlgValuesSupported,
request_object_encryption_alg_values_supported =
RequestObjectEncryptionAlgValuesSupported,
request_object_encryption_enc_values_supported =
RequestObjectEncryptionEncValuesSupported,
token_endpoint_auth_methods_supported =
TokenEndpointAuthMethodsSupported,
token_endpoint_auth_signing_alg_values_supported =
TokenEndpointAuthSigningAlgValuesSupported,
display_values_supported = DisplayValuesSupported,
claim_types_supported = ClaimTypesSupported,
claims_supported = ClaimsSupported,
service_documentation = ServiceDocumentation,
claims_locales_supported = ClaimsLocalesSupported,
ui_locales_supported = UiLocalesSupported,
claims_parameter_supported = ClaimsParameterSupported,
request_parameter_supported = RequestParameterSupported,
request_uri_parameter_supported =
RequestUriParameterSupported,
require_request_uri_registration =
RequireRequestUriRegistration,
op_policy_uri = OpPolicyUri,
op_tos_uri = OpTosUri,
revocation_endpoint = RevocationEndpoint,
revocation_endpoint_auth_methods_supported =
RevocationEndpointAuthMethodsSupported,
revocation_endpoint_auth_signing_alg_values_supported =
RevocationEndpointAuthSigningAlgValuesSupported,
introspection_endpoint = IntrospectionEndpoint,
introspection_endpoint_auth_methods_supported =
IntrospectionEndpointAuthMethodsSupported,
introspection_endpoint_auth_signing_alg_values_supported =
IntrospectionEndpointAuthSigningAlgValuesSupported,
code_challenge_methods_supported =
CodeChallengeMethodsSupported,
end_session_endpoint = EndSessionEndpoint,
require_pushed_authorization_requests = RequirePushedAuthorizationRequests,
pushed_authorization_request_endpoint = PushedAuthorizationRequestEndpoint,
authorization_signing_alg_values_supported = AuthorizationSigningAlgValuesSupported,
authorization_encryption_alg_values_supported =
AuthorizationEncryptionAlgValuesSupported,
authorization_encryption_enc_values_supported =
AuthorizationEncryptionEncValuesSupported,
authorization_response_iss_parameter_supported =
AuthorizationResponseIssParameterSupported,
dpop_signing_alg_values_supported = DpopSigningAlgValuesSupported,
require_signed_request_object = RequireSignedRequestObject,
mtls_endpoint_aliases = MtlsEndpointAliases,
tls_client_certificate_bound_access_tokens = TlsClientCertificateBoundAccessTokens,
extra_fields = ExtraFields
}}
end.
?DOC("See `decode_configuration/2`.").
?DOC(#{since => <<"3.0.0">>}).
-spec decode_configuration(Configuration) -> {ok, t()} | {error, error()} when
Configuration :: map().
decode_configuration(Configuration) -> decode_configuration(Configuration, #{}).
-spec parse_scopes_supported(Setting :: term(), Field :: atom()) ->
{ok, [binary()]} | {error, error()}.
parse_scopes_supported(Setting, Field) ->
case oidcc_decode_util:parse_setting_binary_list(Setting, Field) of
{ok, Scopes} ->
case lists:member(<<"openid">>, Scopes) of
true ->
{ok, Scopes};
false ->
{error, {invalid_config_property, {scopes_including_openid, Field}}}
end;
{error, Reason} ->
{error, Reason}
end.
-spec parse_subject_types_supported(Setting :: term(), Field :: atom()) ->
{ok, [binary()]} | {error, error()}.
parse_subject_types_supported(Setting, Field) ->
oidcc_decode_util:parse_setting_list_enum(
Setting,
Field,
fun
(<<"pairwise">>) ->
{ok, pairwise};
(<<"public">>) ->
{ok, public};
(_SubjectType) ->
error
end
).
-spec parse_token_signing_alg_values_no_none(Setting :: term(), Field :: atom()) ->
{ok, [binary()]} | {error, error()}.
parse_token_signing_alg_values_no_none(Setting, Field) ->
case oidcc_decode_util:parse_setting_binary_list(Setting, Field) of
{ok, SigningAlgValues} ->
case
lists:any(
fun
(<<"none">>) ->
true;
(_) ->
false
end,
SigningAlgValues
)
of
false ->
{ok, SigningAlgValues};
true ->
{error, {invalid_config_property, {alg_no_none, Field}}}
end;
{error, Reason} ->
{error, Reason}
end.
-spec parse_claim_types_supported(Setting :: term(), Field :: atom()) ->
{ok, [binary()]} | {error, error()}.
parse_claim_types_supported(Setting, Field) ->
oidcc_decode_util:parse_setting_list_enum(
Setting,
Field,
fun
(<<"normal">>) ->
{ok, normal};
(<<"aggregated">>) ->
{ok, aggregated};
(<<"distributed">>) ->
{ok, distributed};
(_ClaimType) ->
error
end
).
-spec url_join(RefURI :: uri_string:uri_string(), BaseURI :: uri_string:uri_string()) ->
uri_string:uri_string().
url_join(RefURI, BaseURI) ->
BaseURIBinary = iolist_to_binary(BaseURI),
case binary_part(BaseURIBinary, byte_size(BaseURIBinary) - 1, 1) of
<<"/">> -> uri_string:resolve(RefURI, BaseURI);
_ -> uri_string:resolve(RefURI, [BaseURI, "/"])
end.