Packages
oidcc
3.0.1
3.7.2
3.7.1
3.7.0
3.6.0
3.5.2
3.5.1
3.5.0
retired
3.4.0
3.3.0
3.2.6
3.2.5
3.2.4
3.2.3
3.2.2
3.2.1
3.2.0
3.2.0-beta.3
3.2.0-beta.2
retired
3.2.0-beta.1
retired
3.1.2
3.1.2-beta.1
retired
3.1.1
retired
3.1.0
retired
3.1.0-beta.2
retired
3.1.0-beta.1
retired
3.0.2
3.0.1
retired
3.0.0
retired
3.0.0-rc.6
retired
3.0.0-rc.5
retired
3.0.0-rc.4
retired
3.0.0-rc.3
retired
3.0.0-rc.2
retired
3.0.0-rc.1
retired
3.0.0-alpha.5
retired
3.0.0-alpha.4
retired
3.0.0-alpha.3
retired
3.0.0-alpha.2
retired
3.0.0-alpha.1
retired
2.0.0-alpha.2
retired
2.0.0-alpha.1
retired
1.8.1
retired
1.8.0
retired
1.7.0
retired
1.6.0
retired
1.5.1
retired
1.5.0
retired
1.4.0
retired
1.3.0
retired
1.2.1
retired
1.2.0
retired
1.1.0
retired
1.0.1
retired
1.0.0
retired
OpenID Connect client library for the BEAM.
Retired package: Security issue - DoS Vulnerability, see https://github.com/erlef/oidcc/security/advisories/GHSA-mj35-2rgf-cv8p
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
src/oidcc_client_registration.erl
%%%-------------------------------------------------------------------
%% @doc Dynamic Client Registration Utilities
%%
%% See [https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata]
%%
%% <h2>Records</h2>
%%
%% To use the record, import the definition:
%%
%% ```
%% -include_lib(["oidcc/include/oidcc_client_registration.hrl"]).
%% '''
%%
%% <h2>Telemetry</h2>
%%
%% See {@link 'Elixir.Oidcc.ClientRegistration'}
%% @end
%% @since 3.0.0
%%%-------------------------------------------------------------------
-module(oidcc_client_registration).
-feature(maybe_expr, enable).
-include("oidcc_client_registration.hrl").
-include("oidcc_provider_configuration.hrl").
-export([register/3]).
-export_type([error/0]).
-export_type([opts/0]).
-export_type([response/0]).
-export_type([t/0]).
-type opts() :: #{
initial_access_token => binary() | undefined,
request_opts => oidcc_http_util:request_opts()
}.
% Configure configuration loading / parsing
%
% <h2>Parameters</h2>
%
% <ul>
% <li>`initial_access_token' - Access Token for registration</li>
% <li>`request_opts' - config for HTTP request</li>
% </ul>
-type t() ::
#oidcc_client_registration{
%% OpenID Connect Dynamic Client Registration 1.0
redirect_uris :: [uri_string:uri_string()],
%% OpenID Connect Dynamic Client Registration 1.0
response_types :: [binary()] | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
grant_types :: [binary()] | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
application_type :: web | native,
%% OpenID Connect Dynamic Client Registration 1.0
contacts :: [binary()] | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
client_name :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
logo_uri :: uri_string:uri_string() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
client_uri :: uri_string:uri_string() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
policy_uri :: uri_string:uri_string() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
tos_uri :: uri_string:uri_string() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
jwks :: jose_jwk:key() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
jwks_uri :: uri_string:uri_string() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
sector_identifier_uri :: uri_string:uri_string() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
subject_type :: pairwise | public | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
id_token_signed_response_alg :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
id_token_encrypted_response_alg :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
id_token_encrypted_response_enc :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
userinfo_signed_response_alg :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
userinfo_encrypted_response_alg :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
userinfo_encrypted_response_enc :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
request_object_signing_alg :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
request_object_encryption_alg :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
request_object_encryption_enc :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
token_endpoint_auth_method :: erlang:binary(),
%% OpenID Connect Dynamic Client Registration 1.0
token_endpoint_auth_signing_alg :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
default_max_age :: pos_integer() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
require_auth_time :: boolean(),
%% OpenID Connect Dynamic Client Registration 1.0
default_acr_values :: [binary()] | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
initiate_login_uri :: uri_string:uri_string() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
request_uris :: [uri_string:uri_string()] | undefined,
%% OpenID Connect RP-Initiated Logout 1.0
post_logout_redirect_uris :: [uri_string:uri_string()] | undefined,
%% Unknown Fields
extra_fields :: #{binary() => term()}
}.
%% Record containing Client Registration Metadata
%%
%% See [https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata] and
%% [https://openid.net/specs/openid-connect-rpinitiated-1_0.html#ClientMetadata]
%%
%% All unrecognized fields are stored in `extra_fields'.
-type response() ::
#oidcc_client_registration_response{
client_id :: erlang:binary(),
client_secret :: binary() | undefined,
registration_access_token :: binary() | undefined,
registration_client_uri :: uri_string:uri_string() | undefined,
client_id_issued_at :: pos_integer() | undefined,
client_secret_expires_at :: pos_integer() | undefined,
%% Unknown Fields
extra_fields :: #{binary() => term()}
}.
%% Record containing Client Registration Response
%%
%% See [https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationResponse]
%%
%% All unrecognized fields are stored in `extra_fields'.
-type error() ::
registration_not_supported
| invalid_content_type
| oidcc_decode_util:error()
| oidcc_http_util:error().
-telemetry_event(#{
event => [oidcc, register_client, start],
description => <<"Emitted at the start of registering the client">>,
measurements => <<"#{system_time => non_neg_integer()}">>,
metadata => <<"#{issuer => uri_string:uri_string()}">>
}).
-telemetry_event(#{
event => [oidcc, register_client, stop],
description => <<"Emitted at the end of registering the client">>,
measurements => <<"#{duration => integer(), monotonic_time => integer()}">>,
metadata => <<"#{issuer => uri_string:uri_string()}">>
}).
-telemetry_event(#{
event => [oidcc, register_client, exception],
description => <<"Emitted at the end of registering the client">>,
measurements => <<"#{duration => integer(), monotonic_time => integer()}">>,
metadata => <<"#{issuer => uri_string:uri_string()}">>
}).
%% @doc Register Client
%%
%% <h2>Examples</h2>
%%
%% ```
%% {ok, ProviderConfiguration} =
%% oidcc_provider_configuration:load_configuration("https://your.issuer"),
%%
%% {ok, #oidcc_client_registration_response{
%% client_id = ClientId,
%% client_secret = ClientSecret
%% }} =
%% oidcc_client_registration:register(
%% ProviderConfiguration,
%% #oidcc_client_registration{
%% redirect_uris = ["https://your.application.com/oidcc/callback"]
%% },
%% #{initial_access_token => <<"optional token you got from the provider">>}
%% ).
%% '''
%% @end
%% @since 3.0.0
-spec register(ProviderConfiguration, Registration, Opts) ->
{ok, response()} | {error, error()}
when
ProviderConfiguration :: oidcc_provider_configuration:t(),
Registration :: t(),
Opts :: opts().
register(#oidcc_provider_configuration{registration_endpoint = undefined}, _Registration, _Opts) ->
{error, registration_not_supported};
register(
#oidcc_provider_configuration{issuer = Issuer, registration_endpoint = RegistrationEndpoint},
Registration,
Opts
) ->
RegistrationBody = encode(Registration),
TelemetryOpts = #{topic => [oidcc, register_client], extra_meta => #{issuer => Issuer}},
RequestOpts = maps:get(request_opts, Opts, #{}),
Headers =
case maps:get(initial_access_token, Opts, undefined) of
undefined -> [];
Token -> [{"authorization", ["Bearer ", Token]}]
end,
Request = {RegistrationEndpoint, Headers, "application/json", RegistrationBody},
maybe
{ok, {{json, ResponseMap}, _Headers}} ?=
oidcc_http_util:request(post, Request, TelemetryOpts, RequestOpts),
{ok, #oidcc_client_registration_response{} = Response} ?= decode_response(ResponseMap),
{ok, Response}
else
{error, Reason} -> {error, Reason};
{ok, {{_Format, _Body}, _}} -> {error, invalid_content_type}
end.
-spec decode_response(Response) -> {ok, response()} | {error, error()} when Response :: map().
decode_response(Response) ->
case
oidcc_decode_util:extract(
Response,
[
{required, client_id, fun oidcc_decode_util:parse_setting_binary/2},
{optional, client_secret, undefined, fun oidcc_decode_util:parse_setting_binary/2},
{optional, registration_access_token, undefined,
fun oidcc_decode_util:parse_setting_binary/2},
{optional, registration_client_uri, undefined,
fun oidcc_decode_util:parse_setting_uri_https/2},
{optional, client_id_issued_at, undefined,
fun oidcc_decode_util:parse_setting_number/2},
{optional, client_secret_expires_at, undefined,
fun oidcc_decode_util:parse_setting_number/2}
],
#{}
)
of
{ok, {
#{
client_id := ClientId,
client_secret := ClientSecret,
registration_access_token := RegistrationAccessToken,
registration_client_uri := RegistrationClientUri,
client_id_issued_at := ClientIdIssuedAt,
client_secret_expires_at := ClientSecretExpiresAt
},
ExtraFields
}} ->
{ok, #oidcc_client_registration_response{
client_id = ClientId,
client_secret = ClientSecret,
registration_access_token = RegistrationAccessToken,
registration_client_uri = RegistrationClientUri,
client_id_issued_at = ClientIdIssuedAt,
client_secret_expires_at = ClientSecretExpiresAt,
extra_fields = ExtraFields
}};
{error, Reason} ->
{error, Reason}
end.
-spec encode(Metadata) -> binary() when Metadata :: t().
encode(#oidcc_client_registration{
redirect_uris = RedirectUris,
response_types = ResponseTypes,
grant_types = GrantTypes,
application_type = ApplicationType,
contacts = Contacts,
client_name = ClientName,
logo_uri = LogoUri,
client_uri = ClientUri,
policy_uri = PolicyUri,
tos_uri = TosUri,
jwks = Jwks,
sector_identifier_uri = SectorIdentifierUri,
subject_type = SubjectType,
id_token_signed_response_alg = IdTokenSignedResponseAlg,
id_token_encrypted_response_alg = IdTokenencryptedResponseAlg,
id_token_encrypted_response_enc = IdTokenEncryptedResponseEnc,
userinfo_signed_response_alg = UserinfoSignedResponseAlg,
userinfo_encrypted_response_alg = UserinfoEncryptedResponseAlg,
userinfo_encrypted_response_enc = UserinfoEncryptedResponseEnc,
request_object_signing_alg = RequestObjectSigningAlg,
request_object_encryption_alg = RequestObjectEncryptionAlg,
request_object_encryption_enc = RequestObjectEncryptionEnc,
token_endpoint_auth_method = TokenEndpointAuthMethod,
token_endpoint_auth_signing_alg = TokenEndpointAuthSigningAlg,
default_max_age = DefaultMaxAge,
require_auth_time = RequireAuthTime,
default_acr_values = DefaultAcrValues,
initiate_login_uri = InitiateLoginUri,
request_uris = RequestUris,
post_logout_redirect_uris = PostLogoutRedirectUris,
extra_fields = ExtraFields
}) ->
Map0 = #{
redirect_uris => RedirectUris,
response_types => ResponseTypes,
grant_types => GrantTypes,
application_type => ApplicationType,
contacts => Contacts,
client_name => ClientName,
logo_uri => LogoUri,
client_uri => ClientUri,
policy_uri => PolicyUri,
tos_uri => TosUri,
jwks =>
case Jwks of
undefined ->
undefined;
_ ->
{_KeyType, KeyMap} = jose_jwk:to_map(Jwks),
KeyMap
end,
sector_identifier_uri => SectorIdentifierUri,
subject_type => SubjectType,
id_token_signed_response_alg => IdTokenSignedResponseAlg,
id_token_encrypted_response_alg => IdTokenencryptedResponseAlg,
id_token_encrypted_response_enc => IdTokenEncryptedResponseEnc,
userinfo_signed_response_alg => UserinfoSignedResponseAlg,
userinfo_encrypted_response_alg => UserinfoEncryptedResponseAlg,
userinfo_encrypted_response_enc => UserinfoEncryptedResponseEnc,
request_object_signing_alg => RequestObjectSigningAlg,
request_object_encryption_alg => RequestObjectEncryptionAlg,
request_object_encryption_enc => RequestObjectEncryptionEnc,
token_endpoint_auth_method => TokenEndpointAuthMethod,
token_endpoint_auth_signing_alg => TokenEndpointAuthSigningAlg,
default_max_age => DefaultMaxAge,
require_auth_time => RequireAuthTime,
default_acr_values => DefaultAcrValues,
initiate_login_uri => InitiateLoginUri,
request_uris => RequestUris,
post_logout_redirect_uris => PostLogoutRedirectUris
},
Map1 = maps:merge(Map0, ExtraFields),
Map = maps:filter(
fun
(_Key, undefined) -> false;
(_Key, _Value) -> true
end,
Map1
),
jose:encode(Map).