Skip to content

Commit

Permalink
revert default introspect client_id behaviour, add option for issuer
Browse files Browse the repository at this point in the history
add client_self_only option with default of true to
require client_id match of self client_id and token client_id
when false, disables match and returns only introspect result.
  • Loading branch information
Dan Janowski committed Aug 7, 2024
1 parent c9f5a94 commit eb798ca
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 4 deletions.
29 changes: 25 additions & 4 deletions src/oidcc_token_introspection.erl
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
client_id :: binary(),
exp :: pos_integer(),
scope :: oidcc_scope:scopes(),
username :: binary()
username :: binary(),
iss :: binary()
}.
%% Introspection Result
%%
Expand All @@ -46,10 +47,11 @@
-type opts() :: #{
preferred_auth_methods => [oidcc_auth_util:auth_method(), ...],
request_opts => oidcc_http_util:request_opts(),
dpop_nonce => binary()
dpop_nonce => binary(),
client_self_only => boolean()
}.

-type error() :: introspection_not_supported | oidcc_http_util:error().
-type error() :: client_id_mismatch | introspection_not_supported | oidcc_http_util:error().

-telemetry_event(#{
event => [oidcc, load_configuration, start],
Expand Down Expand Up @@ -158,7 +160,10 @@ introspect(AccessToken, ClientContext, Opts) ->
uri_string:compose_query(Body)},
{ok, {{json, Token}, _Headers}} ?=
oidcc_http_util:request(post, Request, TelemetryOpts, RequestOpts),
extract_response(Token)
client_match(
extract_response(Token),
ClientContext,
maps:get(client_self_only, Opts, true))
else
{error, {use_dpop_nonce, NewDpopNonce, _}} when
DpopOpts =:= #{}
Expand All @@ -175,6 +180,22 @@ introspect(AccessToken, ClientContext, Opts) ->
end
end.

-spec client_match({ok, Token}, ClientContext, ClientSelfOnly) ->
{ok, t()}
| {error, error()}
when
Token :: t(),
ClientContext :: oidcc_client_context:t(),
ClientSelfOnly :: boolean().
client_match({ok,Token},_,false) ->
{ok, Token};
client_match({ok, #oidcc_token_introspection{client_id = ClientId} = Token},
#oidcc_client_context{client_id = ClientId},
true) ->
{ok, Token};
client_match(_,_,true) ->
{error, client_id_mismatch}.

-spec extract_response(TokenMap) ->
{ok, t()}
when
Expand Down
69 changes: 69 additions & 0 deletions test/oidcc_token_introspection_test.erl
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ introspect_test() ->
)
),

?assertMatch(
{ok, #oidcc_token_introspection{active = true}},
oidcc_token_introspection:introspect(
#oidcc_token{access = #oidcc_token_access{token = AccessToken}},
ClientContext,
#{client_self_only => true}
)
),

true = meck:validate(oidcc_http_util),

meck:unload(oidcc_http_util),
Expand Down Expand Up @@ -188,3 +197,63 @@ introspection_invalid_client_id_test() ->
meck:unload(oidcc_http_util),

ok.

introspection_issuer_client_id_test() ->
PrivDir = code:priv_dir(oidcc),

{ok, ConfigurationBinary} = file:read_file(PrivDir ++ "/test/fixtures/example-metadata.json"),
{ok,
#oidcc_provider_configuration{
introspection_endpoint = IntrospectionEndpoint,
issuer = Issuer
} =
Configuration} =
oidcc_provider_configuration:decode_configuration(jose:decode(ConfigurationBinary)),

Jwks = jose_jwk:from_pem_file(PrivDir ++ "/test/fixtures/jwk.pem"),

OtherClientId = <<"other_client_id">>,
MyClientId = <<"my_client_id">>,
ClientSecret = <<"client_secret">>,
AccessToken = <<"access_token">>,

ClientContext = oidcc_client_context:from_manual(
Configuration, Jwks, MyClientId, ClientSecret
),

ok = meck:new(oidcc_http_util, [passthrough]),
HttpFun =
fun(
post,
{ReqEndpoint, _Header, "application/x-www-form-urlencoded", _Body},
_TelemetryOpts,
_RequestOpts
) ->
IntrospectionEndpoint = ReqEndpoint,
{ok, {{json, #{<<"active">> => true, <<"client_id">> => OtherClientId, <<"iss">> => Issuer}}, []}}
end,
ok = meck:expect(oidcc_http_util, request, HttpFun),

?assertMatch(
{ok, #oidcc_token_introspection{active = true, client_id = OtherClientId, iss = Issuer}},
oidcc_token_introspection:introspect(
#oidcc_token{access = #oidcc_token_access{token = AccessToken}},
ClientContext,
#{client_self_only => false}
)
),

?assertMatch(
{error, client_id_mismatch},
oidcc_token_introspection:introspect(
AccessToken,
ClientContext,
#{client_self_only => true}
)
),

true = meck:validate(oidcc_http_util),

meck:unload(oidcc_http_util),

ok.

0 comments on commit eb798ca

Please sign in to comment.