Skip to content

Commit 0183d9a

Browse files
Move introspect token to oauth2_client
1 parent 580eb1b commit 0183d9a

File tree

9 files changed

+143
-93
lines changed

9 files changed

+143
-93
lines changed

deps/oauth2_client/include/oauth2_client.hrl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
-define(REQUEST_CLIENT_SECRET, "client_secret").
2828
-define(REQUEST_SCOPE, "scope").
2929
-define(REQUEST_REFRESH_TOKEN, "refresh_token").
30+
-define(REQUEST_TOKEN, "token").
3031

3132
% define access token response constants
3233
-define(BEARER_TOKEN_TYPE, <<"Bearer">>).

deps/oauth2_client/include/types.hrl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,13 @@
7878
}).
7979

8080
-type refresh_token_request() :: #refresh_token_request{}.
81+
82+
-record(introspect_token_request, {
83+
endpoint :: option(uri_string:uri_string()),
84+
client_id :: binary() | undefined,
85+
client_secret :: binary() | undefined,
86+
client_auth_method :: basic | request_param | undefined,
87+
ssl_options :: option(list())
88+
}).
89+
90+
-type introspect_token_request() :: #introspect_token_request{}.

deps/oauth2_client/src/oauth2_client.erl

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
-module(oauth2_client).
88
-export([get_access_token/2, get_expiration_time/1,
99
refresh_access_token/2,
10+
introspect_token/1,
1011
get_oauth_provider/1, get_oauth_provider/2,
1112
get_openid_configuration/2,
1213
build_openid_discovery_endpoint/3,
@@ -48,6 +49,96 @@ refresh_access_token(OAuthProvider, Request) ->
4849
Response = httpc:request(post, {URL, Header, Type, Body}, HTTPOptions, Options),
4950
parse_access_token_response(Response).
5051

52+
-spec introspect_token(binary()) ->
53+
{ok, successful_access_token_response()} |
54+
{error, unsuccessful_access_token_response() | any()}.
55+
introspect_token(Token) ->
56+
case build_introspection_request() of
57+
{ok, Request} ->
58+
URL = Request#introspect_token_request.endpoint,
59+
Header = build_introspect_authorization_header_if_any(Request),
60+
Type = ?CONTENT_URLENCODED,
61+
Body = build_introspect_request_parameters(Token, Request),
62+
HTTPOptions = case Request#introspect_token_request.ssl_options of
63+
undefined -> [];
64+
SSL -> [{ssl, SSL}]
65+
end ++ get_default_timeout(),
66+
Options = [],
67+
Response = httpc:request(post, {URL, Header, Type, Body}, HTTPOptions, Options),
68+
parse_access_token_response(Response);
69+
{error, Message} ->
70+
#unsuccessful_access_token_response{
71+
error = 500,
72+
error_description = binary_to_list(Message)
73+
}
74+
end.
75+
76+
build_introspect_request_parameters(Token, #introspect_token_request{
77+
client_auth_method = Method,
78+
client_id = ClientId,
79+
client_secret = ClientSecret}) ->
80+
QueryList = case Method of
81+
request_param -> [
82+
client_id_request_parameter(ClientId),
83+
client_secret_request_parameter(ClientSecret)
84+
];
85+
_ -> []
86+
end,
87+
uri_string:compose_query([{?REQUEST_TOKEN, Token} | QueryList]).
88+
89+
90+
build_introspect_authorization_header_if_any(#introspect_token_request{
91+
client_auth_method = Method,
92+
client_id = ClientId,
93+
client_secret = ClientSecret}) ->
94+
case Method of
95+
basic ->
96+
Credentials = binary_to_list(<<ClientId/binary,":",ClientSecret/binary>>),
97+
AuthStr = base64:encode_to_string(Credentials),
98+
[{"Authorization", "Basic " ++ AuthStr}];
99+
_ -> []
100+
end.
101+
102+
build_introspection_request() ->
103+
Result = case get_oauth_provider([introspection_endpoint]) of
104+
{ok, Provider} ->
105+
case {Provider#oauth_provider.introspection_client_id,
106+
Provider#oauth_provider.introspection_client_secret} of
107+
{undefined, _} -> {error, not_found_introspection_endpoint};
108+
{_, _} -> {ok, build_introspection_request(Provider)}
109+
end;
110+
{error, _} = Error -> Error
111+
end,
112+
Providers = case Result of
113+
{ok, _} -> Result;
114+
{error, _} ->
115+
maps:filter(fun(K,V) ->
116+
case {V#oauth_provider.introspection_client_id,
117+
V#oauth_provider.introspection_client_secret} of
118+
{undefined, _} -> false;
119+
{_Id, _Secret} ->
120+
case get_oauth_provider(K, [introspection_endpoint]) of
121+
{ok, _} -> true;
122+
_ -> false
123+
end
124+
end
125+
end, get_env(oauth_providers))
126+
end,
127+
case maps:size(Providers) of
128+
0 -> {error, not_found_introspection_endpoint};
129+
1 -> {ok, build_introspection_request(lists:last(maps:values(Providers))) };
130+
_ -> {error, too_many_introspection_endpoints}
131+
end.
132+
133+
build_introspection_request(Provider) ->
134+
#introspect_token_request{
135+
endpoint = Provider#oauth_provider.introspection_endpoint,
136+
client_id = Provider#oauth_provider.introspection_client_id,
137+
client_secret = Provider#oauth_provider.introspection_client_secret,
138+
client_auth_method = Provider#oauth_provider.introspection_client_auth_method,
139+
ssl_options = Provider#oauth_provider.ssl_options
140+
}.
141+
51142
append_paths(Path1, Path2) ->
52143
erlang:iolist_to_binary([Path1, Path2]).
53144

@@ -412,6 +503,9 @@ lookup_root_oauth_provider() ->
412503
authorization_endpoint = get_env(authorization_endpoint),
413504
end_session_endpoint = get_env(end_session_endpoint),
414505
introspection_endpoint = get_env(introspection_endpoint),
506+
introspection_client_auth_method = get_env(introspection_client_auth_method),
507+
introspection_client_id = get_env(introspection_client_id),
508+
introspection_client_secret = get_env(introspection_client_secret),
415509
ssl_options = extract_ssl_options_as_list(Map)
416510
}.
417511

@@ -546,9 +640,11 @@ get_ssl_options_if_any(OAuthProvider) ->
546640
end.
547641
get_timeout_of_default(Timeout) ->
548642
case Timeout of
549-
undefined -> [{timeout, ?DEFAULT_HTTP_TIMEOUT}];
643+
undefined -> get_default_timeout();
550644
Timeout -> [{timeout, Timeout}]
551645
end.
646+
get_default_timeout() ->
647+
[{timeout, ?DEFAULT_HTTP_TIMEOUT}].
552648

553649
is_json(?CONTENT_JSON) -> true;
554650
is_json(_) -> false.
@@ -601,8 +697,14 @@ map_to_oauth_provider(PropList) when is_list(PropList) ->
601697
proplists:get_value(authorization_endpoint, PropList, undefined),
602698
end_session_endpoint =
603699
proplists:get_value(end_session_endpoint, PropList, undefined),
604-
introspection_endpoint =
700+
introspection_endpoint =
605701
proplists:get_value(introspection_endpoint, PropList, undefined),
702+
introspection_client_id =
703+
proplists:get_value(introspection_client_id, PropList, undefined),
704+
introspection_client_secret =
705+
proplists:get_value(introspection_client_secret, PropList, undefined),
706+
introspection_client_auth_method =
707+
proplists:get_value(introspection_client_auth_method, PropList, basic),
606708
jwks_uri =
607709
proplists:get_value(jwks_uri, PropList, undefined),
608710
ssl_options =

deps/rabbitmq_auth_backend_oauth2/include/oauth2.hrl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,6 @@
4949
preferred_username_claims :: list(),
5050
scope_aliases :: map() | undefined,
5151
oauth_provider_id :: oauth_provider_id(),
52-
oauth_client_id :: binary() | undefined,
53-
oauth_client_secret :: binary() | undefined,
5452
access_token_format :: jwt | opaque | undefined
5553
}).
5654

deps/rabbitmq_auth_backend_oauth2/priv/schema/rabbitmq_auth_backend_oauth2.schema

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@
352352

353353
%% basic_authorization -> Authorization: Basic base64(client_id, client_secret)
354354
%% post_request_param -> &client_id=<client_id>&client_secret=<client_secret>
355-
{mapping,
355+
{mapping,
356356
"auth_oauth2.oauth_providers.$name.introspection_client_auth_method",
357357
"rabbitmq_auth_backend_oauth2.oauth_providers",
358358
[{datatype, {enum, [basic, request_param]}}]}.

deps/rabbitmq_auth_backend_oauth2/src/rabbit_auth_backend_oauth2.erl

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -151,24 +151,32 @@ expiry_timestamp(#auth_user{impl = DecodedTokenFun}) ->
151151

152152
authenticate(_, AuthProps0) ->
153153
AuthProps = to_map(AuthProps0),
154-
Token = token_from_context(AuthProps),
155-
case resolve_resource_server(Token) of
156-
{error, _} = Err0 ->
157-
{refused, "Authentication using OAuth 2/JWT token failed: ~tp", [Err0]};
158-
{ResourceServer, _} = Tuple ->
159-
case check_token(Token, Tuple) of
160-
{refused, {error, {invalid_token, error, _Err, _Stacktrace}}} ->
161-
{refused, "Authentication using an OAuth 2/JWT token failed: provided token is invalid", []};
162-
{refused, Err} ->
163-
{refused, "Authentication using an OAuth 2/JWT token failed: ~tp", [Err]};
164-
{ok, DecodedToken} ->
165-
case with_decoded_token(DecodedToken, fun(In) -> auth_user_from_token(In, ResourceServer) end) of
166-
{error, Err} ->
154+
Token0 = token_from_context(AuthProps),
155+
TokenResult = case uaa_jwt_jwt:is_jwt_token(Token0) of
156+
true -> {ok, Token0};
157+
false -> oauth_client:introspect_token(Token0)
158+
end,
159+
case TokenResult of
160+
{ok, Token} ->
161+
case resolve_resource_server(Token) of
162+
{error, _} = Err0 ->
163+
{refused, "Authentication using OAuth 2/JWT token failed: ~tp", [Err0]};
164+
{ResourceServer, _} = Tuple ->
165+
case check_token(Token, Tuple) of
166+
{refused, {error, {invalid_token, error, _Err, _Stacktrace}}} ->
167+
{refused, "Authentication using an OAuth 2/JWT token failed: provided token is invalid", []};
168+
{refused, Err} ->
167169
{refused, "Authentication using an OAuth 2/JWT token failed: ~tp", [Err]};
168-
Else ->
169-
Else
170+
{ok, DecodedToken} ->
171+
case with_decoded_token(DecodedToken, fun(In) -> auth_user_from_token(In, ResourceServer) end) of
172+
{error, Err} ->
173+
{refused, "Authentication using an OAuth 2/JWT token failed: ~tp", [Err]};
174+
Else ->
175+
Else
176+
end
170177
end
171-
end
178+
end;
179+
_ -> TokenResult
172180
end.
173181

174182
-spec with_decoded_token(Token, Fun) -> Result

deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_provider.erl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ get_internal_oauth_provider(OAuthProviderId) ->
2828
algorithms = get_algorithms(OAuthProviderId)
2929
}.
3030

31-
3231
%%
3332
%% Signing Key storage:
3433
%%

deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_resource_server.erl

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
-export([
1313
resolve_resource_server_from_audience/1,
14-
resolve_single_resource_server_with_opaque_access_token_format/0,
1514
new_resource_server/1
1615
]).
1716

@@ -58,38 +57,6 @@ resolve_resource_server_from_audience(Audience) ->
5857
{ok, get_resource_server(ResourceServerId)}
5958
end.
6059

61-
-spec resolve_single_resource_server_with_opaque_access_token_format() ->
62-
{ok, resource_server()} |
63-
{error, too_many_matched_resource_servers_only_one_allowed} |
64-
{error, no_resource_server_found}.
65-
resolve_single_resource_server_with_opaque_access_token_format() ->
66-
case get_root_resource_server_id() of
67-
<<>> ->
68-
find_unique_resource_server_with_opaque_access_token_format();
69-
_ ->
70-
Root = get_root_resource_server(),
71-
case Root#resource_server.access_token_format of
72-
opaque -> {ok, Root};
73-
_ -> find_unique_resource_server_with_opaque_access_token_format()
74-
end
75-
end.
76-
77-
find_unique_resource_server_with_opaque_access_token_format() ->
78-
Map0 = maps:fold(fun(K,V,Acc)->
79-
case V#resource_server.access_token_format of
80-
opaque ->
81-
case maps:is_key(V#resource_server.oauth_provider_id, Acc) of
82-
false -> maps:put(V#resource_server.oauth_provider_id, K, Acc);
83-
true -> Acc
84-
end;
85-
_ -> Acc
86-
end end, #{}, get_env(resource_servers, #{})),
87-
case maps:size(Map0) of
88-
0 -> {error, no_resource_server_found};
89-
1 -> {ok, lists:last(maps:values(Map0))};
90-
_ -> {error, too_many_matched_resource_servers_only_one_allowed}
91-
end.
92-
9360
-spec get_root_resource_server_id() -> resource_server_id().
9461
get_root_resource_server_id() ->
9562
get_env(resource_server_id, <<>>).

deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_resource_server_SUITE.erl

Lines changed: 3 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@
1919
-define(OAUTH_PROVIDER_B,<<"B">>).
2020

2121
-import(oauth2_client, [get_oauth_provider/2]).
22-
-import(rabbit_oauth2_resource_server,
23-
[resolve_resource_server_from_audience/1,
24-
resolve_single_resource_server_with_opaque_access_token_format/0]).
22+
-import(rabbit_oauth2_resource_server, [resolve_resource_server_from_audience/1]).
2523

2624

2725
all() -> [
@@ -40,16 +38,11 @@ groups() -> [
4038
resolve_resource_server_for_none_audience_returns_rabbitmq,
4139
resolve_resource_server_for_unknown_audience_returns_rabbitmq
4240
]},
43-
cannot_resolve_resource_server_for_opaque_access_token,
44-
{with_opaque_access_token_format, [], [
45-
resolve_resource_server_for_opaque_access_token
46-
]},
4741
{verify_get_rabbitmq_server_configuration, [],
4842
verify_get_rabbitmq_server_configuration()}
4943
]},
5044
{without_resource_server_id, [], [
51-
resolve_resource_server_id_for_any_audience_returns_no_matching_aud_found,
52-
cannot_resolve_resource_server_for_opaque_access_token
45+
resolve_resource_server_id_for_any_audience_returns_no_matching_aud_found
5346
]},
5447

5548
{with_two_resource_servers, [], [
@@ -58,17 +51,13 @@ groups() -> [
5851
resolve_resource_server_id_for_both_resources_returns_error,
5952
resolve_resource_server_for_none_audience_returns_no_aud_found,
6053
resolve_resource_server_for_unknown_audience_returns_no_matching_aud_found,
61-
cannot_resolve_resource_server_for_opaque_access_token,
6254
{with_verify_aud_false, [], [
6355
resolve_resource_server_for_none_audience_returns_rabbitmq2,
6456
resolve_resource_server_for_unknown_audience_returns_rabbitmq2,
6557
{with_rabbitmq1_verify_aud_false, [], [
6658
resolve_resource_server_for_none_audience_returns_error
6759
]}
68-
]},
69-
{with_opaque_access_token_format_for_rabbitmq1_and_rabbitmq2, [], [
70-
resolve_resource_server_for_opaque_access_token
71-
]},
60+
]},
7261
verify_rabbitmq1_server_configuration,
7362
{verify_configuration_inheritance_with_rabbitmq2, [],
7463
verify_configuration_inheritance_with_rabbitmq2()},
@@ -216,19 +205,6 @@ init_per_group(with_two_resource_servers, Config) ->
216205
[{?RABBITMQ_RESOURCE_ONE, RabbitMQ1}, {?RABBITMQ_RESOURCE_TWO, RabbitMQ2}]
217206
++ Config;
218207

219-
init_per_group(with_opaque_access_token_format, Config) ->
220-
set_env(access_token_format, opaque),
221-
Config;
222-
223-
init_per_group(with_opaque_access_token_format_for_rabbitmq1_and_rabbitmq2, Config) ->
224-
RabbitMQServers = get_env(resource_servers, #{}),
225-
Resource0 = maps:get(?RABBITMQ_RESOURCE_ONE, RabbitMQServers, []),
226-
Resource = [{access_token_format, opaque} | Resource0],
227-
Maps0 = maps:put(?RABBITMQ_RESOURCE_ONE, Resource, RabbitMQServers),
228-
Maps1 = maps:put(?RABBITMQ_RESOURCE_TWO, Resource, Maps0),
229-
set_env(resource_servers, Maps1),
230-
Config;
231-
232208
init_per_group(_any, Config) ->
233209
Config.
234210

@@ -280,10 +256,6 @@ end_per_group(with_scope_aliases, Config) ->
280256
unset_env(scope_aliases),
281257
Config;
282258

283-
end_per_group(with_opaque_access_token_format, Config) ->
284-
unset_env(access_token_format),
285-
Config;
286-
287259
end_per_group(_any, Config) ->
288260
Config.
289261

@@ -332,13 +304,6 @@ resolve_resource_server_id_for_both_resources_returns_error(_) ->
332304
assert_resource_server_id({error, aud_matched_many_resource_servers_only_one_allowed},
333305
[?RABBITMQ_RESOURCE_TWO, ?RABBITMQ_RESOURCE_ONE]).
334306

335-
resolve_resource_server_for_opaque_access_token(_) ->
336-
{ok, Actual} = resolve_single_resource_server_with_opaque_access_token_format(),
337-
?assertEqual(?RABBITMQ, Actual#resource_server.id).
338-
339-
cannot_resolve_resource_server_for_opaque_access_token(_) ->
340-
{error, no_resource_server_found} = resolve_single_resource_server_with_opaque_access_token_format().
341-
342307
rabbitmq_verify_aud_is_true(_) ->
343308
assert_verify_aud(true, ?RABBITMQ).
344309

0 commit comments

Comments
 (0)