Skip to content

Commit

Permalink
token introspect remove client id constraint (#363)
Browse files Browse the repository at this point in the history
* token introspect remove client id constraint

previously could only introspect a client_id's own token, introspect
needs to be able to introspect any token for an issuer.

* revert default introspect client_id behaviour, add option for issuer

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.

* fixup

---------

Co-authored-by: Dan Janowski <[email protected]>
Co-authored-by: Jonatan Männchen <[email protected]>
  • Loading branch information
3 people authored Aug 24, 2024
1 parent 78cda17 commit 6aebbdc
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 18 deletions.
48 changes: 30 additions & 18 deletions src/oidcc_token_introspection.erl
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,17 @@ See https://datatracker.ietf.org/doc/html/rfc7662#section-2.2.
client_id :: binary(),
exp :: pos_integer(),
scope :: oidcc_scope:scopes(),
username :: binary()
username :: binary(),
iss :: binary()
}.


?DOC(#{since => <<"3.0.0">>}).
-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()
}.

?DOC(#{since => <<"3.0.0">>}).
Expand Down Expand Up @@ -118,8 +120,7 @@ introspect(AccessToken, ClientContext, Opts) ->
provider_configuration = Configuration,
client_id = ClientId,
client_secret = ClientSecret
} =
ClientContext,
} = ClientContext,
#oidcc_provider_configuration{
introspection_endpoint = Endpoint0,
issuer = Issuer,
Expand Down Expand Up @@ -165,7 +166,8 @@ introspect(AccessToken, ClientContext, Opts) ->
uri_string:compose_query(Body)},
{ok, {{json, Token}, _Headers}} ?=
oidcc_http_util:request(post, Request, TelemetryOpts, RequestOpts),
extract_response(Token, ClientContext)
{ok, TokenMap} ?= extract_response(Token),
client_match(TokenMap, ClientContext, maps:get(client_self_only, Opts, true))
else
{error, {use_dpop_nonce, NewDpopNonce, _}} when
DpopOpts =:= #{}
Expand All @@ -182,12 +184,28 @@ introspect(AccessToken, ClientContext, Opts) ->
end
end.

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

-spec extract_response(TokenMap) ->
{ok, t()}
when
TokenMap :: map().
extract_response(TokenMap) ->
Active =
case maps:get(<<"active">>, TokenMap, undefined) of
true ->
Expand All @@ -205,11 +223,8 @@ extract_response(TokenMap, #oidcc_client_context{client_id = ClientId}) ->
Aud = maps:get(<<"aud">>, TokenMap, undefined),
Iss = maps:get(<<"iss">>, TokenMap, undefined),
Jti = maps:get(<<"jti">>, TokenMap, undefined),
case maps:get(<<"client_id">>, TokenMap, undefined) of
IntrospectionClientId when
IntrospectionClientId == ClientId; IntrospectionClientId == undefined
->
{ok, #oidcc_token_introspection{
ClientId = maps:get(<<"client_id">>, TokenMap, undefined),
{ok, #oidcc_token_introspection{
active = Active,
scope = oidcc_scope:parse(Scope),
client_id = ClientId,
Expand Down Expand Up @@ -239,7 +254,4 @@ extract_response(TokenMap, #oidcc_client_context{client_id = ClientId}) ->
],
TokenMap
)
}};
_ ->
{error, client_id_mismatch}
end.
}}.
75 changes: 75 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,69 @@ 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 6aebbdc

Please sign in to comment.