-module(oidcc_client_context). -feature(maybe_expr, enable). -include("internal/doc.hrl"). ?MODULEDOC(""" Client Configuration for authorization, token exchange, and userinfo. For most projects, it makes sense to use `m:oidcc_provider_configuration_worker` and the high-level interface of `oidcc`. In that case, direct usage of this module is not needed. To use the record, import the definition: ```erlang -include_lib(["oidcc/include/oidcc_client_context.hrl"]). ``` """). ?MODULEDOC(#{since => <<"3.0.0">>}). -include("oidcc_client_context.hrl"). -include("oidcc_provider_configuration.hrl"). -include_lib("jose/include/jose_jwk.hrl"). -export_type([authenticated_opts/0]). -export_type([authenticated_t/0]). -export_type([error/0]). -export_type([opts/0]). -export_type([t/0]). -export_type([unauthenticated_opts/0]). -export_type([unauthenticated_t/0]). -export([from_configuration_worker/3]). -export([from_configuration_worker/4]). -export([from_manual/4]). -export([from_manual/5]). -export([apply_profiles/2]). -type t() :: authenticated_t() | unauthenticated_t(). ?DOC(#{since => <<"3.0.0">>}). -type authenticated_t() :: #oidcc_client_context{ provider_configuration :: oidcc_provider_configuration:t(), jwks :: jose_jwk:key(), client_id :: binary(), client_secret :: binary(), client_jwks :: jose_jwk:key() | none }. ?DOC(#{since => <<"3.0.0">>}). -type unauthenticated_t() :: #oidcc_client_context{ provider_configuration :: oidcc_provider_configuration:t(), jwks :: jose_jwk:key(), client_id :: binary(), client_secret :: unauthenticated, client_jwks :: none }. ?DOC(#{since => <<"3.0.0">>}). -type authenticated_opts() :: #{ client_jwks => jose_jwk:key() }. ?DOC(#{since => <<"3.0.0">>}). -type unauthenticated_opts() :: #{}. ?DOC(#{since => <<"3.0.0">>}). -type opts() :: authenticated_opts() | unauthenticated_opts(). ?DOC(#{since => <<"3.0.0">>}). -type error() :: provider_not_ready. ?DOC(""" Create Client Context from a `m:oidcc_provider_configuration_worker`. See `from_configuration_worker/4`. """). ?DOC(#{since => <<"3.0.0">>}). -spec from_configuration_worker (ProviderName, ClientId, ClientSecret) -> {ok, authenticated_t()} | {error, error()} when ProviderName :: gen_server:server_ref(), ClientId :: binary(), ClientSecret :: binary(); (ProviderName, ClientId, ClientSecret) -> {ok, unauthenticated_t()} | {error, error()} when ProviderName :: gen_server:server_ref(), ClientId :: binary(), ClientSecret :: unauthenticated. from_configuration_worker(ProviderName, ClientId, ClientSecret) -> from_configuration_worker(ProviderName, ClientId, ClientSecret, #{}). ?DOC(""" Create Client Context from a `m:oidcc_provider_configuration_worker`. ## Examples ```erlang {ok, Pid} = oidcc_provider_configuration_worker:start_link(#{ issuer => <<"https://login.salesforce.com">> }), {ok, #oidcc_client_context{}} = oidcc_client_context:from_configuration_worker(Pid, <<"client_id">>, <<"client_secret">>). ``` ```erlang {ok, Pid} = oidcc_provider_configuration_worker:start_link(#{ issuer => <<"https://login.salesforce.com">>, name => {local, salesforce_provider} }), {ok, #oidcc_client_context{}} = oidcc_client_context:from_configuration_worker( salesforce_provider, <<"client_id">>, <<"client_secret">>, #{client_jwks => jose_jwk:generate_key(16)} ). ``` """). ?DOC(#{since => <<"3.0.0">>}). -spec from_configuration_worker (ProviderName, ClientId, ClientSecret, Opts) -> {ok, authenticated_t()} | {error, error()} when ProviderName :: gen_server:server_ref(), ClientId :: binary(), ClientSecret :: binary(), Opts :: authenticated_opts(); (ProviderName, ClientId, ClientSecret, Opts) -> {ok, unauthenticated_t()} | {error, error()} when ProviderName :: gen_server:server_ref(), ClientId :: binary(), ClientSecret :: unauthenticated, Opts :: unauthenticated_opts(). from_configuration_worker(ProviderName, ClientId, ClientSecret, Opts) when is_pid(ProviderName) -> maybe #oidcc_provider_configuration{} = ProviderConfiguration ?= oidcc_provider_configuration_worker:get_provider_configuration(ProviderName), #jose_jwk{} = Jwks ?= oidcc_provider_configuration_worker:get_jwks(ProviderName), {ok, from_manual( ProviderConfiguration, Jwks, ClientId, ClientSecret, Opts )} else undefined -> {error, provider_not_ready} end; from_configuration_worker(ProviderName, ClientId, ClientSecret, Opts) -> case erlang:whereis(ProviderName) of undefined -> {error, provider_not_ready}; Pid -> from_configuration_worker(Pid, ClientId, ClientSecret, Opts) end. ?DOC(""" Create Client Context manually. See `from_manual/5`. """). ?DOC(#{since => <<"3.0.0">>}). -spec from_manual (Configuration, Jwks, ClientId, ClientSecret) -> authenticated_t() when Configuration :: oidcc_provider_configuration:t(), Jwks :: jose_jwk:key(), ClientId :: binary(), ClientSecret :: binary(); (Configuration, Jwks, ClientId, ClientSecret) -> unauthenticated_t() when Configuration :: oidcc_provider_configuration:t(), Jwks :: jose_jwk:key(), ClientId :: binary(), ClientSecret :: unauthenticated. from_manual(Configuration, Jwks, ClientId, ClientSecret) -> from_manual(Configuration, Jwks, ClientId, ClientSecret, #{}). ?DOC(""" Create Client Context manually. ## Examples ```erlang {ok, Configuration} = oidcc_provider_configuration:load_configuration(<<"https://login.salesforce.com">>, []), #oidcc_provider_configuration{jwks_uri = JwksUri} = Configuration, {ok, Jwks} = oidcc_provider_configuration:load_jwks(JwksUri, []). #oidcc_client_context{} = oidcc_client_context:from_manual( Metadata, Jwks, <<"client_id">>, <<"client_secret">>, #{client_jwks => jose_jwk:generate_key(16)} ). ``` """). ?DOC(#{since => <<"3.0.0">>}). -spec from_manual (Configuration, Jwks, ClientId, ClientSecret, Opts) -> authenticated_t() when Configuration :: oidcc_provider_configuration:t(), Jwks :: jose_jwk:key(), ClientId :: binary(), ClientSecret :: binary(), Opts :: authenticated_opts(); (Configuration, Jwks, ClientId, ClientSecret, Opts) -> unauthenticated_t() when Configuration :: oidcc_provider_configuration:t(), Jwks :: jose_jwk:key(), ClientId :: binary(), ClientSecret :: unauthenticated, Opts :: unauthenticated_opts(). from_manual( #oidcc_provider_configuration{} = Configuration, #jose_jwk{} = Jwks, ClientId, unauthenticated, _Opts ) when is_binary(ClientId) -> #oidcc_client_context{ provider_configuration = Configuration, jwks = Jwks, client_id = ClientId, client_secret = unauthenticated }; from_manual( #oidcc_provider_configuration{} = Configuration, #jose_jwk{} = Jwks, ClientId, ClientSecret, Opts ) when is_binary(ClientId), is_binary(ClientSecret) -> #oidcc_client_context{ provider_configuration = Configuration, jwks = Jwks, client_id = ClientId, client_secret = ClientSecret, client_jwks = maps:get(client_jwks, Opts, none) }. ?DOC(""" Apply OpenID Connect / OAuth2 Profiles to the context. Currently, the only supported profiles are: - `fapi2_security_profile` - https://openid.bitbucket.io/fapi/fapi-2_0-security-profile.html - `fapi2_message_signing` - https://openid.bitbucket.io/fapi/fapi-2_0-message-signing.html It returns an updated `t:t/0` record and a map of options to be merged into the `m:oidcc_authorization` and `m:oidcc_token` functions. ## Examples ```erlang ClientContext = #oidcc_client_context{} = oidcc_client_context:from_...(...), {#oidcc_client_context{} = ClientContext1, Opts} = oidcc_client_context:apply_profiles( ClientContext, #{ profiles => [fapi2_message_signing] }), {ok, Uri} = oidcc_authorization:create_redirect_uri( ClientContext1, maps:merge(Opts, #{...}) ). ``` """). ?DOC(#{since => <<"3.2.0">>}). -spec apply_profiles(ClientContext, oidcc_profile:opts()) -> {ok, ClientContext, oidcc_profile:opts_no_profiles()} | {error, oidcc_profile:error()} when ClientContext :: oidcc_client_context:t(). apply_profiles(ClientContext, Opts) -> oidcc_profile:apply_profiles(ClientContext, Opts).