Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: profile for mTLS sender-constrained tokens #336

Merged
merged 1 commit into from
Jan 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 64 additions & 44 deletions src/oidcc_profile.erl
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
-export_type([opts_no_profiles/0]).
-export_type([error/0]).

-type profile() :: fapi2_security_profile | fapi2_message_signing | fapi2_connectid_au.
-type profile() ::
mtls_constrain | fapi2_security_profile | fapi2_message_signing | fapi2_connectid_au.
-type opts() :: #{
profiles => [profile()],
require_pkce => boolean(),
Expand Down Expand Up @@ -98,55 +99,74 @@ apply_profiles(
) ->
%% FAPI2 ConnectID profile
maybe
%% Require everything from FAPI2 Message Signing
%% Require everything from FAPI2 Message Signing, and use mTLS
%% sender-constrained tokens
{ok, ClientContext1, Opts1} ?=
apply_profiles(ClientContext0, Opts0#{
profiles => [fapi2_message_signing | RestProfiles]
profiles => [fapi2_message_signing, mtls_constrain | RestProfiles]
}),
%% Require `purpose' field
Opts2 = Opts1#{require_purpose => true},
%% If a PAR endpoint is present in the mTLS aliases, use that as the default
#oidcc_client_context{provider_configuration = Configuration1} = ClientContext1,
Configuration2 =
case Configuration1#oidcc_provider_configuration.mtls_endpoint_aliases of
#{
<<"pushed_authorization_request_endpoint">> := MtlsParEndpoint
} ->
Configuration1#oidcc_provider_configuration{
pushed_authorization_request_endpoint = MtlsParEndpoint
};
_ ->
Configuration1
end,
%% If the token endpoint is present in the mTLS aliases, use that as the default
Configuration3 =
case Configuration2#oidcc_provider_configuration.mtls_endpoint_aliases of
#{
<<"token_endpoint">> := MtlsTokenEndpoint
} ->
Configuration2#oidcc_provider_configuration{
token_endpoint = MtlsTokenEndpoint
};
_ ->
Configuration2
end,
%% If the userinfo endpoint is present in the mTLS aliases, use that as the default
Configuration4 =
case Configuration3#oidcc_provider_configuration.mtls_endpoint_aliases of
#{
<<"userinfo_endpoint">> := MtlsUserinfoEndpoint
} ->
Configuration3#oidcc_provider_configuration{
userinfo_endpoint = MtlsUserinfoEndpoint
};
_ ->
Configuration3
end,
ClientContext2 = ClientContext1#oidcc_client_context{
provider_configuration = Configuration4
},
{ok, ClientContext2, Opts2}
{ok, ClientContext1, Opts2}
end;
apply_profiles(
#oidcc_client_context{} = ClientContext0,
#{profiles := [mtls_constrain | RestProfiles]} = Opts0
) ->
%% If a PAR endpoint is present in the mTLS aliases, use that as the default
#oidcc_client_context{provider_configuration = Configuration0} = ClientContext0,
Configuration1 =
case Configuration0#oidcc_provider_configuration.mtls_endpoint_aliases of
#{
<<"pushed_authorization_request_endpoint">> := MtlsParEndpoint
} ->
Configuration0#oidcc_provider_configuration{
pushed_authorization_request_endpoint = MtlsParEndpoint
};
_ ->
Configuration0
end,
%% If the token endpoint is present in the mTLS aliases, use that as the default
Configuration2 =
case Configuration1#oidcc_provider_configuration.mtls_endpoint_aliases of
#{
<<"token_endpoint">> := MtlsTokenEndpoint
} ->
Configuration1#oidcc_provider_configuration{
token_endpoint = MtlsTokenEndpoint
};
_ ->
Configuration1
end,
%% If the userinfo endpoint is present in the mTLS aliases, use that as the default
Configuration3 =
case Configuration2#oidcc_provider_configuration.mtls_endpoint_aliases of
#{
<<"userinfo_endpoint">> := MtlsUserinfoEndpoint
} ->
Configuration2#oidcc_provider_configuration{
userinfo_endpoint = MtlsUserinfoEndpoint
};
_ ->
Configuration2
end,
%% If the introspection endpoint is present in the mTLS aliases, use that as the default
Configuration4 =
case Configuration3#oidcc_provider_configuration.mtls_endpoint_aliases of
#{
<<"introspection_endpoint">> := MtlsIntrospectionEndpoint
} ->
Configuration3#oidcc_provider_configuration{
introspection_endpoint = MtlsIntrospectionEndpoint
};
_ ->
Configuration3
end,
ClientContext1 = ClientContext0#oidcc_client_context{
provider_configuration = Configuration4
},
Opts1 = Opts0#{profiles := RestProfiles},
apply_profiles(ClientContext1, Opts1);
apply_profiles(#oidcc_client_context{}, #{profiles := [UnknownProfile | _]}) ->
{error, {unknown_profile, UnknownProfile}};
apply_profiles(#oidcc_client_context{} = ClientContext, #{profiles := []} = Opts0) ->
Expand Down
32 changes: 32 additions & 0 deletions test/oidcc_client_context_test.erl
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,38 @@ apply_profiles_fapi2_message_signing_test() ->

ok.

apply_profiles_mtls_constrain_test() ->
ClientContext0 = client_context_fixture(),
Opts0 = #{
profiles => [mtls_constrain]
},

ProfileResult = oidcc_client_context:apply_profiles(ClientContext0, Opts0),

?assertMatch(
{ok, #oidcc_client_context{}, #{}},
ProfileResult
),

{ok, ClientContext, Opts} = ProfileResult,

?assertMatch(
#oidcc_client_context{
provider_configuration = #oidcc_provider_configuration{
token_endpoint = <<"https://my.provider/tls/token">>,
userinfo_endpoint = <<"https://my.provider/tls/userinfo">>
}
},
ClientContext
),

?assertEqual(
#{},
Opts
),

ok.

apply_profiles_fapi2_connectid_au_test() ->
ClientContext0 = client_context_fixture(),
Opts0 = #{
Expand Down