diff --git a/deps/oauth2_client/BUILD.bazel b/deps/oauth2_client/BUILD.bazel index be565ee245d8..491ea1e4da3c 100644 --- a/deps/oauth2_client/BUILD.bazel +++ b/deps/oauth2_client/BUILD.bazel @@ -108,6 +108,7 @@ rabbitmq_integration_suite( size = "small", additional_beam = [ "test/oauth_http_mock.beam", + "test/oauth2_client_test_util.beam", ], runtime_deps = [ "@cowboy//:erlang_app", diff --git a/deps/oauth2_client/include/oauth2_client.hrl b/deps/oauth2_client/include/oauth2_client.hrl index 745eeec33a53..b7f93104f167 100644 --- a/deps/oauth2_client/include/oauth2_client.hrl +++ b/deps/oauth2_client/include/oauth2_client.hrl @@ -48,7 +48,19 @@ %% The closest we have to a type import in Erlang -type option(T) :: rabbit_types:option(T). +-type oauth_provider_id() :: root | binary(). + +-record(openid_configuration, { + issuer :: option(uri_string:uri_string()), + token_endpoint :: option(uri_string:uri_string()), + authorization_endpoint :: option(uri_string:uri_string()), + end_session_endpoint :: option(uri_string:uri_string()), + jwks_uri :: option(uri_string:uri_string()) + }). +-type openid_configuration() :: #openid_configuration{}. + -record(oauth_provider, { + id :: oauth_provider_id(), issuer :: option(uri_string:uri_string()), token_endpoint :: option(uri_string:uri_string()), authorization_endpoint :: option(uri_string:uri_string()), @@ -58,7 +70,6 @@ }). -type oauth_provider() :: #oauth_provider{}. --type oauth_provider_id() :: binary(). -record(access_token_request, { client_id :: string() | binary(), diff --git a/deps/oauth2_client/src/oauth2_client.erl b/deps/oauth2_client/src/oauth2_client.erl index cb667ee72615..335bcfdfba1b 100644 --- a/deps/oauth2_client/src/oauth2_client.erl +++ b/deps/oauth2_client/src/oauth2_client.erl @@ -7,7 +7,10 @@ -module(oauth2_client). -export([get_access_token/2, get_expiration_time/1, refresh_access_token/2, - get_oauth_provider/1, get_oauth_provider/2, + get_oauth_provider/1, get_oauth_provider/2, + get_openid_configuration/2, get_openid_configuration/3, + merge_openid_configuration/2, + merge_oauth_provider/2, extract_ssl_options_as_list/1 ]). @@ -43,7 +46,8 @@ refresh_access_token(OAuthProvider, Request) -> append_paths(Path1, Path2) -> erlang:iolist_to_binary([Path1, Path2]). --spec get_openid_configuration(uri_string:uri_string(), erlang:iodata() | <<>>, ssl:tls_option() | []) -> {ok, oauth_provider()} | {error, term()}. +-spec get_openid_configuration(uri_string:uri_string(), erlang:iodata() | <<>>, + ssl:tls_option() | []) -> {ok, openid_configuration()} | {error, term()}. get_openid_configuration(IssuerURI, OpenIdConfigurationPath, TLSOptions) -> URLMap = uri_string:parse(IssuerURI), Path = case maps:get(path, URLMap) of @@ -52,24 +56,106 @@ get_openid_configuration(IssuerURI, OpenIdConfigurationPath, TLSOptions) -> P -> append_paths(P, OpenIdConfigurationPath) end, URL = uri_string:resolve(Path, IssuerURI), - rabbit_log:debug("get_openid_configuration issuer URL ~p (~p)", [URL, TLSOptions]), + rabbit_log:debug("get_openid_configuration issuer URL ~p (~p)", [URL, + format_ssl_options(TLSOptions)]), Options = [], Response = httpc:request(get, {URL, []}, TLSOptions, Options), - enrich_oauth_provider(parse_openid_configuration_response(Response), TLSOptions). + parse_openid_configuration_response(Response). --spec get_openid_configuration(uri_string:uri_string(), ssl:tls_option() | []) -> {ok, oauth_provider()} | {error, term()}. +-spec get_openid_configuration(uri_string:uri_string(), ssl:tls_option() | []) -> + {ok, openid_configuration()} | {error, term()}. get_openid_configuration(IssuerURI, TLSOptions) -> get_openid_configuration(IssuerURI, ?DEFAULT_OPENID_CONFIGURATION_PATH, TLSOptions). +% Returns {ok, with_modidified_oauth_provider} or {ok} if oauth_provider was +% not modified +-spec merge_openid_configuration(openid_configuration(), oauth_provider()) -> + oauth_provider(). +merge_openid_configuration(OpendIdConfiguration, OAuthProvider) -> + OAuthProvider0 = case OpendIdConfiguration#openid_configuration.issuer of + undefined -> OAuthProvider; + Issuer -> + OAuthProvider#oauth_provider{issuer = Issuer} + end, + OAuthProvider1 = case OpendIdConfiguration#openid_configuration.token_endpoint of + undefined -> OAuthProvider0; + TokenEndpoint -> + OAuthProvider0#oauth_provider{token_endpoint = TokenEndpoint} + end, + OAuthProvider2 = case OpendIdConfiguration#openid_configuration.authorization_endpoint of + undefined -> OAuthProvider1; + AuthorizationEndpoint -> + OAuthProvider1#oauth_provider{authorization_endpoint = AuthorizationEndpoint} + end, + OAuthProvider3 = case OpendIdConfiguration#openid_configuration.end_session_endpoint of + undefined -> OAuthProvider2; + EndSessionEndpoint -> + OAuthProvider2#oauth_provider{end_session_endpoint = EndSessionEndpoint} + end, + case OpendIdConfiguration#openid_configuration.jwks_uri of + undefined -> OAuthProvider3; + JwksUri -> + OAuthProvider3#oauth_provider{jwks_uri = JwksUri} + end. + +-spec merge_oauth_provider(oauth_provider(), proplists:proplist()) -> + proplists:proplist(). +merge_oauth_provider(OAuthProvider, Proplist) -> + Proplist0 = case OAuthProvider#oauth_provider.token_endpoint of + undefined -> Proplist; + TokenEndpoint -> [{token_endpoint, TokenEndpoint} | + proplists:delete(token_endpoint, Proplist)] + end, + Proplist1 = case OAuthProvider#oauth_provider.authorization_endpoint of + undefined -> Proplist0; + AuthzEndpoint -> [{authorization_endpoint, AuthzEndpoint} | + proplists:delete(authorization_endpoint, Proplist0)] + end, + Proplist2 = case OAuthProvider#oauth_provider.end_session_endpoint of + undefined -> Proplist1; + EndSessionEndpoint -> [{end_session_endpoint, EndSessionEndpoint} | + proplists:delete(end_session_endpoint, Proplist1)] + end, + case OAuthProvider#oauth_provider.jwks_uri of + undefined -> Proplist2; + JwksEndPoint -> [{jwks_uri, JwksEndPoint} | + proplists:delete(jwks_uri, Proplist2)] + end. + +parse_openid_configuration_response({error, Reason}) -> + {error, Reason}; +parse_openid_configuration_response({ok,{{_,Code,Reason}, Headers, Body}}) -> + map_response_to_openid_configuration(Code, Reason, Headers, Body). +map_response_to_openid_configuration(Code, Reason, Headers, Body) -> + case decode_body(proplists:get_value("content-type", Headers, ?CONTENT_JSON), Body) of + {error, {error, InternalError}} -> + {error, InternalError}; + {error, _} = Error -> + Error; + Value -> + case Code of + 200 -> {ok, map_to_openid_configuration(Value)}; + 201 -> {ok, map_to_openid_configuration(Value)}; + _ -> {error, Reason} + end + end. +map_to_openid_configuration(Map) -> + #openid_configuration{ + issuer = maps:get(?RESPONSE_ISSUER, Map), + token_endpoint = maps:get(?RESPONSE_TOKEN_ENDPOINT, Map, undefined), + authorization_endpoint = maps:get(?RESPONSE_AUTHORIZATION_ENDPOINT, Map, undefined), + end_session_endpoint = maps:get(?RESPONSE_END_SESSION_ENDPOINT, Map, undefined), + jwks_uri = maps:get(?RESPONSE_JWKS_URI, Map, undefined) + }. --spec get_expiration_time(successful_access_token_response()) -> +-spec get_expiration_time(successful_access_token_response()) -> {ok, [{expires_in, integer() }| {exp, integer() }]} | {error, missing_exp_field}. get_expiration_time(#successful_access_token_response{expires_in = ExpiresInSec, access_token = AccessToken}) -> case ExpiresInSec of - undefined -> - case jwt_helper:get_expiration_time(jwt_helper:decode(AccessToken)) of + undefined -> + case jwt_helper:get_expiration_time(jwt_helper:decode(AccessToken)) of {ok, Exp} -> {ok, [{exp, Exp}]}; - {error, _} = Error -> Error + {error, _} = Error -> Error end; _ -> {ok, [{expires_in, ExpiresInSec}]} end. @@ -112,34 +198,19 @@ do_update_oauth_provider_endpoints_configuration(OAuthProvider) -> List = application:get_env(rabbitmq_auth_backend_oauth2, key_config, []), ModifiedList = case OAuthProvider#oauth_provider.jwks_uri of undefined -> List; - JwksEndPoint -> [{jwks_url, JwksEndPoint} | List] + JwksEndPoint -> [{jwks_url, JwksEndPoint} | proplists:delete(jwks_url, List)] end, application:set_env(rabbitmq_auth_backend_oauth2, key_config, ModifiedList), - rabbit_log:debug("Updated oauth_provider details: ~p ", [ OAuthProvider]), + rabbit_log:debug("Updated oauth_provider details: ~p ", [ format_oauth_provider(OAuthProvider)]), OAuthProvider. do_update_oauth_provider_endpoints_configuration(OAuthProviderId, OAuthProvider) -> OAuthProviders = application:get_env(rabbitmq_auth_backend_oauth2, oauth_providers, #{}), - LookupProviderPropList = maps:get(OAuthProviderId, OAuthProviders), - ModifiedList0 = case OAuthProvider#oauth_provider.token_endpoint of - undefined -> LookupProviderPropList; - TokenEndpoint -> [{token_endpoint, TokenEndpoint} | LookupProviderPropList] - end, - ModifiedList1 = case OAuthProvider#oauth_provider.authorization_endpoint of - undefined -> ModifiedList0; - AuthzEndpoint -> [{authorization_endpoint, AuthzEndpoint} | ModifiedList0] - end, - ModifiedList2 = case OAuthProvider#oauth_provider.end_session_endpoint of - undefined -> ModifiedList1; - EndSessionEndpoint -> [{end_session_endpoint, EndSessionEndpoint} | ModifiedList1] - end, - ModifiedList3 = case OAuthProvider#oauth_provider.jwks_uri of - undefined -> ModifiedList2; - JwksEndPoint -> [{jwks_uri, JwksEndPoint} | ModifiedList2] - end, - ModifiedOAuthProviders = maps:put(OAuthProviderId, ModifiedList3, OAuthProviders), + Proplist = maps:get(OAuthProviderId, OAuthProviders), + ModifiedOAuthProviders = maps:put(OAuthProviderId, + merge_oauth_provider(OAuthProvider, Proplist), OAuthProviders), application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, ModifiedOAuthProviders), - rabbit_log:debug("Replacing oauth_providers ~p", [ ModifiedOAuthProviders]), + rabbit_log:debug("Replaced oauth_providers "), OAuthProvider. use_global_locks_on_all_nodes() -> @@ -176,25 +247,27 @@ unlock(LockId) -> get_oauth_provider(ListOfRequiredAttributes) -> case application:get_env(rabbitmq_auth_backend_oauth2, default_oauth_provider) of undefined -> get_oauth_provider_from_keyconfig(ListOfRequiredAttributes); - {ok, DefaultOauthProvider} -> - rabbit_log:debug("Using default_oauth_provider ~p", [DefaultOauthProvider]), - get_oauth_provider(DefaultOauthProvider, ListOfRequiredAttributes) + {ok, DefaultOauthProviderId} -> + rabbit_log:debug("Using default_oauth_provider ~p", [DefaultOauthProviderId]), + get_oauth_provider(DefaultOauthProviderId, ListOfRequiredAttributes) end. get_oauth_provider_from_keyconfig(ListOfRequiredAttributes) -> OAuthProvider = lookup_oauth_provider_from_keyconfig(), - rabbit_log:debug("Using oauth_provider ~p from keyconfig", [OAuthProvider]), + rabbit_log:debug("Using oauth_provider ~s from keyconfig", [format_oauth_provider(OAuthProvider)]), case find_missing_attributes(OAuthProvider, ListOfRequiredAttributes) of [] -> {ok, OAuthProvider}; - _ -> + _ = MissingAttributes -> + rabbit_log:debug("OauthProvider has following missing attributes ~p", [MissingAttributes]), Result2 = case OAuthProvider#oauth_provider.issuer of undefined -> {error, {missing_oauth_provider_attributes, [issuer]}}; Issuer -> rabbit_log:debug("Downloading oauth_provider using issuer ~p", [Issuer]), case get_openid_configuration(Issuer, get_ssl_options_if_any(OAuthProvider)) of - {ok, OauthProvider} -> - {ok, update_oauth_provider_endpoints_configuration(OauthProvider)}; + {ok, OpenIdConfiguration} -> + {ok, update_oauth_provider_endpoints_configuration( + merge_openid_configuration(OpenIdConfiguration, OAuthProvider))}; {error, _} = Error2 -> Error2 end end, @@ -202,7 +275,7 @@ get_oauth_provider_from_keyconfig(ListOfRequiredAttributes) -> {ok, OAuthProvider2} -> case find_missing_attributes(OAuthProvider2, ListOfRequiredAttributes) of [] -> - rabbit_log:debug("Resolved oauth_provider ~p", [OAuthProvider]), + rabbit_log:debug("Resolved oauth_provider ~p", [format_oauth_provider(OAuthProvider)]), {ok, OAuthProvider2}; _ = Attrs-> {error, {missing_oauth_provider_attributes, Attrs}} @@ -213,35 +286,37 @@ get_oauth_provider_from_keyconfig(ListOfRequiredAttributes) -> -spec get_oauth_provider(oauth_provider_id(), list()) -> {ok, oauth_provider()} | {error, any()}. +get_oauth_provider(root, ListOfRequiredAttributes) -> + get_oauth_provider(ListOfRequiredAttributes); + get_oauth_provider(OAuth2ProviderId, ListOfRequiredAttributes) when is_list(OAuth2ProviderId) -> get_oauth_provider(list_to_binary(OAuth2ProviderId), ListOfRequiredAttributes); -get_oauth_provider(OAuth2ProviderId, ListOfRequiredAttributes) when is_binary(OAuth2ProviderId) -> - rabbit_log:debug("get_oauth_provider ~p with at least these attributes: ~p", [OAuth2ProviderId, ListOfRequiredAttributes]), - case lookup_oauth_provider_config(OAuth2ProviderId) of +get_oauth_provider(OAuthProviderId, ListOfRequiredAttributes) when is_binary(OAuthProviderId) -> + rabbit_log:debug("get_oauth_provider ~p with at least these attributes: ~p", [OAuthProviderId, ListOfRequiredAttributes]), + case lookup_oauth_provider_config(OAuthProviderId) of {error, _} = Error0 -> rabbit_log:debug("Failed to find oauth_provider ~p configuration due to ~p", - [OAuth2ProviderId, Error0]), + [OAuthProviderId, Error0]), Error0; Config -> rabbit_log:debug("Found oauth_provider configuration ~p", [Config]), - OAuthProvider = case Config of - {error,_} = Error -> Error; - _ -> map_to_oauth_provider(Config) - end, - rabbit_log:debug("Resolved oauth_provider ~p", [OAuthProvider]), + OAuthProvider = map_to_oauth_provider(Config), + rabbit_log:debug("Resolved oauth_provider ~p", [format_oauth_provider(OAuthProvider)]), case find_missing_attributes(OAuthProvider, ListOfRequiredAttributes) of [] -> {ok, OAuthProvider}; - _ -> + _ = MissingAttributes -> + rabbit_log:debug("OauthProvider has following missing attributes ~p", [MissingAttributes]), Result2 = case OAuthProvider#oauth_provider.issuer of undefined -> {error, {missing_oauth_provider_attributes, [issuer]}}; Issuer -> rabbit_log:debug("Downloading oauth_provider ~p using issuer ~p", - [OAuth2ProviderId, Issuer]), + [OAuthProviderId, Issuer]), case get_openid_configuration(Issuer, get_ssl_options_if_any(OAuthProvider)) of - {ok, OauthProvider} -> - {ok, update_oauth_provider_endpoints_configuration(OAuth2ProviderId, OauthProvider)}; + {ok, OpenIdConfiguration} -> + {ok, update_oauth_provider_endpoints_configuration(OAuthProviderId, + merge_openid_configuration(OpenIdConfiguration, OAuthProvider))}; {error, _} = Error2 -> Error2 end end, @@ -249,7 +324,7 @@ get_oauth_provider(OAuth2ProviderId, ListOfRequiredAttributes) when is_binary(OA {ok, OAuthProvider2} -> case find_missing_attributes(OAuthProvider2, ListOfRequiredAttributes) of [] -> - rabbit_log:debug("Resolved oauth_provider ~p", [OAuthProvider]), + rabbit_log:debug("Resolved oauth_provider ~p", [format_oauth_provider(OAuthProvider)]), {ok, OAuthProvider2}; _ = Attrs-> {error, {missing_oauth_provider_attributes, Attrs}} @@ -289,6 +364,7 @@ lookup_oauth_provider_from_keyconfig() -> EndSessionEndpoint = application:get_env(rabbitmq_auth_backend_oauth2, end_session_endpoint, undefined), Map = maps:from_list(application:get_env(rabbitmq_auth_backend_oauth2, key_config, [])), #oauth_provider{ + id = root, issuer = Issuer, jwks_uri = maps:get(jwks_url, Map, undefined), %% jwks_url not uri . _url is the legacy name token_endpoint = TokenEndpoint, @@ -297,8 +373,6 @@ lookup_oauth_provider_from_keyconfig() -> ssl_options = extract_ssl_options_as_list(Map) }. - - -spec extract_ssl_options_as_list(#{atom() => any()}) -> proplists:proplist(). extract_ssl_options_as_list(Map) -> {Verify, CaCerts, CaCertFile} = case get_verify_or_peer_verification(Map, verify_peer) of @@ -313,7 +387,6 @@ extract_ssl_options_as_list(Map) -> end; verify_none -> {verify_none, undefined, undefined} end, - [ {verify, Verify} ] ++ case Verify of @@ -363,10 +436,16 @@ lookup_oauth_provider_config(OAuth2ProviderId) -> case maps:get(OAuth2ProviderId, MapOfProviders, undefined) of undefined -> {error, {oauth_provider_not_found, OAuth2ProviderId}}; - Value -> Value + OAuthProvider -> + ensure_oauth_provider_has_id_property(OAuth2ProviderId, OAuthProvider) end; _ -> {error, invalid_oauth_provider_configuration} end. +ensure_oauth_provider_has_id_property(OAuth2ProviderId, OAuth2Provider) -> + case proplists:is_defined(id, OAuth2Provider) of + true -> OAuth2Provider; + false -> OAuth2Provider ++ [{id, OAuth2ProviderId}] + end. build_access_token_request_body(Request) -> uri_string:compose_query([ @@ -429,8 +508,6 @@ decode_body(MimeType, Body) -> true -> decode_body(?CONTENT_JSON, Body); false -> {error, mime_type_is_not_json} end. - - map_to_successful_access_token_response(Map) -> #successful_access_token_response{ access_token = maps:get(?RESPONSE_ACCESS_TOKEN, Map), @@ -438,25 +515,14 @@ map_to_successful_access_token_response(Map) -> refresh_token = maps:get(?RESPONSE_REFRESH_TOKEN, Map, undefined), expires_in = maps:get(?RESPONSE_EXPIRES_IN, Map, undefined) }. - map_to_unsuccessful_access_token_response(Map) -> #unsuccessful_access_token_response{ error = maps:get(?RESPONSE_ERROR, Map), error_description = maps:get(?RESPONSE_ERROR_DESCRIPTION, Map, undefined) }. - - -map_to_oauth_provider(Map) when is_map(Map) -> - #oauth_provider{ - issuer = maps:get(?RESPONSE_ISSUER, Map), - token_endpoint = maps:get(?RESPONSE_TOKEN_ENDPOINT, Map, undefined), - authorization_endpoint = maps:get(?RESPONSE_AUTHORIZATION_ENDPOINT, Map, undefined), - end_session_endpoint = maps:get(?RESPONSE_END_SESSION_ENDPOINT, Map, undefined), - jwks_uri = maps:get(?RESPONSE_JWKS_URI, Map, undefined) - }; - -map_to_oauth_provider(PropList) when is_list(PropList) -> +map_to_oauth_provider(PropList) when is_list(PropList) -> #oauth_provider{ + id = proplists:get_value(id, PropList), issuer = proplists:get_value(issuer, PropList), token_endpoint = proplists:get_value(token_endpoint, PropList), authorization_endpoint = proplists:get_value(authorization_endpoint, PropList, undefined), @@ -464,13 +530,6 @@ map_to_oauth_provider(PropList) when is_list(PropList) -> jwks_uri = proplists:get_value(jwks_uri, PropList, undefined), ssl_options = extract_ssl_options_as_list(maps:from_list(proplists:get_value(https, PropList, []))) }. - - -enrich_oauth_provider({ok, OAuthProvider}, TLSOptions) -> - {ok, OAuthProvider#oauth_provider{ssl_options=TLSOptions}}; -enrich_oauth_provider(Response, _) -> - Response. - map_to_access_token_response(Code, Reason, Headers, Body) -> case decode_body(proplists:get_value("content-type", Headers, ?CONTENT_JSON), Body) of {error, {error, InternalError}} -> @@ -487,28 +546,38 @@ map_to_access_token_response(Code, Reason, Headers, Body) -> _ -> {error, Reason} end end. - -map_response_to_oauth_provider(Code, Reason, Headers, Body) -> - case decode_body(proplists:get_value("content-type", Headers, ?CONTENT_JSON), Body) of - {error, {error, InternalError}} -> - {error, InternalError}; - {error, _} = Error -> - Error; - Value -> - case Code of - 200 -> {ok, map_to_oauth_provider(Value)}; - 201 -> {ok, map_to_oauth_provider(Value)}; - _ -> {error, Reason} - end - end. - - parse_access_token_response({error, Reason}) -> {error, Reason}; parse_access_token_response({ok,{{_,Code,Reason}, Headers, Body}}) -> map_to_access_token_response(Code, Reason, Headers, Body). -parse_openid_configuration_response({error, Reason}) -> - {error, Reason}; -parse_openid_configuration_response({ok,{{_,Code,Reason}, Headers, Body}}) -> - map_response_to_oauth_provider(Code, Reason, Headers, Body). +-spec format_ssl_options([ssl:tls_client_option()]) -> string(). +format_ssl_options(TlsOptions) -> + CaCertsCount = case proplists:get_value(cacerts, TlsOptions, []) of + [] -> 0; + Certs -> length(Certs) + end, + io_lib:format("{verify: ~p, fail_if_no_peer_cert: ~p, crl_check: ~p, " ++ + "depth: ~p, cacertfile: ~p, cacerts(count): ~p }", [ + proplists:get_value(verify, TlsOptions), + proplists:get_value(fail_if_no_peer_cert, TlsOptions), + proplists:get_value(crl_check, TlsOptions), + proplists:get_value(depth, TlsOptions), + proplists:get_value(cacertfile, TlsOptions), + CaCertsCount]). + +format_oauth_provider_id(root) -> ""; +format_oauth_provider_id(Id) -> binary_to_list(Id). + +-spec format_oauth_provider(oauth_provider()) -> string(). +format_oauth_provider(OAuthProvider) -> + io_lib:format("{id: ~p, issuer: ~p, token_endpoint: ~p, " ++ + "authorization_endpoint: ~p, end_session_endpoint: ~p, " ++ + "jwks_uri: ~p, ssl_options: ~s }", [ + format_oauth_provider_id(OAuthProvider#oauth_provider.id), + OAuthProvider#oauth_provider.issuer, + OAuthProvider#oauth_provider.token_endpoint, + OAuthProvider#oauth_provider.authorization_endpoint, + OAuthProvider#oauth_provider.end_session_endpoint, + OAuthProvider#oauth_provider.jwks_uri, + format_ssl_options(OAuthProvider#oauth_provider.ssl_options)]). diff --git a/deps/oauth2_client/test/system_SUITE.erl b/deps/oauth2_client/test/system_SUITE.erl index 1be0acc72815..a0be9dd3976d 100644 --- a/deps/oauth2_client/test/system_SUITE.erl +++ b/deps/oauth2_client/test/system_SUITE.erl @@ -16,189 +16,76 @@ -define(MOCK_TOKEN_ENDPOINT, <<"/token">>). -define(AUTH_PORT, 8000). --define(GRANT_ACCESS_TOKEN, -#{request => - #{ - method => <<"POST">>, - path => ?MOCK_TOKEN_ENDPOINT, - parameters => [ - {?REQUEST_CLIENT_ID, <<"guest">>}, - {?REQUEST_CLIENT_SECRET, <<"password">>} - ] - }, - response => [ - {code, 200}, - {content_type, ?CONTENT_JSON}, - {payload, [ - {access_token, <<"some access token">>}, - {token_type, <<"Bearer">>} - ]} - ] -}). --define(DENIES_ACCESS_TOKEN, -#{request => - #{ - method => <<"POST">>, - path => ?MOCK_TOKEN_ENDPOINT, - parameters => [ - {?REQUEST_CLIENT_ID, <<"invalid_client">>}, - {?REQUEST_CLIENT_SECRET, <<"password">>} - ] - }, - response => [ - {code, 400}, - {content_type, ?CONTENT_JSON}, - {payload, [ - {error, <<"invalid_client">>}, - {error_description, <<"invalid client found">>} - ]} - ] -}). - --define(AUTH_SERVER_ERROR, -#{request => - #{ - method => <<"POST">>, - path => ?MOCK_TOKEN_ENDPOINT, - parameters => [ - {?REQUEST_CLIENT_ID, <<"guest">>}, - {?REQUEST_CLIENT_SECRET, <<"password">>} - ] - }, - response => [ - {code, 500} - ] -}). - --define(NON_JSON_PAYLOAD, -#{request => - #{ - method => <<"POST">>, - path => ?MOCK_TOKEN_ENDPOINT, - parameters => [ - {?REQUEST_CLIENT_ID, <<"guest">>}, - {?REQUEST_CLIENT_SECRET, <<"password">>} - ] - }, - response => [ - {code, 400}, - {content_type, ?CONTENT_JSON}, - {payload, <<"{ some illegal json}">>} - ] -}). - --define(GET_OPENID_CONFIGURATION, -#{request => - #{ - method => <<"GET">>, - path => ?DEFAULT_OPENID_CONFIGURATION_PATH - }, - response => [ - {code, 200}, - {content_type, ?CONTENT_JSON}, - {payload, [ - {issuer, build_issuer("http") }, - {authorization_endpoint, <<"http://localhost:8000/authorize">>}, - {token_endpoint, build_token_endpoint_uri("http")}, - {end_session_endpoint, <<"http://localhost:8000/logout">>}, - {jwks_uri, build_jwks_uri("http")} - ]} - ] -}). --define(GET_OPENID_CONFIGURATION_WITH_SSL, -#{request => - #{ - method => <<"GET">>, - path => ?DEFAULT_OPENID_CONFIGURATION_PATH - }, - response => [ - {code, 200}, - {content_type, ?CONTENT_JSON}, - {payload, [ - {issuer, build_issuer("https") }, - {authorization_endpoint, <<"https://localhost:8000/authorize">>}, - {token_endpoint, build_token_endpoint_uri("https")}, - {end_session_endpoint, <<"http://localhost:8000/logout">>}, - {jwks_uri, build_jwks_uri("https")} - ]} - ] -}). --define(GRANTS_REFRESH_TOKEN, - #{request => #{ - method => <<"POST">>, - path => ?MOCK_TOKEN_ENDPOINT, - parameters => [ - {?REQUEST_CLIENT_ID, <<"guest">>}, - {?REQUEST_CLIENT_SECRET, <<"password">>}, - {?REQUEST_REFRESH_TOKEN, <<"some refresh token">>} - ] - }, - response => [ - {code, 200}, - {content_type, ?CONTENT_JSON}, - {payload, [ - {access_token, <<"some refreshed access token">>}, - {token_type, <<"Bearer">>} - ]} - ] -}). +-define(ISSUER_PATH, "/somepath"). +-define(CUSTOM_OPENID_CONFIGURATION_ENDPOINT, "/somepath"). +-define(UTIL_MOD, oauth2_client_test_util). +-define(EXPIRES_IN_SECONDS, 10000). all() -> [ - {group, http_up}, - {group, http_down}, - {group, https} + {group, https_down}, + {group, https}, + {group, with_all_oauth_provider_settings} + ]. groups() -> [ - {http_up, [], [ - {group, verify_access_token}, - {group, with_all_oauth_provider_settings}, - {group, without_all_oauth_providers_settings} - ]}, {with_all_oauth_provider_settings, [], [ {group, verify_get_oauth_provider} ]}, {without_all_oauth_providers_settings, [], [ {group, verify_get_oauth_provider} ]}, + {verify_openid_configuration, [], [ + get_openid_configuration, + get_openid_configuration_returns_partial_payload, + get_openid_configuration_using_path, + get_openid_configuration_using_path_and_custom_endpoint, + get_openid_configuration_using_custom_endpoint + ]}, {verify_access_token, [], [ grants_access_token, denies_access_token, auth_server_error, non_json_payload, - grants_refresh_token + grants_refresh_token, + expiration_time_in_response_payload, + expiration_time_in_token ]}, {verify_get_oauth_provider, [], [ get_oauth_provider, + {with_default_oauth_provider, [], [ + get_oauth_provider + ]}, get_oauth_provider_given_oauth_provider_id ]}, - {http_down, [], [ + {https_down, [], [ connection_error ]}, {https, [], [ + {group, verify_openid_configuration}, grants_access_token, grants_refresh_token, ssl_connection_error, - {group, with_all_oauth_provider_settings}, {group, without_all_oauth_providers_settings} ]} ]. init_per_suite(Config) -> [ - {denies_access_token, [ {token_endpoint, ?DENIES_ACCESS_TOKEN} ]}, - {auth_server_error, [ {token_endpoint, ?AUTH_SERVER_ERROR} ]}, - {non_json_payload, [ {token_endpoint, ?NON_JSON_PAYLOAD} ]}, - {grants_refresh_token, [ {token_endpoint, ?GRANTS_REFRESH_TOKEN} ]} + {denies_access_token, [ {token_endpoint, denies_access_token_expectation()} ]}, + {auth_server_error, [ {token_endpoint, auth_server_error_when_access_token_request_expectation()} ]}, + {non_json_payload, [ {token_endpoint, non_json_payload_when_access_token_request_expectation()} ]}, + {grants_refresh_token, [ {token_endpoint, grants_refresh_token_expectation()} ]} | Config]. end_per_suite(Config) -> Config. init_per_group(https, Config) -> + {ok, _} = application:ensure_all_started(inets), {ok, _} = application:ensure_all_started(ssl), application:ensure_all_started(cowboy), Config0 = rabbit_ct_helpers:run_setup_steps(Config), @@ -207,31 +94,51 @@ init_per_group(https, Config) -> WrongCaCertFile = filename:join([CertsDir, "server", "server.pem"]), [{group, https}, {oauth_provider_id, <<"uaa">>}, - {oauth_provider, build_https_oauth_provider(CaCertFile)}, - {oauth_provider_with_issuer, keep_only_issuer_and_ssl_options(build_https_oauth_provider(CaCertFile))}, + {oauth_provider, build_https_oauth_provider(<<"uaa">>, CaCertFile)}, + {oauth_provider_with_issuer, keep_only_issuer_and_ssl_options( + build_https_oauth_provider(<<"uaa">>, CaCertFile))}, {issuer, build_issuer("https")}, - {oauth_provider_with_wrong_ca, build_https_oauth_provider(WrongCaCertFile)} | + {oauth_provider_with_wrong_ca, build_https_oauth_provider(<<"uaa">>, WrongCaCertFile)} | Config0]; -init_per_group(http_up, Config) -> +init_per_group(https_down, Config) -> {ok, _} = application:ensure_all_started(inets), - application:ensure_all_started(cowboy), - [{group, http_up}, - {oauth_provider_id, <<"uaa">>}, - {issuer, build_issuer("http")}, - {oauth_provider_with_issuer, keep_only_issuer_and_ssl_options(build_http_oauth_provider())}, - {oauth_provider, build_http_oauth_provider()} | Config]; + {ok, _} = application:ensure_all_started(ssl), + Config0 = rabbit_ct_helpers:run_setup_steps(Config), + CertsDir = ?config(rmq_certsdir, Config0), + CaCertFile = filename:join([CertsDir, "testca", "cacert.pem"]), -init_per_group(http_down, Config) -> - [{issuer, build_issuer("http")}, + [{issuer, build_issuer("https")}, {oauth_provider_id, <<"uaa">>}, - {oauth_provider, build_http_oauth_provider()} | Config]; + {oauth_provider, build_https_oauth_provider(<<"uaa">>, CaCertFile)} | Config]; + +init_per_group(openid_configuration_with_path, Config) -> + [{use_openid_configuration_with_path, true} | Config]; init_per_group(with_all_oauth_provider_settings, Config) -> - [{with_all_oauth_provider_settings, true} | Config]; + Config0 = rabbit_ct_helpers:run_setup_steps(Config), + CertsDir = ?config(rmq_certsdir, Config0), + CaCertFile = filename:join([CertsDir, "testca", "cacert.pem"]), + + [{with_all_oauth_provider_settings, true}, + {oauth_provider_id, <<"uaa">>}, + {oauth_provider, build_https_oauth_provider(<<"uaa">>, CaCertFile)} | Config]; init_per_group(without_all_oauth_providers_settings, Config) -> - [{with_all_oauth_provider_settings, false} | Config]; + Config0 = rabbit_ct_helpers:run_setup_steps(Config), + CertsDir = ?config(rmq_certsdir, Config0), + CaCertFile = filename:join([CertsDir, "testca", "cacert.pem"]), + + [{with_all_oauth_provider_settings, false}, + {oauth_provider_id, <<"uaa">>}, + {oauth_provider, keep_only_issuer_and_ssl_options( + build_https_oauth_provider(<<"uaa">>, CaCertFile))} | Config]; + +init_per_group(with_default_oauth_provider, Config) -> + OAuthProvider = ?config(oauth_provider, Config), + application:set_env(rabbitmq_auth_backend_oauth2, default_oauth_provider, + OAuthProvider#oauth_provider.id), + Config; init_per_group(_, Config) -> Config. @@ -239,54 +146,87 @@ init_per_group(_, Config) -> get_http_oauth_server_expectations(TestCase, Config) -> case ?config(TestCase, Config) of - undefined -> - case ?config(group, Config) of - https -> [ - {token_endpoint, ?GRANT_ACCESS_TOKEN}, - {get_openid_configuration, ?GET_OPENID_CONFIGURATION_WITH_SSL } - ]; - _ -> [ - {token_endpoint, ?GRANT_ACCESS_TOKEN}, - {get_openid_configuration, ?GET_OPENID_CONFIGURATION } - ] - end; - Expectations -> Expectations + undefined -> + ct:log("get_openid_configuration_http_expectation : ~p", [get_openid_configuration_http_expectation(TestCase)]), + [ {token_endpoint, build_http_mock_behaviour(build_http_access_token_request(), + build_http_200_access_token_response())}, + {get_openid_configuration, get_openid_configuration_http_expectation(TestCase)} + ]; + Expectations -> + Expectations end. +get_openid_configuration_http_expectation(TestCaseAtom) -> + TestCase = binary_to_list(atom_to_binary(TestCaseAtom)), + Payload = case string:find(TestCase, "returns_partial_payload") of + nomatch -> + build_http_get_openid_configuration_payload(); + _ -> + List0 = proplists:delete(authorization_endpoint, + build_http_get_openid_configuration_payload()), + proplists:delete(end_session_endpoint, List0) + end, + Path = case string:find(TestCase, "path") of + nomatch -> ""; + _ -> ?ISSUER_PATH + end, + Endpoint = case string:find(TestCase, "custom_endpoint") of + nomatch -> ?DEFAULT_OPENID_CONFIGURATION_PATH; + _ -> ?CUSTOM_OPENID_CONFIGURATION_ENDPOINT + end, + build_http_mock_behaviour(build_http_get_openid_configuration_request(Endpoint, Path), + build_http_200_json_response(Payload)). lookup_expectation(Endpoint, Config) -> proplists:get_value(Endpoint, ?config(oauth_server_expectations, Config)). + + configure_all_oauth_provider_settings(Config) -> OAuthProvider = ?config(oauth_provider, Config), - OAuthProviders = #{ ?config(oauth_provider_id, Config) => oauth_provider_to_proplist(OAuthProvider) }, - - application:set_env(rabbitmq_auth_backend_oauth2, issuer, OAuthProvider#oauth_provider.issuer), - application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, OAuthProviders), - application:set_env(rabbitmq_auth_backend_oauth2, token_endpoint, OAuthProvider#oauth_provider.token_endpoint), - application:set_env(rabbitmq_auth_backend_oauth2, end_sessione_endpoint, OAuthProvider#oauth_provider.end_session_endpoint), - application:set_env(rabbitmq_auth_backend_oauth2, authorization_endpoint, OAuthProvider#oauth_provider.authorization_endpoint), + OAuthProviders = #{ ?config(oauth_provider_id, Config) => + oauth_provider_to_proplist(OAuthProvider) }, + + application:set_env(rabbitmq_auth_backend_oauth2, issuer, + OAuthProvider#oauth_provider.issuer), + application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, + OAuthProviders), + application:set_env(rabbitmq_auth_backend_oauth2, token_endpoint, + OAuthProvider#oauth_provider.token_endpoint), + application:set_env(rabbitmq_auth_backend_oauth2, end_session_endpoint, + OAuthProvider#oauth_provider.end_session_endpoint), + application:set_env(rabbitmq_auth_backend_oauth2, authorization_endpoint, + OAuthProvider#oauth_provider.authorization_endpoint), KeyConfig = [ { jwks_url, OAuthProvider#oauth_provider.jwks_uri } ] ++ case OAuthProvider#oauth_provider.ssl_options of - undefined -> + undefined -> []; - _ -> - [ {peer_verification, proplists:get_value(verify, OAuthProvider#oauth_provider.ssl_options) }, - {cacertfile, proplists:get_value(cacertfile, OAuthProvider#oauth_provider.ssl_options) } ] + _ -> + [ {peer_verification, proplists:get_value(verify, + OAuthProvider#oauth_provider.ssl_options) }, + {cacertfile, proplists:get_value(cacertfile, + OAuthProvider#oauth_provider.ssl_options) } + ] end, application:set_env(rabbitmq_auth_backend_oauth2, key_config, KeyConfig). configure_minimum_oauth_provider_settings(Config) -> OAuthProvider = ?config(oauth_provider_with_issuer, Config), - OAuthProviders = #{ ?config(oauth_provider_id, Config) => oauth_provider_to_proplist(OAuthProvider) }, - application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, OAuthProviders), - application:set_env(rabbitmq_auth_backend_oauth2, issuer, OAuthProvider#oauth_provider.issuer), + OAuthProviders = #{ ?config(oauth_provider_id, Config) => + oauth_provider_to_proplist(OAuthProvider) }, + application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, + OAuthProviders), + application:set_env(rabbitmq_auth_backend_oauth2, issuer, + OAuthProvider#oauth_provider.issuer), KeyConfig = case OAuthProvider#oauth_provider.ssl_options of - undefined -> + undefined -> []; - _ -> - [ {peer_verification, proplists:get_value(verify, OAuthProvider#oauth_provider.ssl_options) }, - {cacertfile, proplists:get_value(cacertfile, OAuthProvider#oauth_provider.ssl_options) } ] + _ -> + [{peer_verification, proplists:get_value(verify, + OAuthProvider#oauth_provider.ssl_options) }, + {cacertfile, proplists:get_value(cacertfile, + OAuthProvider#oauth_provider.ssl_options) } + ] end, application:set_env(rabbitmq_auth_backend_oauth2, key_config, KeyConfig). @@ -303,14 +243,14 @@ init_per_testcase(TestCase, Config) -> ListOfExpectations = maps:values(proplists:to_map(HttpOauthServerExpectations)), case ?config(group, Config) of - http_up -> - start_http_oauth_server(?AUTH_PORT, ListOfExpectations); https -> - start_https_oauth_server(?AUTH_PORT, ?config(rmq_certsdir, Config), ListOfExpectations); - _ -> - ok + ct:log("Start https with expectations ~p", [ListOfExpectations]), + start_https_oauth_server(?AUTH_PORT, ?config(rmq_certsdir, Config), + ListOfExpectations); + _ -> + do_nothing end, - [{oauth_server_expectations, HttpOauthServerExpectations} | Config ]. + [{oauth_server_expectations, HttpOauthServerExpectations} | Config ]. end_per_testcase(_, Config) -> application:unset_env(rabbitmq_auth_backend_oauth2, oauth_providers), @@ -320,27 +260,126 @@ end_per_testcase(_, Config) -> application:unset_env(rabbitmq_auth_backend_oauth2, end_session_endpoint), application:unset_env(rabbitmq_auth_backend_oauth2, key_config), case ?config(group, Config) of - http_up -> - stop_http_auth_server(); https -> - stop_http_auth_server(); - _ -> - ok + stop_https_auth_server(); + _ -> + do_nothing end, Config. end_per_group(https_and_rabbitmq_node, Config) -> rabbit_ct_helpers:run_steps(Config, rabbit_ct_broker_helpers:teardown_steps()); +end_per_group(with_default_oauth_provider, Config) -> + application:unset_env(rabbitmq_auth_backend_oauth2, default_oauth_provider), + Config; + end_per_group(_, Config) -> Config. +get_openid_configuration(Config) -> + ExpectedOAuthProvider = ?config(oauth_provider, Config), + SslOptions = [{ssl, ExpectedOAuthProvider#oauth_provider.ssl_options}], + {ok, ActualOpenId} = oauth2_client:get_openid_configuration( + build_issuer("https"), + SslOptions), + ExpectedOpenId = map_oauth_provider_to_openid_configuration(ExpectedOAuthProvider), + assertOpenIdConfiguration(ExpectedOpenId, ActualOpenId). + +map_oauth_provider_to_openid_configuration(OAuthProvider) -> + #openid_configuration{ + issuer = OAuthProvider#oauth_provider.issuer, + token_endpoint = OAuthProvider#oauth_provider.token_endpoint, + end_session_endpoint = OAuthProvider#oauth_provider.end_session_endpoint, + jwks_uri = OAuthProvider#oauth_provider.jwks_uri, + authorization_endpoint = OAuthProvider#oauth_provider.authorization_endpoint + }. +get_openid_configuration_returns_partial_payload(Config) -> + ExpectedOAuthProvider0 = ?config(oauth_provider, Config), + ExpectedOAuthProvider = #oauth_provider{ + issuer = ExpectedOAuthProvider0#oauth_provider.issuer, + token_endpoint = ExpectedOAuthProvider0#oauth_provider.token_endpoint, + jwks_uri = ExpectedOAuthProvider0#oauth_provider.jwks_uri}, + + SslOptions = [{ssl, ExpectedOAuthProvider0#oauth_provider.ssl_options}], + {ok, Actual} = oauth2_client:get_openid_configuration( + build_issuer("https"), + SslOptions), + ExpectedOpenId = map_oauth_provider_to_openid_configuration(ExpectedOAuthProvider), + assertOpenIdConfiguration(ExpectedOpenId, Actual). + +get_openid_configuration_using_path(Config) -> + ExpectedOAuthProvider = ?config(oauth_provider, Config), + SslOptions = [{ssl, ExpectedOAuthProvider#oauth_provider.ssl_options}], + {ok, Actual} = oauth2_client:get_openid_configuration( + build_issuer("https", ?ISSUER_PATH), + SslOptions), + ExpectedOpenId = map_oauth_provider_to_openid_configuration(ExpectedOAuthProvider), + assertOpenIdConfiguration(ExpectedOpenId,Actual). +get_openid_configuration_using_path_and_custom_endpoint(Config) -> + ExpectedOAuthProvider = ?config(oauth_provider, Config), + SslOptions = [{ssl, ExpectedOAuthProvider#oauth_provider.ssl_options}], + {ok, Actual} = oauth2_client:get_openid_configuration( + build_issuer("https", ?ISSUER_PATH), + ?CUSTOM_OPENID_CONFIGURATION_ENDPOINT, + SslOptions), + ExpectedOpenId = map_oauth_provider_to_openid_configuration(ExpectedOAuthProvider), + assertOpenIdConfiguration(ExpectedOpenId, Actual). +get_openid_configuration_using_custom_endpoint(Config) -> + ExpectedOAuthProvider = ?config(oauth_provider, Config), + SslOptions = [{ssl, ExpectedOAuthProvider#oauth_provider.ssl_options}], + {ok, Actual} = oauth2_client:get_openid_configuration( + build_issuer("https"), + ?CUSTOM_OPENID_CONFIGURATION_ENDPOINT, + SslOptions), + ExpectedOpenId = map_oauth_provider_to_openid_configuration(ExpectedOAuthProvider), + assertOpenIdConfiguration(ExpectedOpenId, Actual). + + +assertOpenIdConfiguration(ExpectedOpenIdProvider, ActualOpenIdProvider) -> + ?assertEqual(ExpectedOpenIdProvider#openid_configuration.issuer, + ActualOpenIdProvider#openid_configuration.issuer), + ?assertEqual(ExpectedOpenIdProvider#openid_configuration.jwks_uri, + ActualOpenIdProvider#openid_configuration.jwks_uri), + ?assertEqual(ExpectedOpenIdProvider#openid_configuration.end_session_endpoint, + ActualOpenIdProvider#openid_configuration.end_session_endpoint), + ?assertEqual(ExpectedOpenIdProvider#openid_configuration.token_endpoint, + ActualOpenIdProvider#openid_configuration.token_endpoint), + ?assertEqual(ExpectedOpenIdProvider#openid_configuration.authorization_endpoint, + ActualOpenIdProvider#openid_configuration.authorization_endpoint). + +expiration_time_in_response_payload(Config) -> + #{request := #{parameters := Parameters}, + response := [ {code, 200}, {content_type, _CT}, {payload, _JsonPayload}] } = + lookup_expectation(token_endpoint, Config), + + {ok, #successful_access_token_response{} = Response } = + oauth2_client:get_access_token(?config(oauth_provider, Config), + build_access_token_request(Parameters)), + + {ok, [{expires_in, 10000}]} = oauth2_client:get_expiration_time( + Response#successful_access_token_response{expires_in = 10000}). + +expiration_time_in_token(Config) -> + #{request := #{parameters := Parameters}, + response := [ {code, 200}, {content_type, _CT}, {payload, _JsonPayload}] } = + lookup_expectation(token_endpoint, Config), + + {ok, #successful_access_token_response{} = Response } = + oauth2_client:get_access_token(?config(oauth_provider, Config), + build_access_token_request(Parameters)), + + {ok, [{exp, ?EXPIRES_IN_SECONDS}]} = oauth2_client:get_expiration_time(Response). + grants_access_token_dynamically_resolving_oauth_provider(Config) -> #{request := #{parameters := Parameters}, - response := [ {code, 200}, {content_type, _CT}, {payload, JsonPayload}] } = lookup_expectation(token_endpoint, Config), + response := [ {code, 200}, {content_type, _CT}, {payload, JsonPayload}] } = + lookup_expectation(token_endpoint, Config), - {ok, #successful_access_token_response{access_token = AccessToken, token_type = TokenType} } = - oauth2_client:get_access_token(?config(oauth_provider_id, Config), build_access_token_request(Parameters)), + {ok, #successful_access_token_response{access_token = AccessToken, + token_type = TokenType} } = + oauth2_client:get_access_token(?config(oauth_provider_id, Config), + build_access_token_request(Parameters)), ?assertEqual(proplists:get_value(token_type, JsonPayload), TokenType), ?assertEqual(proplists:get_value(access_token, JsonPayload), AccessToken). @@ -350,8 +389,10 @@ grants_access_token(Config) -> response := [ {code, 200}, {content_type, _CT}, {payload, JsonPayload}] } = lookup_expectation(token_endpoint, Config), - {ok, #successful_access_token_response{access_token = AccessToken, token_type = TokenType} } = - oauth2_client:get_access_token(?config(oauth_provider, Config), build_access_token_request(Parameters)), + {ok, #successful_access_token_response{access_token = AccessToken, + token_type = TokenType} } = + oauth2_client:get_access_token(?config(oauth_provider, Config), + build_access_token_request(Parameters)), ?assertEqual(proplists:get_value(token_type, JsonPayload), TokenType), ?assertEqual(proplists:get_value(access_token, JsonPayload), AccessToken). @@ -360,8 +401,10 @@ grants_refresh_token(Config) -> response := [ {code, 200}, {content_type, _CT}, {payload, JsonPayload}] } = lookup_expectation(token_endpoint, Config), - {ok, #successful_access_token_response{access_token = AccessToken, token_type = TokenType} } = - oauth2_client:refresh_access_token(?config(oauth_provider, Config), build_refresh_token_request(Parameters)), + {ok, #successful_access_token_response{access_token = AccessToken, + token_type = TokenType} } = + oauth2_client:refresh_access_token(?config(oauth_provider, Config), + build_refresh_token_request(Parameters)), ?assertEqual(proplists:get_value(token_type, JsonPayload), TokenType), ?assertEqual(proplists:get_value(access_token, JsonPayload), AccessToken). @@ -369,8 +412,10 @@ denies_access_token(Config) -> #{request := #{parameters := Parameters}, response := [ {code, 400}, {content_type, _CT}, {payload, JsonPayload}] } = lookup_expectation(token_endpoint, Config), - {error, #unsuccessful_access_token_response{error = Error, error_description = ErrorDescription} } = - oauth2_client:get_access_token(?config(oauth_provider, Config),build_access_token_request(Parameters)), + {error, #unsuccessful_access_token_response{error = Error, + error_description = ErrorDescription} } = + oauth2_client:get_access_token(?config(oauth_provider, Config), + build_access_token_request(Parameters)), ?assertEqual(proplists:get_value(error, JsonPayload), Error), ?assertEqual(proplists:get_value(error_description, JsonPayload), ErrorDescription). @@ -378,12 +423,14 @@ auth_server_error(Config) -> #{request := #{parameters := Parameters}, response := [ {code, 500} ] } = lookup_expectation(token_endpoint, Config), {error, "Internal Server Error"} = - oauth2_client:get_access_token(?config(oauth_provider, Config), build_access_token_request(Parameters)). + oauth2_client:get_access_token(?config(oauth_provider, Config), + build_access_token_request(Parameters)). non_json_payload(Config) -> #{request := #{parameters := Parameters}} = lookup_expectation(token_endpoint, Config), {error, {failed_to_decode_json, _ErrorArgs}} = - oauth2_client:get_access_token(?config(oauth_provider, Config), build_access_token_request(Parameters)). + oauth2_client:get_access_token(?config(oauth_provider, Config), + build_access_token_request(Parameters)). connection_error(Config) -> #{request := #{parameters := Parameters}} = lookup_expectation(token_endpoint, Config), @@ -397,44 +444,116 @@ ssl_connection_error(Config) -> {error, {failed_connect, _} } = oauth2_client:get_access_token( ?config(oauth_provider_with_wrong_ca, Config), build_access_token_request(Parameters)). -get_oauth_provider(Config) -> - #{response := [ {code, 200}, {content_type, _CT}, {payload, JsonPayload}] } - = lookup_expectation(get_openid_configuration, Config), - - {ok, #oauth_provider{issuer = Issuer, token_endpoint = TokenEndPoint, jwks_uri = Jwks_uri}} = +verify_get_oauth_provider_returns_oauth_provider_from_key_config() -> + {ok, #oauth_provider{id = Id, + issuer = Issuer, + token_endpoint = TokenEndPoint, + jwks_uri = Jwks_uri}} = oauth2_client:get_oauth_provider([issuer, token_endpoint, jwks_uri]), + ExpectedIssuer = application:get_env(rabbitmq_auth_backend_oauth2, issuer, undefined), + ExpectedTokenEndPoint = application:get_env(rabbitmq_auth_backend_oauth2, token_endpoint, undefined), + ExpectedJwks_uri = proplists:get_value(jwks_url, + application:get_env(rabbitmq_auth_backend_oauth2, key_config, [])), + ?assertEqual(root, Id), + ?assertEqual(ExpectedIssuer, Issuer), + ?assertEqual(ExpectedTokenEndPoint, TokenEndPoint), + ?assertEqual(ExpectedJwks_uri, Jwks_uri). + +verify_get_oauth_provider_returns_default_oauth_provider(DefaultOAuthProviderId) -> + {ok, OAuthProvider1} = + oauth2_client:get_oauth_provider([issuer, token_endpoint, jwks_uri]), + {ok, OAuthProvider2} = + oauth2_client:get_oauth_provider(DefaultOAuthProviderId, + [issuer, token_endpoint, jwks_uri]), + ct:log("verify_get_oauth_provider_returns_default_oauth_provider ~p vs ~p", [OAuthProvider1, OAuthProvider2]), + ?assertEqual(OAuthProvider1, OAuthProvider2). - ?assertEqual(proplists:get_value(issuer, JsonPayload), Issuer), - ?assertEqual(proplists:get_value(token_endpoint, JsonPayload), TokenEndPoint), - ?assertEqual(proplists:get_value(jwks_uri, JsonPayload), Jwks_uri). +get_oauth_provider(Config) -> + case ?config(with_all_oauth_provider_settings, Config) of + true -> + case application:get_env(rabbitmq_auth_backend_oauth2, default_oauth_provider, undefined) of + undefined -> + verify_get_oauth_provider_returns_oauth_provider_from_key_config(); + DefaultOAuthProviderId -> + verify_get_oauth_provider_returns_default_oauth_provider(DefaultOAuthProviderId) + end; + false -> + #{response := [ {code, 200}, {content_type, _CT}, {payload, JsonPayload}] } + = lookup_expectation(get_openid_configuration, Config), + {ok, #oauth_provider{issuer = Issuer, + token_endpoint = TokenEndPoint, + jwks_uri = Jwks_uri} + } = oauth2_client:get_oauth_provider([issuer, token_endpoint, jwks_uri]), + + ?assertEqual(proplists:get_value(issuer, JsonPayload), Issuer), + ?assertEqual(proplists:get_value(token_endpoint, JsonPayload), TokenEndPoint), + ?assertEqual(proplists:get_value(jwks_uri, JsonPayload), Jwks_uri) + end. get_oauth_provider_given_oauth_provider_id(Config) -> - #{response := [ {code, 200}, {content_type, _CT}, {payload, JsonPayload}] } - = lookup_expectation(get_openid_configuration, Config), - - ct:log("get_oauth_provider ~p", [?config(oauth_provider_id, Config)]), - {ok, #oauth_provider{ - issuer = Issuer, - token_endpoint = TokenEndPoint, - authorization_endpoint = AuthorizationEndpoint, - end_session_endpoint = EndSessionEndpoint, - jwks_uri = Jwks_uri}} = - oauth2_client:get_oauth_provider(?config(oauth_provider_id, Config), - [issuer, token_endpoint, jwks_uri, authorization_endpoint, end_session_endpoint]), - - ?assertEqual(proplists:get_value(issuer, JsonPayload), Issuer), - ?assertEqual(proplists:get_value(token_endpoint, JsonPayload), TokenEndPoint), - ?assertEqual(proplists:get_value(authorization_endpoint, JsonPayload), AuthorizationEndpoint), - ?assertEqual(proplists:get_value(end_session_endpoint, JsonPayload), EndSessionEndpoint), - ?assertEqual(proplists:get_value(jwks_uri, JsonPayload), Jwks_uri). + case ?config(with_all_oauth_provider_settings, Config) of + true -> + {ok, #oauth_provider{ + id = Id, + issuer = Issuer, + token_endpoint = TokenEndPoint, + authorization_endpoint = AuthorizationEndpoint, + end_session_endpoint = EndSessionEndpoint, + jwks_uri = Jwks_uri}} = + oauth2_client:get_oauth_provider(?config(oauth_provider_id, Config), + [issuer, token_endpoint, jwks_uri, authorization_endpoint, + end_session_endpoint]), + + OAuthProviders = application:get_env(rabbitmq_auth_backend_oauth2, + oauth_providers, #{}), + ExpectedProvider = maps:get(Id, OAuthProviders, []), + ?assertEqual(proplists:get_value(issuer, ExpectedProvider), + Issuer), + ?assertEqual(proplists:get_value(token_endpoint, ExpectedProvider), + TokenEndPoint), + ?assertEqual(proplists:get_value(authorization_endpoint, ExpectedProvider), + AuthorizationEndpoint), + ?assertEqual(proplists:get_value(end_session_endpoint, ExpectedProvider), + EndSessionEndpoint), + ?assertEqual(proplists:get_value(jwks_uri, ExpectedProvider), + Jwks_uri); + false -> + #{response := [ {code, 200}, {content_type, _CT}, {payload, JsonPayload}] } + = lookup_expectation(get_openid_configuration, Config), + + {ok, #oauth_provider{ + issuer = Issuer, + token_endpoint = TokenEndPoint, + authorization_endpoint = AuthorizationEndpoint, + end_session_endpoint = EndSessionEndpoint, + jwks_uri = Jwks_uri}} = + oauth2_client:get_oauth_provider(?config(oauth_provider_id, Config), + [issuer, token_endpoint, jwks_uri, authorization_endpoint, + end_session_endpoint]), + + ?assertEqual(proplists:get_value(issuer, JsonPayload), + Issuer), + ?assertEqual(proplists:get_value(token_endpoint, JsonPayload), + TokenEndPoint), + ?assertEqual(proplists:get_value(authorization_endpoint, JsonPayload), + AuthorizationEndpoint), + ?assertEqual(proplists:get_value(end_session_endpoint, JsonPayload), + EndSessionEndpoint), + ?assertEqual(proplists:get_value(jwks_uri, JsonPayload), + Jwks_uri) + end. + %%% HELPERS build_issuer(Scheme) -> + build_issuer(Scheme, ""). +build_issuer(Scheme, Path) -> uri_string:recompose(#{scheme => Scheme, host => "localhost", port => rabbit_data_coercion:to_integer(?AUTH_PORT), - path => ""}). + path => Path}). + build_token_endpoint_uri(Scheme) -> uri_string:recompose(#{scheme => Scheme, @@ -459,60 +578,48 @@ build_refresh_token_request(Request) -> client_secret = proplists:get_value(?REQUEST_CLIENT_SECRET, Request), refresh_token = proplists:get_value(?REQUEST_REFRESH_TOKEN, Request) }. -build_http_oauth_provider() -> - #oauth_provider { - issuer = build_issuer("http"), - token_endpoint = build_token_endpoint_uri("http"), - jwks_uri = build_jwks_uri("http") - }. keep_only_issuer_and_ssl_options(OauthProvider) -> #oauth_provider { + id = OauthProvider#oauth_provider.id, issuer = OauthProvider#oauth_provider.issuer, ssl_options = OauthProvider#oauth_provider.ssl_options }. -build_https_oauth_provider(CaCertFile) -> +build_https_oauth_provider(Id, CaCertFile) -> #oauth_provider { + id = Id, issuer = build_issuer("https"), + authorization_endpoint = "https://localhost:8000/authorize", + end_session_endpoint = "https://localhost:8000/logout", token_endpoint = build_token_endpoint_uri("https"), jwks_uri = build_jwks_uri("https"), ssl_options = ssl_options(verify_peer, false, CaCertFile) }. -oauth_provider_to_proplist(#oauth_provider{ issuer = Issuer, token_endpoint = TokenEndpoint, - ssl_options = SslOptions, jwks_uri = Jwks_url}) -> +oauth_provider_to_proplist(#oauth_provider{ + issuer = Issuer, + token_endpoint = TokenEndpoint, + end_session_endpoint = EndSessionEndpoint, + authorization_endpoint = AuthorizationEndpoint, + ssl_options = SslOptions, + jwks_uri = Jwks_uri}) -> [ { issuer, Issuer}, {token_endpoint, TokenEndpoint}, + {end_session_endpoint, EndSessionEndpoint}, + {authorization_endpoint, AuthorizationEndpoint}, { https, case SslOptions of undefined -> []; Value -> Value - end}, - {jwks_url, Jwks_url} ]. - -start_http_oauth_server(Port, Expectations) when is_list(Expectations) -> - Dispatch = cowboy_router:compile([ - {'_', [{Path, oauth_http_mock, Expected} || #{request := #{path := Path}} = Expected <- Expectations ]} - ]), - ct:log("start_http_oauth_server with expectation list : ~p -> dispatch: ~p", [Expectations, Dispatch]), - {ok, _} = cowboy:start_clear(mock_http_auth_listener,[ {port, Port} ], - #{env => #{dispatch => Dispatch}}); - -start_http_oauth_server(Port, #{request := #{path := Path}} = Expected) -> - Dispatch = cowboy_router:compile([ - {'_', [{Path, oauth_http_mock, Expected}]} - ]), - ct:log("start_http_oauth_server with expectation : ~p -> dispatch: ~p ", [Expected, Dispatch]), - {ok, _} = cowboy:start_clear( - mock_http_auth_listener, - [{port, Port} - ], - #{env => #{dispatch => Dispatch}}). + end}, + {jwks_uri, Jwks_uri} ]. start_https_oauth_server(Port, CertsDir, Expectations) when is_list(Expectations) -> Dispatch = cowboy_router:compile([ - {'_', [{Path, oauth_http_mock, Expected} || #{request := #{path := Path}} = Expected <- Expectations ]} + {'_', [{Path, oauth_http_mock, Expected} || #{request := #{path := Path}} + = Expected <- Expectations ]} ]), - ct:log("start_https_oauth_server with expectation list : ~p -> dispatch: ~p", [Expectations, Expectations]), + ct:log("start_https_oauth_server with expectation list : ~p -> dispatch: ~p", + [Expectations, Dispatch]), {ok, _} = cowboy:start_tls( mock_http_auth_listener, [{port, Port}, @@ -523,7 +630,8 @@ start_https_oauth_server(Port, CertsDir, Expectations) when is_list(Expectations start_https_oauth_server(Port, CertsDir, #{request := #{path := Path}} = Expected) -> Dispatch = cowboy_router:compile([{'_', [{Path, oauth_http_mock, Expected}]}]), - ct:log("start_https_oauth_server with expectation : ~p -> dispatch: ~p", [Expected, Dispatch]), + ct:log("start_https_oauth_server with expectation : ~p -> dispatch: ~p", + [Expected, Dispatch]), {ok, _} = cowboy:start_tls( mock_http_auth_listener, [{port, Port}, @@ -532,7 +640,7 @@ start_https_oauth_server(Port, CertsDir, #{request := #{path := Path}} = Expecte ], #{env => #{dispatch => Dispatch}}). -stop_http_auth_server() -> +stop_https_auth_server() -> cowboy:stop_listener(mock_http_auth_listener). -spec ssl_options(ssl:verify_type(), boolean(), file:filename()) -> list(). @@ -543,3 +651,130 @@ ssl_options(PeerVerification, FailIfNoPeerCert, CaCertFile) -> {crl_check, false}, {crl_cache, {ssl_crl_cache, {internal, [{http, 10000}]}}}, {cacertfile, CaCertFile}]. + +token(ExpiresIn) -> + Jwk = ?UTIL_MOD:fixture_jwk(), + AccessToken = ?UTIL_MOD:expirable_token_with_expiration_time(ExpiresIn), + {_, EncodedToken} = ?UTIL_MOD:sign_token_hs(AccessToken, Jwk), + EncodedToken. + + + +build_http_mock_behaviour(Request, Response) -> + #{request => Request, response => Response}. +build_http_get_request(Path) -> + build_http_get_request(Path, undefined). +build_http_get_request(Path, Parameters) -> + build_http_request(<<"GET">>, Path, Parameters). +build_http_request(Method, Path, Parameters) when is_binary(Path) -> + #{ + method => Method, + path => Path, + parameters => Parameters + }; +build_http_request(Method, Path, Parameters) -> + Request = #{ + method => Method, + path => list_to_binary(Path) + }, + case Parameters of + [] -> Request; + undefined -> Request; + _ -> maps:put(parameters, Parameters, Request) + end. + +build_http_get_openid_configuration_request() -> + build_http_get_openid_configuration_request(?DEFAULT_OPENID_CONFIGURATION_PATH). +build_http_get_openid_configuration_request(Endpoint) -> + build_http_get_openid_configuration_request(Endpoint, ""). +build_http_get_openid_configuration_request(Endpoint, Path) -> + build_http_get_request(Path ++ Endpoint). + + +build_http_200_json_response(Payload) -> + build_http_response(200, ?CONTENT_JSON, Payload). + +build_http_response(Code, ContentType, Payload) -> + [ + {code, Code}, + {content_type, ContentType}, + {payload, Payload} + ]. +build_http_get_openid_configuration_payload() -> + Scheme = "https", + [ + {issuer, build_issuer(Scheme) }, + {authorization_endpoint, Scheme ++ "://localhost:8000/authorize"}, + {token_endpoint, build_token_endpoint_uri(Scheme)}, + {end_session_endpoint, Scheme ++ "://localhost:8000/logout"}, + {jwks_uri, build_jwks_uri(Scheme)} + ]. + +build_http_access_token_request() -> + build_http_request( + <<"POST">>, + ?MOCK_TOKEN_ENDPOINT, + [ + {?REQUEST_CLIENT_ID, <<"guest">>}, + {?REQUEST_CLIENT_SECRET, <<"password">>} + ]). +build_http_200_access_token_response() -> + [ + {code, 200}, + {content_type, ?CONTENT_JSON}, + {payload, [ + {access_token, token(?EXPIRES_IN_SECONDS)}, + {token_type, <<"Bearer">>} + ]} + ]. +build_http_400_access_token_response() -> + [ + {code, 400}, + {content_type, ?CONTENT_JSON}, + {payload, [ + {error, <<"invalid_client">>}, + {error_description, <<"invalid client found">>} + ]} + ]. +denies_access_token_expectation() -> + build_http_mock_behaviour(build_http_request( + <<"POST">>, + ?MOCK_TOKEN_ENDPOINT, + [ + {?REQUEST_CLIENT_ID, <<"invalid_client">>}, + {?REQUEST_CLIENT_SECRET, <<"password">>} + ]), build_http_400_access_token_response() + ). +auth_server_error_when_access_token_request_expectation() -> + build_http_mock_behaviour(build_http_request( + <<"POST">>, + ?MOCK_TOKEN_ENDPOINT, + [ + {?REQUEST_CLIENT_ID, <<"guest">>}, + {?REQUEST_CLIENT_SECRET, <<"password">>} + ]), [{code, 500}] + ). +non_json_payload_when_access_token_request_expectation() -> + build_http_mock_behaviour(build_http_request( + <<"POST">>, + ?MOCK_TOKEN_ENDPOINT, + [ + {?REQUEST_CLIENT_ID, <<"guest">>}, + {?REQUEST_CLIENT_SECRET, <<"password">>} + ]), [ + {code, 400}, + {content_type, ?CONTENT_JSON}, + {payload, <<"{ some illegal json}">>} + ] + ). + +grants_refresh_token_expectation() -> + build_http_mock_behaviour(build_http_request( + <<"POST">>, + ?MOCK_TOKEN_ENDPOINT, + [ + {?REQUEST_CLIENT_ID, <<"guest">>}, + {?REQUEST_CLIENT_SECRET, <<"password">>}, + {?REQUEST_REFRESH_TOKEN, <<"some refresh token">>} + ]), build_http_200_access_token_response() + ). diff --git a/deps/oauth2_client/test/unit_SUITE.erl b/deps/oauth2_client/test/unit_SUITE.erl index 0ffa6304ad14..ab632ceedc68 100644 --- a/deps/oauth2_client/test/unit_SUITE.erl +++ b/deps/oauth2_client/test/unit_SUITE.erl @@ -20,6 +20,7 @@ all() -> [ {group, ssl_options}, + {group, merge}, {group, get_expiration_time} ]. @@ -37,9 +38,96 @@ groups() -> access_token_response_without_expiration_time, access_token_response_with_expires_in, access_token_response_with_exp_in_access_token + ]}, + {merge, [], [ + merge_openid_configuration, + merge_oauth_provider ]} ]. +merge_oauth_provider(_) -> + OAuthProvider = #oauth_provider{id = "some_id", ssl_options = [ {verify, verify_none} ]}, + Proplist = [], + Proplist1 = oauth2_client:merge_oauth_provider(OAuthProvider, Proplist), + ?assertEqual([], Proplist), + + OAuthProvider1 = OAuthProvider#oauth_provider{jwks_uri = "https://jwks_uri"}, + Proplist2 = oauth2_client:merge_oauth_provider(OAuthProvider1, Proplist1), + ?assertEqual([{jwks_uri, OAuthProvider1#oauth_provider.jwks_uri}], Proplist2), + + OAuthProvider2 = OAuthProvider1#oauth_provider{end_session_endpoint = "https://end_session_endpoint"}, + Proplist3 = oauth2_client:merge_oauth_provider(OAuthProvider2, Proplist2), + ?assertEqual([{jwks_uri, OAuthProvider2#oauth_provider.jwks_uri}, + {end_session_endpoint, OAuthProvider2#oauth_provider.end_session_endpoint}], + Proplist3), + + OAuthProvider3 = OAuthProvider2#oauth_provider{authorization_endpoint = "https://authorization_endpoint"}, + Proplist4 = oauth2_client:merge_oauth_provider(OAuthProvider3, Proplist3), + ?assertEqual([{jwks_uri, OAuthProvider3#oauth_provider.jwks_uri}, + {end_session_endpoint, OAuthProvider3#oauth_provider.end_session_endpoint}, + {authorization_endpoint, OAuthProvider3#oauth_provider.authorization_endpoint}], + Proplist4), + + OAuthProvider4 = OAuthProvider3#oauth_provider{token_endpoint = "https://token_endpoint"}, + Proplist5 = oauth2_client:merge_oauth_provider(OAuthProvider4, Proplist4), + ?assertEqual([{jwks_uri, OAuthProvider4#oauth_provider.jwks_uri}, + {end_session_endpoint, OAuthProvider4#oauth_provider.end_session_endpoint}, + {authorization_endpoint, OAuthProvider4#oauth_provider.authorization_endpoint}, + {token_endpoint, OAuthProvider4#oauth_provider.token_endpoint}], + Proplist5). + +merge_openid_configuration(_) -> + OpenIdConfiguration = #openid_configuration{}, + OAuthProvider = #oauth_provider{id = "some_id", ssl_options = [ {verify, verify_none} ]}, + OAuthProvider1 = oauth2_client:merge_openid_configuration( + OpenIdConfiguration, OAuthProvider), + ?assertEqual(OAuthProvider#oauth_provider.id, OAuthProvider1#oauth_provider.id), + ?assertEqual([{verify, verify_none}], OAuthProvider1#oauth_provider.ssl_options), + ?assertEqual(undefined, OAuthProvider1#oauth_provider.jwks_uri), + ?assertEqual(undefined, OAuthProvider1#oauth_provider.token_endpoint), + ?assertEqual(undefined, OAuthProvider1#oauth_provider.authorization_endpoint), + ?assertEqual(undefined, OAuthProvider1#oauth_provider.end_session_endpoint), + + OpenIdConfiguration1 = #openid_configuration{jwks_uri = "https://jwks_uri"}, + OAuthProvider2 = oauth2_client:merge_openid_configuration( + OpenIdConfiguration1, OAuthProvider1), + ?assertEqual(OpenIdConfiguration1#openid_configuration.jwks_uri, + OAuthProvider2#oauth_provider.jwks_uri), + ?assertEqual(undefined, OAuthProvider2#oauth_provider.token_endpoint), + ?assertEqual(undefined, OAuthProvider2#oauth_provider.authorization_endpoint), + ?assertEqual(undefined, OAuthProvider2#oauth_provider.end_session_endpoint), + + OpenIdConfiguration2 = #openid_configuration{end_session_endpoint = "https://end_session_endpoint"}, + OAuthProvider3 = oauth2_client:merge_openid_configuration( + OpenIdConfiguration2, OAuthProvider2), + ?assertEqual(OpenIdConfiguration2#openid_configuration.end_session_endpoint, + OAuthProvider3#oauth_provider.end_session_endpoint), + ?assertEqual(undefined, OAuthProvider3#oauth_provider.authorization_endpoint), + ?assertEqual(undefined, OAuthProvider3#oauth_provider.token_endpoint), + + OpenIdConfiguration3 = #openid_configuration{authorization_endpoint = "https://authorization_endpoint"}, + OAuthProvider4 = oauth2_client:merge_openid_configuration( + OpenIdConfiguration3, OAuthProvider3), + ?assertEqual(OpenIdConfiguration3#openid_configuration.authorization_endpoint, + OAuthProvider4#oauth_provider.authorization_endpoint), + ?assertEqual(undefined, OAuthProvider4#oauth_provider.token_endpoint), + + OpenIdConfiguration4 = #openid_configuration{token_endpoint = "https://token_endpoint"}, + OAuthProvider5 = oauth2_client:merge_openid_configuration( + OpenIdConfiguration4, OAuthProvider4), + ?assertEqual(OpenIdConfiguration4#openid_configuration.token_endpoint, + OAuthProvider5#oauth_provider.token_endpoint), + + ?assertEqual(OpenIdConfiguration2#openid_configuration.end_session_endpoint, + OAuthProvider5#oauth_provider.end_session_endpoint), + ?assertEqual(OpenIdConfiguration3#openid_configuration.authorization_endpoint, + OAuthProvider5#oauth_provider.authorization_endpoint), + ?assertEqual(OpenIdConfiguration2#openid_configuration.end_session_endpoint, + OAuthProvider5#oauth_provider.end_session_endpoint), + ?assertEqual(OpenIdConfiguration1#openid_configuration.jwks_uri, + OAuthProvider5#oauth_provider.jwks_uri). + + no_ssl_options_triggers_verify_peer(_) -> ?assertMatch([ {verify, verify_peer}, @@ -83,7 +171,7 @@ peer_verification_set_to_verify_none(_) -> ?assertEqual(Expected2, oauth2_client:extract_ssl_options_as_list(#{ peer_verification => verify_none, cacertfile => "/tmp" - })). + })). peer_verification_set_to_verify_peer_with_cacertfile(_) -> @@ -144,4 +232,3 @@ access_token_response_without_expiration_time(_) -> }, ct:log("AccessTokenResponse ~p", [AccessTokenResponse]), ?assertEqual({error, missing_exp_field}, oauth2_client:get_expiration_time(AccessTokenResponse)). - diff --git a/deps/rabbitmq_auth_backend_oauth2/BUILD.bazel b/deps/rabbitmq_auth_backend_oauth2/BUILD.bazel index 85de4faebfc7..4a5234daf850 100644 --- a/deps/rabbitmq_auth_backend_oauth2/BUILD.bazel +++ b/deps/rabbitmq_auth_backend_oauth2/BUILD.bazel @@ -143,6 +143,14 @@ rabbitmq_suite( ], ) +rabbitmq_suite( + name = "rabbit_oauth2_schema_SUITE", + size = "medium", + deps = [ + "//deps/rabbit_common:erlang_app", + ], +) + rabbitmq_integration_suite( name = "system_SUITE", size = "medium", diff --git a/deps/rabbitmq_auth_backend_oauth2/app.bzl b/deps/rabbitmq_auth_backend_oauth2/app.bzl index 7f15bf721026..5376be18628d 100644 --- a/deps/rabbitmq_auth_backend_oauth2/app.bzl +++ b/deps/rabbitmq_auth_backend_oauth2/app.bzl @@ -14,6 +14,7 @@ def all_beam_files(name = "all_beam_files"): "src/rabbit_auth_backend_oauth2.erl", "src/rabbit_auth_backend_oauth2_app.erl", "src/rabbit_oauth2_config.erl", + "src/rabbit_oauth2_schema.erl", "src/rabbit_oauth2_scope.erl", "src/uaa_jwks.erl", "src/uaa_jwt.erl", @@ -49,6 +50,7 @@ def all_test_beam_files(name = "all_test_beam_files"): "src/rabbit_auth_backend_oauth2_app.erl", "src/rabbit_oauth2_config.erl", "src/rabbit_oauth2_scope.erl", + "src/rabbit_oauth2_schema.erl", "src/uaa_jwks.erl", "src/uaa_jwt.erl", "src/uaa_jwt_jwk.erl", @@ -94,6 +96,7 @@ def all_srcs(name = "all_srcs"): "src/rabbit_auth_backend_oauth2_app.erl", "src/rabbit_oauth2_config.erl", "src/rabbit_oauth2_scope.erl", + "src/rabbit_oauth2_schema.erl", "src/uaa_jwks.erl", "src/uaa_jwt.erl", "src/uaa_jwt_jwk.erl", @@ -156,6 +159,15 @@ def test_suite_beam_files(name = "test_suite_beam_files"): erlc_opts = "//:test_erlc_opts", deps = ["//deps/rabbit_common:erlang_app"], ) + erlang_bytecode( + name = "rabbit_oauth2_schema_SUITE_beam_files", + testonly = True, + srcs = ["test/rabbit_oauth2_schema_SUITE.erl"], + outs = ["test/rabbit_oauth2_schema_SUITE.beam"], + app_name = "rabbitmq_auth_backend_oauth2", + erlc_opts = "//:test_erlc_opts", + deps = ["//deps/rabbit_common:erlang_app"], + ) erlang_bytecode( name = "system_SUITE_beam_files", testonly = True, diff --git a/deps/rabbitmq_auth_backend_oauth2/priv/schema/rabbitmq_auth_backend_oauth2.schema b/deps/rabbitmq_auth_backend_oauth2/priv/schema/rabbitmq_auth_backend_oauth2.schema index c53c5d162b80..399708ae2562 100644 --- a/deps/rabbitmq_auth_backend_oauth2/priv/schema/rabbitmq_auth_backend_oauth2.schema +++ b/deps/rabbitmq_auth_backend_oauth2/priv/schema/rabbitmq_auth_backend_oauth2.schema @@ -130,22 +130,7 @@ {translation, "rabbitmq_auth_backend_oauth2.key_config.signing_keys", fun(Conf) -> - Settings = cuttlefish_variable:filter_by_prefix("auth_oauth2.signing_keys", Conf), - TryReadingFileFun = - fun(Path) -> - case file:read_file(Path) of - {ok, Bin} -> - string:trim(Bin, trailing, "\n"); - _ -> - %% this throws and makes Cuttlefish treak the key as invalid - cuttlefish:invalid("file does not exist or cannot be read by the node") - end - end, - SigningKeys = - lists:map(fun({Id, Path}) -> - {list_to_binary(lists:last(Id)), {pem, TryReadingFileFun(Path)}} - end, Settings), - maps:from_list(SigningKeys) + rabbit_oauth2_schema:translate_signing_keys(Conf) end}. {mapping, @@ -285,36 +270,29 @@ "rabbitmq_auth_backend_oauth2.oauth_providers", [{datatype, {enum, [true, false, peer, best_effort]}}]}. +{mapping, + "auth_oauth2.oauth_providers.$name.default_key", + "rabbitmq_auth_backend_oauth2.oauth_providers", + [{datatype, string}]}. + +%% A map of signing keys +%% +%% {signing_keys, #{<<"id1">> => {pem, <<"value1">>}, <<"id2">> => {pem, <<"value2">>}}} +%% validator doesn't work + +{mapping, + "auth_oauth2.oauth_providers.$name.signing_keys.$id", + "rabbitmq_auth_backend_oauth2.oauth_providers", + [{datatype, file}, {validators, ["file_accessible"]}]}. + +{mapping, + "auth_oauth2.oauth_providers.$name.algorithms.$algorithm", + "rabbitmq_auth_backend_oauth2.oauth_providers", + [{datatype, string}]}. + {translation, "rabbitmq_auth_backend_oauth2.oauth_providers", fun(Conf) -> - Settings = cuttlefish_variable:filter_by_prefix("auth_oauth2.oauth_providers", Conf), - AuthBackends = [{Name, {list_to_atom(Key), list_to_binary(V)}} || {["auth_oauth2","oauth_providers", Name, Key], V} <- Settings ], - Https = [{Name, {https, {list_to_atom(Key), V}}} || {["auth_oauth2","oauth_providers", Name, "https", Key], V} <- Settings ], - - %% Aggregate all options for one provider - KeyFun = fun({Name, _}) -> list_to_binary(Name) end, - ValueFun = fun({_, V}) -> V end, - ProviderNameToListOfSettings = maps:groups_from_list(KeyFun, ValueFun, AuthBackends), - ProviderNameToListOfHttpsSettings = maps:groups_from_list(KeyFun, fun({_, {https, V}}) -> V end, Https), - ProviderNameToListWithHttps = maps:map(fun(K1,L1) -> [{https, L1}] end, ProviderNameToListOfHttpsSettings), - NewGroup = maps:merge_with(fun(K, V1, V2) -> V1 ++ V2 end, ProviderNameToListOfSettings, ProviderNameToListWithHttps), - - ListOrSingleFun = fun(K, List) -> - case K of - ssl_options -> proplists:get_all_values(K, List); - _ -> - case proplists:lookup_all(K, List) of - [One] -> proplists:get_value(K, List); - [One|_] = V -> V - end - end - end, - GroupKeyConfigFun = fun(K, List) -> - ListKeys = proplists:get_keys(List), - [{K, ListOrSingleFun(K, List)} || K <- ListKeys] - end, - maps:map(GroupKeyConfigFun, NewGroup) - + rabbit_oauth2_schema:translate_oauth_providers(Conf) end}. {mapping, @@ -347,34 +325,13 @@ [{datatype, string}] }. +{mapping, + "auth_oauth2.resource_servers.$name.preferred_username_claims.$preferred_username_claims", + "rabbitmq_auth_backend_oauth2.resource_servers", + [{datatype, string}]}. + + {translation, "rabbitmq_auth_backend_oauth2.resource_servers", fun(Conf) -> - Settings = cuttlefish_variable:filter_by_prefix("auth_oauth2.resource_servers", Conf), - AuthBackends = [{Name, {list_to_atom(Key), list_to_binary(V)}} || {["auth_oauth2","resource_servers", Name, Key], V} <- Settings], - KeyFun = fun({Name,_}) -> list_to_binary(Name) end, - ValueFun = fun({_,V}) -> V end, - NewGroup = maps:groups_from_list(KeyFun, ValueFun, AuthBackends), - ListOrSingleFun = fun(K, List) -> - case K of - key_config -> proplists:get_all_values(K, List); - _ -> - case proplists:lookup_all(K, List) of - [One] -> proplists:get_value(K, List); - [One|_] = V -> V - end - end - end, - GroupKeyConfigFun = fun(K, List) -> - ListKeys = proplists:get_keys(List), - [ {K,ListOrSingleFun(K,List)} || K <- ListKeys ] - end, - NewGroupTwo = maps:map(GroupKeyConfigFun, NewGroup), - IndexByIdOrElseNameFun = fun(K, V, NewMap) -> - case proplists:get_value(id, V) of - undefined -> maps:put(K, V, NewMap); - ID -> maps:put(ID, V, NewMap) - end - end, - maps:fold(IndexByIdOrElseNameFun,#{}, NewGroupTwo) - + rabbit_oauth2_schema:translate_resource_servers(Conf) end}. diff --git a/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_config.erl b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_config.erl index 1a02dccde057..f6219c06ad0f 100644 --- a/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_config.erl +++ b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_config.erl @@ -15,13 +15,16 @@ -define(TOP_RESOURCE_SERVER_ID, application:get_env(?APP, resource_server_id)). %% scope aliases map "role names" to a set of scopes - -export([ add_signing_key/2, add_signing_key/3, replace_signing_keys/1, replace_signing_keys/2, - get_signing_keys/0, get_signing_keys/1, get_signing_key/2, - get_key_config/0, get_key_config/1, get_default_resource_server_id/0, - get_oauth_provider_for_resource_server_id/2, + get_signing_keys/0, get_signing_keys/1, get_signing_key/1, get_signing_key/2, + get_default_key/0, + get_default_resource_server_id/0, + get_resource_server_id_for_audience/1, + get_algorithms/0, get_algorithms/1, get_default_key/1, + get_oauth_provider_id_for_resource_server_id/1, + get_oauth_provider/2, get_allowed_resource_server_ids/0, find_audience_in_resource_server_ids/1, is_verify_aud/0, is_verify_aud/1, get_additional_scopes_key/0, get_additional_scopes_key/1, @@ -42,165 +45,239 @@ get_preferred_username_claims() -> append_or_return_default(Value, ?DEFAULT_PREFERRED_USERNAME_CLAIMS); _ -> ?DEFAULT_PREFERRED_USERNAME_CLAIMS end. --spec get_preferred_username_claims(binary()) -> list(). +-spec get_preferred_username_claims(binary() | list()) -> list(). get_preferred_username_claims(ResourceServerId) -> - get_preferred_username_claims(get_default_resource_server_id(), - ResourceServerId). -get_preferred_username_claims(TopResourceServerId, ResourceServerId) - when ResourceServerId =:= TopResourceServerId -> - get_preferred_username_claims(); -get_preferred_username_claims(TopResourceServerId, ResourceServerId) - when ResourceServerId =/= TopResourceServerId -> - ResourceServer = maps:get(ResourceServerId, application:get_env(?APP, - resource_servers, #{})), - case proplists:get_value(preferred_username_claims, ResourceServer) of - undefined -> get_preferred_username_claims(); - Value -> append_or_return_default(Value, ?DEFAULT_PREFERRED_USERNAME_CLAIMS) + ResourceServers = application:get_env(?APP, resource_servers, #{}), + ResourceServer = maps:get(ResourceServerId, ResourceServers, []), + case proplists:get_value(preferred_username_claims, ResourceServer, undefined) of + undefined -> + get_preferred_username_claims(); + Value -> + append_or_return_default(Value, ?DEFAULT_PREFERRED_USERNAME_CLAIMS) end. +-spec get_default_key() -> {ok, binary()} | {error, no_default_key_configured}. +get_default_key() -> + get_default_key(root). + +-spec get_default_key(oauth_provider_id()) -> {ok, binary()} | {error, no_default_key_configured}. +get_default_key(root) -> + case application:get_env(?APP, key_config, undefined) of + undefined -> + {error, no_default_key_configured}; + KeyConfig -> + case proplists:get_value(default_key, KeyConfig, undefined) of + undefined -> {error, no_default_key_configured}; + V -> {ok, V} + end + end; +get_default_key(OauthProviderId) -> + OauthProviders = application:get_env(?APP, oauth_providers, #{}), + case maps:get(OauthProviderId, OauthProviders, []) of + [] -> + {error, no_default_key_configured}; + OauthProvider -> + case proplists:get_value(default_key, OauthProvider, undefined) of + undefined -> {error, no_default_key_configured}; + V -> {ok, V} + end + end. + +%% +%% Signing Key storage: +%% +%% * Static signing keys configured via config file are stored under signing_keys attribute +%% in their respective location (under key_config for the root oauth provider and +%% directly under each oauth provider) +%% * Dynamic signing keys loaded via rabbitmqctl or via JWKS endpoint are stored under +%% jwks attribute in their respective location. However, this attribute stores the +%% combination of static signing keys and dynamic signing keys. If the same kid is +%% found in both sets, the dynamic kid overrides the static kid. +%% + -type key_type() :: json | pem | map. --spec add_signing_key(binary(), {key_type(), binary()} ) -> {ok, map()} | {error, term()}. +-spec add_signing_key(binary(), {key_type(), binary()} ) -> map() | {error, term()}. add_signing_key(KeyId, Key) -> LockId = lock(), - try do_add_signing_key(KeyId, Key) of + try do_add_signing_key(KeyId, Key, root) of V -> V after unlock(LockId) end. --spec add_signing_key(binary(), binary(), {key_type(), binary()}) -> {ok, map()} | {error, term()}. -add_signing_key(ResourceServerId, KeyId, Key) -> - LockId = lock(), - try do_add_signing_key(ResourceServerId, KeyId, Key) of - V -> V - after - unlock(LockId) +-spec add_signing_key(binary(), {key_type(), binary()}, oauth_provider_id()) -> + map() | {error, term()}. +add_signing_key(KeyId, Key, OAuthProviderId) -> + case lock() of + {error, _} = Error -> + Error; + LockId -> + try do_add_signing_key(KeyId, Key, OAuthProviderId) of + V -> V + after + unlock(LockId) + end end. -do_add_signing_key(KeyId, Key) -> - do_replace_signing_keys(maps:put(KeyId, Key, get_signing_keys())). +do_add_signing_key(KeyId, Key, OAuthProviderId) -> + do_replace_signing_keys(maps:put(KeyId, Key, + get_signing_keys_from_jwks(OAuthProviderId)), OAuthProviderId). -do_add_signing_key(ResourceServerId, KeyId, Key) -> - do_replace_signing_keys(ResourceServerId, - maps:put(KeyId, Key, get_signing_keys(ResourceServerId))). +get_signing_keys_from_jwks(root) -> + KeyConfig = application:get_env(?APP, key_config, []), + proplists:get_value(jwks, KeyConfig, #{}); +get_signing_keys_from_jwks(OAuthProviderId) -> + OAuthProviders0 = application:get_env(?APP, oauth_providers, #{}), + OAuthProvider0 = maps:get(OAuthProviderId, OAuthProviders0, []), + proplists:get_value(jwks, OAuthProvider0, #{}). +-spec replace_signing_keys(map()) -> map() | {error, term()}. replace_signing_keys(SigningKeys) -> - LockId = lock(), - try do_replace_signing_keys(SigningKeys) of - V -> V - after - unlock(LockId) + replace_signing_keys(SigningKeys, root). + +-spec replace_signing_keys(map(), oauth_provider_id()) -> map() | {error, term()}. +replace_signing_keys(SigningKeys, OAuthProviderId) -> + case lock() of + {error,_} = Error -> + Error; + LockId -> + try do_replace_signing_keys(SigningKeys, OAuthProviderId) of + V -> V + after + unlock(LockId) + end end. -replace_signing_keys(ResourceServerId, SigningKeys) -> - LockId = lock(), - try do_replace_signing_keys(ResourceServerId, SigningKeys) of - V -> V - after - unlock(LockId) - end. - -do_replace_signing_keys(SigningKeys) -> +do_replace_signing_keys(SigningKeys, root) -> KeyConfig = application:get_env(?APP, key_config, []), - KeyConfig1 = proplists:delete(signing_keys, KeyConfig), - KeyConfig2 = [{signing_keys, SigningKeys} | KeyConfig1], + KeyConfig1 = proplists:delete(jwks, KeyConfig), + KeyConfig2 = [{jwks, maps:merge( + proplists:get_value(signing_keys, KeyConfig1, #{}), + SigningKeys)} | KeyConfig1], application:set_env(?APP, key_config, KeyConfig2), rabbit_log:debug("Replacing signing keys ~p", [ KeyConfig2]), + SigningKeys; + +do_replace_signing_keys(SigningKeys, OauthProviderId) -> + OauthProviders0 = application:get_env(?APP, oauth_providers, #{}), + OauthProvider0 = maps:get(OauthProviderId, OauthProviders0, []), + OauthProvider1 = proplists:delete(jwks, OauthProvider0), + OauthProvider = [{jwks, maps:merge( + proplists:get_value(signing_keys, OauthProvider1, #{}), + SigningKeys)} | OauthProvider1], + + OauthProviders = maps:put(OauthProviderId, OauthProvider, OauthProviders0), + application:set_env(?APP, oauth_providers, OauthProviders), + rabbit_log:debug("Replacing signing keys for ~p -> ~p", [OauthProviderId, OauthProvider]), SigningKeys. -do_replace_signing_keys(ResourceServerId, SigningKeys) -> - do_replace_signing_keys(get_default_resource_server_id(), - ResourceServerId, SigningKeys). -do_replace_signing_keys(TopResourceServerId, ResourceServerId, SigningKeys) - when ResourceServerId =:= TopResourceServerId -> - do_replace_signing_keys(SigningKeys); -do_replace_signing_keys(TopResourceServerId, ResourceServerId, SigningKeys) - when ResourceServerId =/= TopResourceServerId -> - ResourceServers = application:get_env(?APP, resource_servers, #{}), - ResourceServer = maps:get(ResourceServerId, ResourceServers, []), - KeyConfig0 = proplists:get_value(key_config, ResourceServer, []), - KeyConfig1 = proplists:delete(signing_keys, KeyConfig0), - KeyConfig2 = [{signing_keys, SigningKeys} | KeyConfig1], - - ResourceServer1 = proplists:delete(key_config, ResourceServer), - ResourceServer2 = [{key_config, KeyConfig2} | ResourceServer1], - - ResourceServers1 = maps:put(ResourceServerId, ResourceServer2, ResourceServers), - application:set_env(?APP, resource_servers, ResourceServers1), - rabbit_log:debug("Replacing signing keys for ~p -> ~p", [ResourceServerId, ResourceServers1]), - SigningKeys. -spec get_signing_keys() -> map(). -get_signing_keys() -> proplists:get_value(signing_keys, get_key_config(), #{}). +get_signing_keys() -> + get_signing_keys(root). --spec get_signing_keys(binary()) -> map(). -get_signing_keys(ResourceServerId) -> - get_signing_keys(get_default_resource_server_id(), ResourceServerId). +-spec get_signing_keys(oauth_provider_id()) -> map(). +get_signing_keys(root) -> + case application:get_env(?APP, key_config, undefined) of + undefined -> + #{}; + KeyConfig -> + case proplists:get_value(jwks, KeyConfig, undefined) of + undefined -> proplists:get_value(signing_keys, KeyConfig, #{}); + Jwks -> Jwks + end + end; +get_signing_keys(OauthProviderId) -> + OauthProviders = application:get_env(?APP, oauth_providers, #{}), + OauthProvider = maps:get(OauthProviderId, OauthProviders, []), + case proplists:get_value(jwks, OauthProvider, undefined) of + undefined -> + proplists:get_value(signing_keys, OauthProvider, #{}); + Jwks -> + Jwks + end. -get_signing_keys(TopResourceServerId, ResourceServerId) - when ResourceServerId =:= TopResourceServerId -> - get_signing_keys(); -get_signing_keys(TopResourceServerId, ResourceServerId) - when ResourceServerId =/= TopResourceServerId -> - proplists:get_value(signing_keys, get_key_config(ResourceServerId), #{}). +-spec get_resource_server_id_for_audience(binary() | list() | none) -> binary() | {error, term()}. +get_resource_server_id_for_audience(none) -> + case is_verify_aud() of + true -> + {error, no_matching_aud_found}; + false -> + case get_default_resource_server_id() of + {error, missing_resource_server_id_in_config} -> + {error, mising_audience_in_token_and_resource_server_in_config}; + V -> V + end + end; +get_resource_server_id_for_audience(Audience) -> + case find_audience_in_resource_server_ids(Audience) of + {ok, ResourceServerId} -> + ResourceServerId; + {error, only_one_resource_server_as_audience_found_many} = Error -> + Error; + {error, no_matching_aud_found} -> + case is_verify_aud() of + true -> + {error, no_matching_aud_found}; + false -> + case get_default_resource_server_id() of + {error, missing_resource_server_id_in_config} -> + {error, mising_audience_in_token_and_resource_server_in_config}; + V -> V + end + end + end. --spec get_oauth_provider_for_resource_server_id(binary(), list()) -> - {ok, oauth_provider()} | {error, any()}. +-spec get_oauth_provider_id_for_resource_server_id(binary()) -> oauth_provider_id(). -get_oauth_provider_for_resource_server_id(ResourceServerId, RequiredAttributeList) -> - get_oauth_provider_for_resource_server_id(get_default_resource_server_id(), - ResourceServerId, RequiredAttributeList). -get_oauth_provider_for_resource_server_id(TopResourceServerId, - ResourceServerId, RequiredAttributeList) when ResourceServerId =:= TopResourceServerId -> +get_oauth_provider_id_for_resource_server_id(ResourceServerId) -> + get_oauth_provider_id_for_resource_server_id(get_default_resource_server_id(), + ResourceServerId). +get_oauth_provider_id_for_resource_server_id(TopResourceServerId, + ResourceServerId) when ResourceServerId =:= TopResourceServerId -> case application:get_env(?APP, default_oauth_provider) of - undefined -> - oauth2_client:get_oauth_provider(RequiredAttributeList); - {ok, DefaultOauthProviderId} -> - oauth2_client:get_oauth_provider(DefaultOauthProviderId, RequiredAttributeList) + undefined -> root; + {ok, DefaultOauthProviderId} -> DefaultOauthProviderId end; - -get_oauth_provider_for_resource_server_id(TopResourceServerId, ResourceServerId, - RequiredAttributeList) when ResourceServerId =/= TopResourceServerId -> +get_oauth_provider_id_for_resource_server_id(TopResourceServerId, + ResourceServerId) when ResourceServerId =/= TopResourceServerId -> case proplists:get_value(oauth_provider_id, get_resource_server_props(ResourceServerId)) of undefined -> case application:get_env(?APP, default_oauth_provider) of - undefined -> - oauth2_client:get_oauth_provider(RequiredAttributeList); - {ok, DefaultOauthProviderId} -> - oauth2_client:get_oauth_provider(DefaultOauthProviderId, - RequiredAttributeList) + undefined -> root; + {ok, DefaultOauthProviderId} -> DefaultOauthProviderId end; - OauthProviderId -> - oauth2_client:get_oauth_provider(OauthProviderId, RequiredAttributeList) + OauthProviderId -> OauthProviderId end. --spec get_key_config() -> list(). -get_key_config() -> application:get_env(?APP, key_config, []). - --spec get_key_config(binary()) -> list(). -get_key_config(ResourceServerId) -> - get_key_config(get_default_resource_server_id(), ResourceServerId). -get_key_config(TopResourceServerId, ResourceServerId) - when ResourceServerId =:= TopResourceServerId -> - get_key_config(); -get_key_config(TopResourceServerId, ResourceServerId) - when ResourceServerId =/= TopResourceServerId -> - proplists:get_value(key_config, get_resource_server_props(ResourceServerId), - get_key_config()). +-spec get_oauth_provider(oauth_provider_id(), list()) -> + {ok, oauth_provider()} | {error, any()}. +get_oauth_provider(OAuthProviderId, RequiredAttributeList) -> + oauth2_client:get_oauth_provider(OAuthProviderId, RequiredAttributeList). + +-spec get_algorithms() -> list() | undefined. +get_algorithms() -> + get_algorithms(root). + +-spec get_algorithms(oauth_provider_id()) -> list() | undefined. +get_algorithms(root) -> + proplists:get_value(algorithms, application:get_env(?APP, key_config, []), + undefined); +get_algorithms(OAuthProviderId) -> + OAuthProviders = application:get_env(?APP, oauth_providers, #{}), + case maps:get(OAuthProviderId, OAuthProviders, undefined) of + undefined -> undefined; + V -> proplists:get_value(algorithms, V, undefined) + end. get_resource_server_props(ResourceServerId) -> ResourceServers = application:get_env(?APP, resource_servers, #{}), maps:get(ResourceServerId, ResourceServers, []). -get_signing_key(KeyId, ResourceServerId) -> - get_signing_key(get_default_resource_server_id(), KeyId, ResourceServerId). - -get_signing_key(TopResourceServerId, KeyId, ResourceServerId) - when ResourceServerId =:= TopResourceServerId -> - maps:get(KeyId, get_signing_keys(), undefined); -get_signing_key(TopResourceServerId, KeyId, ResourceServerId) - when ResourceServerId =/= TopResourceServerId -> - maps:get(KeyId, get_signing_keys(ResourceServerId), undefined). +get_signing_key(KeyId) -> + maps:get(KeyId, get_signing_keys(root), undefined). +get_signing_key(KeyId, OAuthProviderId) -> + maps:get(KeyId, get_signing_keys(OAuthProviderId), undefined). append_or_return_default(ListOrBinary, Default) -> @@ -213,7 +290,7 @@ append_or_return_default(ListOrBinary, Default) -> -spec get_default_resource_server_id() -> binary() | {error, term()}. get_default_resource_server_id() -> case ?TOP_RESOURCE_SERVER_ID of - undefined -> {error, missing_token_audience_and_or_config_resource_server_id }; + undefined -> {error, missing_resource_server_id_in_config }; {ok, ResourceServerId} -> ResourceServerId end. @@ -241,13 +318,17 @@ find_audience_in_resource_server_ids(AudList) when is_list(AudList) -> [] -> {error, no_matching_aud_found} end. - -spec is_verify_aud() -> boolean(). is_verify_aud() -> application:get_env(?APP, verify_aud, true). -spec is_verify_aud(binary()) -> boolean(). is_verify_aud(ResourceServerId) -> - is_verify_aud(get_default_resource_server_id(), ResourceServerId). + case get_default_resource_server_id() of + {error, _} -> + is_verify_aud(undefined, ResourceServerId); + V -> + is_verify_aud(V, ResourceServerId) + end. is_verify_aud(TopResourceServerId, ResourceServerId) when ResourceServerId =:= TopResourceServerId -> is_verify_aud(); is_verify_aud(TopResourceServerId, ResourceServerId) @@ -261,10 +342,14 @@ get_additional_scopes_key() -> undefined -> {error, not_found}; ScopeKey -> {ok, ScopeKey} end. - -spec get_additional_scopes_key(binary()) -> {ok, binary()} | {error, not_found}. get_additional_scopes_key(ResourceServerId) -> - get_additional_scopes_key(get_default_resource_server_id(), ResourceServerId). + case get_default_resource_server_id() of + {error, _} -> + get_additional_scopes_key(undefined, ResourceServerId); + V -> + get_additional_scopes_key(V, ResourceServerId) + end. get_additional_scopes_key(TopResourceServerId, ResourceServerId) when ResourceServerId =:= TopResourceServerId -> get_additional_scopes_key(); get_additional_scopes_key(TopResourceServerId, ResourceServerId) @@ -279,13 +364,20 @@ get_additional_scopes_key(TopResourceServerId, ResourceServerId) -spec get_scope_prefix() -> binary(). get_scope_prefix() -> - DefaultScopePrefix = erlang:iolist_to_binary([ - get_default_resource_server_id(), <<".">>]), + DefaultScopePrefix = case get_default_resource_server_id() of + {error, _} -> <<"">>; + V -> erlang:iolist_to_binary([V, <<".">>]) + end, application:get_env(?APP, scope_prefix, DefaultScopePrefix). -spec get_scope_prefix(binary()) -> binary(). get_scope_prefix(ResourceServerId) -> - get_scope_prefix(get_default_resource_server_id(), ResourceServerId). + case get_default_resource_server_id() of + {error, _} -> + get_scope_prefix(undefined, ResourceServerId); + V -> + get_scope_prefix(V, ResourceServerId) + end. get_scope_prefix(TopResourceServerId, ResourceServerId) when ResourceServerId =:= TopResourceServerId -> get_scope_prefix(); get_scope_prefix(TopResourceServerId, ResourceServerId) @@ -306,7 +398,12 @@ get_resource_server_type() -> application:get_env(?APP, resource_server_type, << -spec get_resource_server_type(binary()) -> binary(). get_resource_server_type(ResourceServerId) -> - get_resource_server_type(get_default_resource_server_id(), ResourceServerId). + case get_default_resource_server_id() of + {error, _} -> + get_resource_server_type(undefined, ResourceServerId); + V -> + get_resource_server_type(V, ResourceServerId) + end. get_resource_server_type(TopResourceServerId, ResourceServerId) when ResourceServerId =:= TopResourceServerId -> get_resource_server_type(); get_resource_server_type(TopResourceServerId, ResourceServerId) @@ -318,7 +415,12 @@ get_resource_server_type(TopResourceServerId, ResourceServerId) -spec has_scope_aliases(binary()) -> boolean(). has_scope_aliases(ResourceServerId) -> - has_scope_aliases(get_default_resource_server_id(), ResourceServerId). + case get_default_resource_server_id() of + {error, _} -> + has_scope_aliases(undefined, ResourceServerId); + V -> + has_scope_aliases(V, ResourceServerId) + end. has_scope_aliases(TopResourceServerId, ResourceServerId) when ResourceServerId =:= TopResourceServerId -> case application:get_env(?APP, scope_aliases) of @@ -336,7 +438,12 @@ has_scope_aliases(TopResourceServerId, ResourceServerId) -spec get_scope_aliases(binary()) -> map(). get_scope_aliases(ResourceServerId) -> - get_scope_aliases(get_default_resource_server_id(), ResourceServerId). + case get_default_resource_server_id() of + {error, _} -> + get_scope_aliases(undefined, ResourceServerId); + V -> + get_scope_aliases(V, ResourceServerId) + end. get_scope_aliases(TopResourceServerId, ResourceServerId) when ResourceServerId =:= TopResourceServerId -> application:get_env(?APP, scope_aliases, #{}); @@ -357,15 +464,11 @@ lock() -> LockId = case global:set_lock({oauth2_config_lock, rabbitmq_auth_backend_oauth2}, Nodes, Retries) of true -> rabbitmq_auth_backend_oauth2; - false -> undefined + false -> {error, unable_to_claim_lock} end, LockId. unlock(LockId) -> Nodes = rabbit_nodes:list_running(), - case LockId of - undefined -> ok; - Value -> - global:del_lock({oauth2_config_lock, Value}, Nodes) - end, + global:del_lock({oauth2_config_lock, LockId}, Nodes), ok. diff --git a/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_schema.erl b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_schema.erl new file mode 100644 index 000000000000..d79972509ba0 --- /dev/null +++ b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_schema.erl @@ -0,0 +1,157 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% + +-module(rabbit_oauth2_schema). + + +-export([ + translate_oauth_providers/1, + translate_resource_servers/1, + translate_signing_keys/1 +]). + +extract_key_as_binary({Name,_}) -> list_to_binary(Name). +extract_value({_Name,V}) -> V. + +-spec translate_resource_servers([{list(), binary()}]) -> map(). +translate_resource_servers(Conf) -> + Settings = cuttlefish_variable:filter_by_prefix("auth_oauth2.resource_servers", Conf), + Map = merge_list_of_maps([ + extract_resource_server_properties(Settings), + extract_resource_server_preferred_username_claims(Settings) + ]), + Map0 = maps:map(fun(K,V) -> + case proplists:get_value(id, V) of + undefined -> V ++ [{id, K}]; + _ -> V + end end, Map), + ResourceServers = maps:values(Map0), + lists:foldl(fun(Elem,AccMap)-> maps:put(proplists:get_value(id, Elem), Elem, AccMap) end, #{}, + ResourceServers). + +-spec translate_oauth_providers([{list(), binary()}]) -> map(). +translate_oauth_providers(Conf) -> + Settings = cuttlefish_variable:filter_by_prefix("auth_oauth2.oauth_providers", Conf), + + merge_list_of_maps([ + extract_oauth_providers_properties(Settings), + extract_oauth_providers_algorithm(Settings), + extract_oauth_providers_https(Settings), + extract_oauth_providers_signing_keys(Settings)]). + +-spec translate_signing_keys([{list(), binary()}]) -> map(). +translate_signing_keys(Conf) -> + Settings = cuttlefish_variable:filter_by_prefix("auth_oauth2.signing_keys", Conf), + ListOfKidPath = lists:map(fun({Id, Path}) -> {list_to_binary(lists:last(Id)), Path} end, Settings), + translate_list_of_signing_keys(ListOfKidPath). + +-spec translate_list_of_signing_keys([{list(), list()}]) -> map(). +translate_list_of_signing_keys(ListOfKidPath) -> + TryReadingFileFun = + fun(Path) -> + case file:read_file(Path) of + {ok, Bin} -> + string:trim(Bin, trailing, "\n"); + _Error -> + %% this throws and makes Cuttlefish treak the key as invalid + cuttlefish:invalid("file does not exist or cannot be read by the node") + end + end, + maps:map(fun(_K, Path) -> {pem, TryReadingFileFun(Path)} end, maps:from_list(ListOfKidPath)). + +validator_file_exists(Attr, Filename) -> + case file:read_file(Filename) of + {ok, _} -> + Filename; + _Error -> + %% this throws and makes Cuttlefish treak the key as invalid + cuttlefish:invalid(io_lib:format( + "Invalid attribute (~p) value: file ~p does not exist or cannot be read by the node", [Attr, Filename])) + end. +validator_https_uri(Attr, Uri) when is_binary(Uri) -> + list_to_binary(validator_https_uri(Attr, binary_to_list(Uri))); + +validator_https_uri(Attr, Uri) -> + case string:nth_lexeme(Uri, 1, "://") == "https" of + true -> Uri; + false -> + cuttlefish:invalid(io_lib:format( + "Invalid attribute (~p) value: uri ~p must be a valid https uri", [Attr, Uri])) + end. + +merge_list_of_maps(ListOfMaps) -> + lists:foldl(fun(Elem, AccIn) -> maps:merge_with(fun(_K,V1,V2) -> V1 ++ V2 end, + Elem, AccIn) end, #{}, ListOfMaps). + +extract_oauth_providers_properties(Settings) -> + KeyFun = fun extract_key_as_binary/1, + ValueFun = fun extract_value/1, + + OAuthProviders = [{Name, mapOauthProviderProperty({list_to_atom(Key), list_to_binary(V)})} + || {["auth_oauth2","oauth_providers", Name, Key], V} <- Settings ], + maps:groups_from_list(KeyFun, ValueFun, OAuthProviders). + +extract_resource_server_properties(Settings) -> + KeyFun = fun extract_key_as_binary/1, + ValueFun = fun extract_value/1, + + OAuthProviders = [{Name, {list_to_atom(Key), list_to_binary(V)}} + || {["auth_oauth2","resource_servers", Name, Key], V} <- Settings ], + maps:groups_from_list(KeyFun, ValueFun, OAuthProviders). + +mapOauthProviderProperty({Key, Value}) -> + {Key, case Key of + issuer -> validator_https_uri(Key, Value); + token_endpoint -> validator_https_uri(Key, Value); + jwks_uri -> validator_https_uri(Key, Value); + end_session_endpoint -> validator_https_uri(Key, Value); + authorization_endpoint -> validator_https_uri(Key, Value); + _ -> Value + end}. + +extract_oauth_providers_https(Settings) -> + ExtractProviderNameFun = fun extract_key_as_binary/1, + + AttributesPerProvider = [{Name, mapHttpProperty({list_to_atom(Key), V})} || + {["auth_oauth2","oauth_providers", Name, "https", Key], V} <- Settings ], + + maps:map(fun(_K,V)-> [{https, V}] end, + maps:groups_from_list(ExtractProviderNameFun, fun({_, V}) -> V end, AttributesPerProvider)). + +mapHttpProperty({Key, Value}) -> + {Key, case Key of + cacertfile -> validator_file_exists(Key, Value); + _ -> Value + end}. + +extract_oauth_providers_algorithm(Settings) -> + KeyFun = fun extract_key_as_binary/1, + + IndexedAlgorithms = [{Name, {Index, list_to_binary(V)}} || + {["auth_oauth2","oauth_providers", Name, "algorithms", Index], V} <- Settings ], + SortedAlgorithms = lists:sort(fun({_,{AI,_}},{_,{BI,_}}) -> AI < BI end, IndexedAlgorithms), + Algorithms = [{Name, V} || {Name, {_I, V}} <- SortedAlgorithms], + maps:map(fun(_K,V)-> [{algorithms, V}] end, + maps:groups_from_list(KeyFun, fun({_, V}) -> V end, Algorithms)). + +extract_resource_server_preferred_username_claims(Settings) -> + KeyFun = fun extract_key_as_binary/1, + + IndexedClaims = [{Name, {Index, list_to_binary(V)}} || + {["auth_oauth2","resource_servers", Name, "preferred_username_claims", Index], V} <- Settings ], + SortedClaims = lists:sort(fun({_,{AI,_}},{_,{BI,_}}) -> AI < BI end, IndexedClaims), + Claims = [{Name, V} || {Name, {_I, V}} <- SortedClaims], + maps:map(fun(_K,V)-> [{preferred_username_claims, V}] end, + maps:groups_from_list(KeyFun, fun({_, V}) -> V end, Claims)). + +extract_oauth_providers_signing_keys(Settings) -> + KeyFun = fun extract_key_as_binary/1, + + IndexedSigningKeys = [{Name, {list_to_binary(Kid), list_to_binary(V)}} || + {["auth_oauth2","oauth_providers", Name, "signing_keys", Kid], V} <- Settings ], + maps:map(fun(_K,V)-> [{signing_keys, translate_list_of_signing_keys(V)}] end, + maps:groups_from_list(KeyFun, fun({_, V}) -> V end, IndexedSigningKeys)). diff --git a/deps/rabbitmq_auth_backend_oauth2/src/uaa_jwt.erl b/deps/rabbitmq_auth_backend_oauth2/src/uaa_jwt.erl index d78b7b4c9c1c..eafaa2122c74 100644 --- a/deps/rabbitmq_auth_backend_oauth2/src/uaa_jwt.erl +++ b/deps/rabbitmq_auth_backend_oauth2/src/uaa_jwt.erl @@ -29,42 +29,46 @@ add_signing_key(KeyId, Type, Value) -> Err end. --spec update_jwks_signing_keys(term()) -> ok | {error, term()}. -update_jwks_signing_keys(ResourceServerId) -> - case rabbit_oauth2_config:get_oauth_provider_for_resource_server_id(ResourceServerId, [jwks_uri]) of - {error, _} = Error -> - rabbit_log:error("Failed to obtain a JWKS URL for resource_server_id '~tp'", [ResourceServerId]), - Error; - {ok, #oauth_provider{jwks_uri = JwksUrl, ssl_options = SslOptions}} -> - rabbit_log:debug("OAuth 2 JWT: downloading keys from ~tp (TLS options: ~p)", [JwksUrl, SslOptions]), - case uaa_jwks:get(JwksUrl, SslOptions) of - {ok, {_, _, JwksBody}} -> - KeyList = maps:get(<<"keys">>, jose:decode(erlang:iolist_to_binary(JwksBody)), []), - Keys = maps:from_list(lists:map(fun(Key) -> {maps:get(<<"kid">>, Key, undefined), {json, Key}} end, KeyList)), - rabbit_log:debug("OAuth 2 JWT: downloaded keys ~tp", [Keys]), - case rabbit_oauth2_config:replace_signing_keys(ResourceServerId, Keys) of - {error, _} = Err -> Err; - _ -> ok - end; - {error, _} = Err -> - rabbit_log:error("OAuth 2 JWT: failed to download keys: ~tp", [Err]), - Err - end +-spec update_jwks_signing_keys(oauth_provider()) -> ok | {error, term()}. +update_jwks_signing_keys(#oauth_provider{id = Id, jwks_uri = JwksUrl, + ssl_options = SslOptions}) -> + rabbit_log:debug("OAuth 2 JWT: downloading keys from ~tp (TLS options: ~p)", + [JwksUrl, SslOptions]), + case uaa_jwks:get(JwksUrl, SslOptions) of + {ok, {_, _, JwksBody}} -> + KeyList = maps:get(<<"keys">>, + jose:decode(erlang:iolist_to_binary(JwksBody)), []), + Keys = maps:from_list(lists:map(fun(Key) -> + {maps:get(<<"kid">>, Key, undefined), {json, Key}} end, KeyList)), + rabbit_log:debug("OAuth 2 JWT: downloaded keys ~tp", [Keys]), + case rabbit_oauth2_config:replace_signing_keys(Keys, Id) of + {error, _} = Err -> Err; + _ -> ok + end; + {error, _} = Err -> + rabbit_log:error("OAuth 2 JWT: failed to download keys: ~tp", [Err]), + Err end. -spec decode_and_verify(binary()) -> {boolean(), binary(), map()} | {error, term()}. decode_and_verify(Token) -> - case uaa_jwt_jwt:resolve_resource_server_id(Token) of + case resolve_resource_server_id(Token) of {error, _} = Err -> Err; ResourceServerId -> - rabbit_log:debug("OAuth 2 JWT: resolved resource_server_id: '~tp'", [ResourceServerId]), - case uaa_jwt_jwt:get_key_id(ResourceServerId, Token) of + OAuthProviderId = + rabbit_oauth2_config:get_oauth_provider_id_for_resource_server_id(ResourceServerId), + rabbit_log:debug("OAuth 2 JWT: resolved resource_server_id: ~p oauth_provider_id: ~p", + [ResourceServerId, OAuthProviderId]), + case uaa_jwt_jwt:get_key_id(rabbit_oauth2_config:get_default_key(OAuthProviderId), Token) of {ok, KeyId} -> rabbit_log:debug("OAuth 2 JWT: signing_key_id : '~tp'", [KeyId]), - case get_jwk(KeyId, ResourceServerId) of + case get_jwk(KeyId, OAuthProviderId) of {ok, JWK} -> - case uaa_jwt_jwt:decode_and_verify(ResourceServerId, JWK, Token) of + case uaa_jwt_jwt:decode_and_verify( + OAuthProviderId, + JWK, + Token) of {true, Payload} -> {true, ResourceServerId, Payload}; {false, Payload} -> {false, ResourceServerId, Payload} end; @@ -75,23 +79,37 @@ decode_and_verify(Token) -> end end. --spec get_jwk(binary(), binary()) -> {ok, map()} | {error, term()}. -get_jwk(KeyId, ResourceServerId) -> - get_jwk(KeyId, ResourceServerId, true). +resolve_resource_server_id(Token) -> + case uaa_jwt_jwt:get_aud(Token) of + {error, _} = Error -> + Error; + {ok, Audience} -> + rabbit_oauth2_config:get_resource_server_id_for_audience(Audience) + end. + +-spec get_jwk(binary(), oauth_provider_id()) -> {ok, map()} | {error, term()}. +get_jwk(KeyId, OAuthProviderId) -> + get_jwk(KeyId, OAuthProviderId, true). -get_jwk(KeyId, ResourceServerId, AllowUpdateJwks) -> - case rabbit_oauth2_config:get_signing_key(KeyId, ResourceServerId) of +get_jwk(KeyId, OAuthProviderId, AllowUpdateJwks) -> + case rabbit_oauth2_config:get_signing_key(KeyId, OAuthProviderId) of undefined -> if AllowUpdateJwks -> rabbit_log:debug("OAuth 2 JWT: signing key '~tp' not found. Downloading it... ", [KeyId]), - case update_jwks_signing_keys(ResourceServerId) of - ok -> - get_jwk(KeyId, ResourceServerId, false); - {error, no_jwks_url} -> - {error, key_not_found}; - {error, _} = Err -> - Err + case rabbit_oauth2_config:get_oauth_provider(OAuthProviderId, [jwks_uri]) of + {ok, OAuthProvider} -> + case update_jwks_signing_keys(OAuthProvider) of + ok -> + get_jwk(KeyId, OAuthProviderId, false); + {error, no_jwks_url} -> + {error, key_not_found}; + {error, _} = Err -> + Err + end; + {error, _} = Error -> + rabbit_log:debug("OAuth 2 JWT: unable to download keys due to ~p", [Error]), + Error end; true -> rabbit_log:debug("OAuth 2 JWT: signing key '~tp' not found. Downloading is not allowed", [KeyId]), diff --git a/deps/rabbitmq_auth_backend_oauth2/src/uaa_jwt_jwt.erl b/deps/rabbitmq_auth_backend_oauth2/src/uaa_jwt_jwt.erl index 962a3b55daba..7d8c37457028 100644 --- a/deps/rabbitmq_auth_backend_oauth2/src/uaa_jwt_jwt.erl +++ b/deps/rabbitmq_auth_backend_oauth2/src/uaa_jwt_jwt.erl @@ -6,23 +6,15 @@ %% -module(uaa_jwt_jwt). --export([decode/1, decode_and_verify/3, get_key_id/2, get_aud/1, resolve_resource_server_id/1]). +-export([decode_and_verify/3, get_key_id/2, get_aud/1]). -include_lib("jose/include/jose_jwt.hrl"). -include_lib("jose/include/jose_jws.hrl"). -decode(Token) -> - try - #jose_jwt{fields = Fields} = jose_jwt:peek_payload(Token), - Fields - catch Type:Err:Stacktrace -> - {error, {invalid_token, Type, Err, Stacktrace}} - end. -decode_and_verify(ResourceServerId, Jwk, Token) -> - KeyConfig = rabbit_oauth2_config:get_key_config(ResourceServerId), +decode_and_verify(OauthProviderId, Jwk, Token) -> Verify = - case proplists:get_value(algorithms, KeyConfig) of + case rabbit_oauth2_config:get_algorithms(OauthProviderId) of undefined -> jose_jwt:verify(Jwk, Token); Algs -> jose_jwt:verify_strict(Jwk, Algs, Token) end, @@ -32,31 +24,11 @@ decode_and_verify(ResourceServerId, Jwk, Token) -> end. -resolve_resource_server_id(Token) -> - case get_aud(Token) of - {error, _} = Error -> Error; - undefined -> - case rabbit_oauth2_config:is_verify_aud() of - true -> {error, no_matching_aud_found}; - false -> rabbit_oauth2_config:get_default_resource_server_id() - end; - {ok, Audience} -> - case rabbit_oauth2_config:find_audience_in_resource_server_ids(Audience) of - {ok, ResourceServerId} -> ResourceServerId; - {error, only_one_resource_server_as_audience_found_many} = Error -> Error; - {error, no_matching_aud_found} -> - case rabbit_oauth2_config:is_verify_aud() of - true -> {error, no_matching_aud_found}; - false -> rabbit_oauth2_config:get_default_resource_server_id() - end - end - end. - -get_key_id(ResourceServerId, Token) -> +get_key_id(DefaultKey, Token) -> try case jose_jwt:peek_protected(Token) of #jose_jws{fields = #{<<"kid">> := Kid}} -> {ok, Kid}; - #jose_jws{} -> get_default_key(ResourceServerId) + #jose_jws{} -> DefaultKey end catch Type:Err:Stacktrace -> {error, {invalid_token, Type, Err, Stacktrace}} @@ -66,16 +38,8 @@ get_aud(Token) -> try case jose_jwt:peek_payload(Token) of #jose_jwt{fields = #{<<"aud">> := Aud}} -> {ok, Aud}; - #jose_jwt{} -> undefined + #jose_jwt{} -> {ok, none} end catch Type:Err:Stacktrace -> {error, {invalid_token, Type, Err, Stacktrace}} end. - - -get_default_key(ResourceServerId) -> - KeyConfig = rabbit_oauth2_config:get_key_config(ResourceServerId), - case proplists:get_value(default_key, KeyConfig, undefined) of - undefined -> {error, no_key}; - Val -> {ok, Val} - end. diff --git a/deps/rabbitmq_auth_backend_oauth2/test/config_schema_SUITE_data/rabbitmq_auth_backend_oauth2.snippets b/deps/rabbitmq_auth_backend_oauth2/test/config_schema_SUITE_data/rabbitmq_auth_backend_oauth2.snippets index 3d93e06d4d42..b5dcd0a5877a 100644 --- a/deps/rabbitmq_auth_backend_oauth2/test/config_schema_SUITE_data/rabbitmq_auth_backend_oauth2.snippets +++ b/deps/rabbitmq_auth_backend_oauth2/test/config_schema_SUITE_data/rabbitmq_auth_backend_oauth2.snippets @@ -87,12 +87,12 @@ {resource_servers, #{ <<"rabbitmq-operations">> => [ - {id, <<"rabbitmq-operations">>}, - {scope_prefix, <<"api://">>} + {scope_prefix, <<"api://">>}, + {id, <<"rabbitmq-operations">>} ], <<"rabbitmq-customers">> => [ - {id, <<"rabbitmq-customers">>}, - {additional_scopes_key, <<"roles">>} + {additional_scopes_key, <<"roles">>}, + {id, <<"rabbitmq-customers">>} ] } }, @@ -131,8 +131,13 @@ auth_oauth2.oauth_providers.keycloak.jwks_uri = https://keycloak/keys auth_oauth2.oauth_providers.keycloak.authorization_endpoint = https://keycloak/authorize auth_oauth2.oauth_providers.keycloak.end_session_endpoint = https://keycloak/logout - auth_oauth2.oauth_providers.keycloak.https.cacertfile = /mnt/certs/ca_certificate.pem - auth_oauth2.oauth_providers.keycloak.https.verify = verify_none", + auth_oauth2.oauth_providers.keycloak.https.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem + auth_oauth2.oauth_providers.keycloak.https.verify = verify_none + auth_oauth2.oauth_providers.keycloak.https.depth = 2 + auth_oauth2.oauth_providers.keycloak.default_key = token-key + auth_oauth2.oauth_providers.keycloak.signing_keys.id1 = test/config_schema_SUITE_data/certs/key.pem + auth_oauth2.oauth_providers.keycloak.algorithms.1 = HS256 + auth_oauth2.oauth_providers.keycloak.algorithms.2 = RS256", [ {rabbitmq_auth_backend_oauth2, [ {resource_server_id,<<"new_resource_server_id">>}, @@ -143,24 +148,31 @@ {verify_aud, true}, {oauth_providers, #{ - <<"uaa">> => [ - {issuer, <<"https://uaa">>} - ], <<"keycloak">> => [ + {signing_keys, + #{ + <<"id1">> => {pem, <<"I'm not a certificate">>} + } + }, {https, [ + {depth, 2}, {verify, verify_none}, - {cacertfile, "/mnt/certs/ca_certificate.pem"} + {cacertfile, "test/config_schema_SUITE_data/certs/cacert.pem"} ]}, + {algorithms, [<<"HS256">>, <<"RS256">>]}, + {default_key, <<"token-key">>}, {end_session_endpoint, <<"https://keycloak/logout">>}, {authorization_endpoint, <<"https://keycloak/authorize">>}, - {token_endpoint, <<"https://keycloak/token">>}, - {jwks_uri, <<"https://keycloak/keys">>} - ] + {jwks_uri, <<"https://keycloak/keys">>}, + {token_endpoint, <<"https://keycloak/token">>} + ], + <<"uaa">> => [ + {issuer, <<"https://uaa">>} + ] } } ]} ],[] } - ]. diff --git a/deps/rabbitmq_auth_backend_oauth2/test/jwks_SUITE.erl b/deps/rabbitmq_auth_backend_oauth2/test/jwks_SUITE.erl index ec72a0f46abf..db4de4d8a677 100644 --- a/deps/rabbitmq_auth_backend_oauth2/test/jwks_SUITE.erl +++ b/deps/rabbitmq_auth_backend_oauth2/test/jwks_SUITE.erl @@ -23,39 +23,114 @@ all() -> {group, happy_path}, {group, unhappy_path}, {group, no_peer_verification}, - {group, multi_resource} + {group, verify_signing_keys} ]. groups() -> - [ - {happy_path, [], [ - test_successful_connection_with_a_full_permission_token_and_all_defaults, - test_successful_connection_with_a_full_permission_token_and_explicitly_configured_vhost, - test_successful_connection_with_simple_strings_for_aud_and_scope, - test_successful_connection_with_complex_claim_as_a_map, - test_successful_connection_with_complex_claim_as_a_list, - test_successful_connection_with_complex_claim_as_a_binary, - test_successful_connection_with_keycloak_token, - test_successful_connection_with_algorithm_restriction, - test_successful_token_refresh - ]}, + [{happy_path, [], [ + test_successful_connection_with_a_full_permission_token_and_all_defaults, + test_successful_connection_with_a_full_permission_token_and_explicitly_configured_vhost, + test_successful_connection_with_simple_strings_for_aud_and_scope, + test_successful_connection_with_complex_claim_as_a_map, + test_successful_connection_with_complex_claim_as_a_list, + test_successful_connection_with_complex_claim_as_a_binary, + test_successful_connection_with_keycloak_token, + test_successful_connection_with_algorithm_restriction, + test_successful_token_refresh + ]}, {unhappy_path, [], [ - test_failed_connection_with_expired_token, - test_failed_connection_with_a_non_token, - test_failed_connection_with_a_token_with_insufficient_vhost_permission, - test_failed_connection_with_a_token_with_insufficient_resource_permission, - test_failed_connection_with_algorithm_restriction, - test_failed_token_refresh_case1, - test_failed_token_refresh_case2 - ]}, - {no_peer_verification, [], [ + test_failed_connection_with_expired_token, + test_failed_connection_with_a_non_token, + test_failed_connection_with_a_token_with_insufficient_vhost_permission, + test_failed_connection_with_a_token_with_insufficient_resource_permission, + test_failed_connection_with_algorithm_restriction, + test_failed_token_refresh_case1, + test_failed_token_refresh_case2 + ]}, + {no_peer_verification, [], [ {group, happy_path}, {group, unhappy_path} - ]}, - {multi_resource, [], [ - test_m_successful_connection, - test_m_failed_connection_due_to_missing_key - ]} + ]}, + {verify_signing_keys, [], [ + {with_oauth_providers_A_B_and_C, [], [ + {with_default_oauth_provider_B, [], [ + {with_oauth_provider_A_with_jwks_with_one_signing_key, [], [ + {with_resource_servers_rabbitmq1_with_oauth_provider_A, [], [ + test_successful_connection_for_rabbitmq1_audience_signed_by_provider_A, + {without_kid, [], [ + test_unsuccessful_connection_for_rabbitmq1_signed_by_provider_A, + {with_oauth_providers_A_with_default_key, [], [ + test_successful_connection_for_rabbitmq1_audience_signed_by_provider_A + ]} + ]} + ]} + ]}, + {with_oauth_provider_B_with_one_static_key_and_jwks_with_two_signing_keys, [], [ + {with_resource_servers_rabbitmq2, [], [ + test_successful_connection_for_rabbitmq2_audience_signed_by_provider_B_with_static_key, + test_successful_connection_for_rabbitmq2_audience_signed_by_provider_B_with_jwks_key_1, + test_successful_connection_for_rabbitmq2_audience_signed_by_provider_B_with_jwks_key_2, + {without_kid, [], [ + test_unsuccessful_connection_for_rabbitmq2_signed_by_provider_B_with_static_key, + {with_oauth_providers_B_with_default_key_static_key, [], [ + test_successful_connection_for_rabbitmq2_audience_signed_by_provider_B_with_static_key + ]} + ]} + ]}, + {with_oauth_provider_C_with_two_static_keys, [], [ + {with_resource_servers_rabbitmq3_with_oauth_provider_C, [], [ + test_successful_connection_for_rabbitmq3_audience_signed_by_provider_C_with_static_key_1, + test_successful_connection_for_rabbitmq3_audience_signed_by_provider_C_with_static_key_2, + {without_kid, [], [ + test_unsuccessful_connection_for_rabbitmq3_audience_signed_by_provider_C_with_static_key_1, + {with_oauth_providers_C_with_default_key_static_key_1, [], [ + test_successful_connection_for_rabbitmq3_audience_signed_by_provider_C_with_static_key_1 + ]} + ]} + ]} + ]} + ]} + ]} + + ]}, + {with_root_oauth_provider_with_two_static_keys_and_one_jwks_key, [], [ + {with_resource_server_rabbitmq, [], [ + test_successful_connection_for_rabbitmq_audience_signed_by_root_oauth_provider_with_static_key_1, + test_successful_connection_for_rabbitmq_audience_signed_by_root_oauth_provider_with_static_key_2, + test_successful_connection_for_rabbitmq_audience_signed_by_root_oauth_provider_with_jwks_key, + {without_kid, [], [ + test_unsuccessful_connection_for_rabbitmq_audience_signed_by_root_oauth_provider_with_static_key_1, + {with_root_oauth_provider_with_default_key_1, [], [ + test_successful_connection_for_rabbitmq_audience_signed_by_root_oauth_provider_with_static_key_1 + ]} + ]}, + {with_resource_servers_rabbitmq2, [], [ + test_successful_connection_for_rabbitmq2_audience_signed_by_root_oauth_provider_with_jwks_key, + {without_kid, [], [ + test_unsuccessful_connection_for_rabbitmq2_audience_signed_by_root_oauth_provider_with_jwks_key, + {with_root_oauth_provider_with_default_jwks_key, [], [ + test_successful_connection_for_rabbitmq2_audience_signed_by_root_oauth_provider_with_jwks_key + ]} + ]}, + {with_oauth_providers_A_B_and_C, [], [ + {with_oauth_provider_A_with_jwks_with_one_signing_key, [], [ + {with_resource_servers_rabbitmq1_with_oauth_provider_A, [], [ + test_successful_connection_for_rabbitmq1_audience_signed_by_provider_A, + test_successful_connection_for_rabbitmq2_audience_signed_by_root_oauth_provider_with_jwks_key, + test_successful_connection_for_rabbitmq_audience_signed_by_root_oauth_provider_with_static_key_1, + {without_kid, [], [ + test_unsuccessful_connection_for_rabbitmq1_signed_by_provider_A, + {with_oauth_providers_A_with_default_key, [], [ + test_successful_connection_for_rabbitmq1_audience_signed_by_provider_A + ]} + ]} + ]} + ]} + ]} + ]} + ]} + ]} + ]} ]. %% @@ -74,6 +149,7 @@ init_per_suite(Config) -> fun preconfigure_node/1, fun start_jwks_server/1, fun preconfigure_token/1 + %We fun add_vhosts/1 ]). end_per_suite(Config) -> @@ -83,60 +159,213 @@ end_per_suite(Config) -> ] ++ rabbit_ct_broker_helpers:teardown_steps()). init_per_group(no_peer_verification, Config) -> - add_vhosts(Config), KeyConfig = rabbit_ct_helpers:set_config(?config(key_config, Config), [{jwks_url, ?config(non_strict_jwks_url, Config)}, {peer_verification, verify_none}]), ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, [rabbitmq_auth_backend_oauth2, key_config, KeyConfig]), rabbit_ct_helpers:set_config(Config, {key_config, KeyConfig}); -init_per_group(multi_resource, Config) -> - add_vhosts(Config), - ResourceServersConfig = - #{ - <<"rabbitmq1">> => [ - {id, <<"rabbitmq1">>}, - {oauth_provider_id, <<"one">>} +init_per_group(without_kid, Config) -> + rabbit_ct_helpers:set_config(Config, [{include_kid, false}]); + +init_per_group(with_resource_servers_rabbitmq1_with_oauth_provider_A, Config) -> + ResourceServersConfig0 = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, + [rabbitmq_auth_backend_oauth2, resource_servers, #{}]), + Resource0 = maps:get(<<"rabbitmq1">>, ResourceServersConfig0, [{id, <<"rabbitmq1">>}]), + ResourceServersConfig1 = maps:put(<<"rabbitmq1">>, [{oauth_provider_id, <<"A">>} | Resource0], ResourceServersConfig0), + + ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, + [rabbitmq_auth_backend_oauth2, resource_servers, ResourceServersConfig1]); + +init_per_group(with_oauth_providers_A_B_and_C, Config) -> + OAuthProviders = #{ + <<"A">> => [ + {id, <<"A">>}, + {https, [{verify, verify_none}]} ], - <<"rabbitmq2">> => [ - {id, <<"rabbitmq2">>}, - {oauth_provider_id, <<"two">>} - ] - }, - OAuthProviders = - #{ - <<"one">> => [ - {issuer, strict_jwks_url(Config, "/")}, - {jwks_uri, strict_jwks_url(Config, "/jwks1")}, - {https, [{verify, verify_none}]} + <<"B">> => [ + {id, <<"B">>}, + {https, [{verify, verify_none}]} ], - <<"two">> => [ - {issuer, strict_jwks_url(Config, "/")}, - {jwks_uri, strict_jwks_url(Config, "/jwks2")}, - {https, [{verify, verify_none}]} + <<"C">> => [ + {id, <<"C">>}, + {https, [{verify, verify_none}]} ] - }, - ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, [rabbitmq_auth_backend_oauth2, resource_servers, ResourceServersConfig]), - ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, [rabbitmq_auth_backend_oauth2, oauth_providers, OAuthProviders]), + }, + ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, + [rabbitmq_auth_backend_oauth2, oauth_providers, OAuthProviders]), + Config; + +init_per_group(with_default_oauth_provider_B, Config) -> + ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, + [rabbitmq_auth_backend_oauth2, default_oauth_provider, <<"B">>]); +init_per_group(with_oauth_providers_A_with_default_key, Config) -> + {ok, OAuthProviders0} = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, + [rabbitmq_auth_backend_oauth2, oauth_providers]), + OAuthProvider = maps:get(<<"A">>, OAuthProviders0, []), + OAuthProviders1 = maps:put(<<"A">>, [ + {default_key, ?UTIL_MOD:token_key(?config(fixture_jwksA, Config))} | OAuthProvider], + OAuthProviders0), + + ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, + [rabbitmq_auth_backend_oauth2, oauth_providers, OAuthProviders1]), + Config; + +init_per_group(with_oauth_provider_A_with_jwks_with_one_signing_key, Config) -> + {ok, OAuthProviders0} = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, + [rabbitmq_auth_backend_oauth2, oauth_providers]), + OAuthProvider = maps:get(<<"A">>, OAuthProviders0, []), + OAuthProviders1 = maps:put(<<"A">>, [{jwks_uri, strict_jwks_url(Config, "/jwksA")} | OAuthProvider], + OAuthProviders0), + + ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, + [rabbitmq_auth_backend_oauth2, oauth_providers, OAuthProviders1]), + Config; +init_per_group(with_resource_servers_rabbitmq2, Config) -> + ResourceServersConfig0 = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, + [rabbitmq_auth_backend_oauth2, resource_servers, #{}]), + Resource0 = maps:get(<<"rabbitmq2">>, ResourceServersConfig0, [{id, <<"rabbitmq2">>}]), + ResourceServersConfig1 = maps:put(<<"rabbitmq2">>, Resource0, ResourceServersConfig0), + + ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, + [rabbitmq_auth_backend_oauth2, resource_servers, ResourceServersConfig1]); +init_per_group(with_oauth_providers_B_with_default_key_static_key, Config) -> + {ok, OAuthProviders0} = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, + [rabbitmq_auth_backend_oauth2, oauth_providers]), + OAuthProvider = maps:get(<<"B">>, OAuthProviders0, []), + OAuthProviders1 = maps:put(<<"B">>, [ + {default_key, ?UTIL_MOD:token_key(?config(fixture_staticB, Config))} | + proplists:delete(default_key, OAuthProvider)], + OAuthProviders0), + + ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, + [rabbitmq_auth_backend_oauth2, oauth_providers, OAuthProviders1]), + Config; +init_per_group(with_oauth_provider_C_with_two_static_keys, Config) -> + {ok, OAuthProviders0} = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, + [rabbitmq_auth_backend_oauth2, oauth_providers]), + OAuthProvider = maps:get(<<"C">>, OAuthProviders0, []), + Jwks1 = ?config(fixture_staticC_1, Config), + Jwks2 = ?config(fixture_staticC_2, Config), + SigningKeys = #{ + ?UTIL_MOD:token_key(Jwks1) => {json, Jwks1}, + ?UTIL_MOD:token_key(Jwks2) => {json, Jwks2} + }, + OAuthProviders1 = maps:put(<<"C">>, [{signing_keys, SigningKeys} | OAuthProvider], + OAuthProviders0), + + ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, + [rabbitmq_auth_backend_oauth2, oauth_providers, OAuthProviders1]), + Config; + +init_per_group(with_root_oauth_provider_with_two_static_keys_and_one_jwks_key, Config) -> + KeyConfig = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, + [rabbitmq_auth_backend_oauth2, key_config, []]), + Jwks1 = ?config(fixture_static_1, Config), + Jwks2 = ?config(fixture_static_2, Config), + SigningKeys = #{ + ?UTIL_MOD:token_key(Jwks1) => {json, Jwks1}, + ?UTIL_MOD:token_key(Jwks2) => {json, Jwks2} + }, + KeyConfig1 = [{signing_keys, SigningKeys}, + {jwks_url, strict_jwks_url(Config, "/jwks")}| KeyConfig], + ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, + [rabbitmq_auth_backend_oauth2, key_config, KeyConfig1]), + + Config; +init_per_group(with_root_oauth_provider_with_default_key_1, Config) -> + KeyConfig = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, + [rabbitmq_auth_backend_oauth2, key_config, []]), + KeyConfig1 = [{default_key, ?UTIL_MOD:token_key(?config(fixture_static_1, Config))} | KeyConfig], + ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, + [rabbitmq_auth_backend_oauth2, key_config, KeyConfig1]), + Config; +init_per_group(with_root_oauth_provider_with_default_jwks_key, Config) -> + KeyConfig = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, + [rabbitmq_auth_backend_oauth2, key_config, []]), + KeyConfig1 = [{default_key, ?UTIL_MOD:token_key(?config(fixture_jwk, Config))} | KeyConfig], + ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, + [rabbitmq_auth_backend_oauth2, key_config, KeyConfig1]), + Config; + +init_per_group(with_oauth_provider_B_with_one_static_key_and_jwks_with_two_signing_keys, Config) -> + {ok, OAuthProviders0} = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, + [rabbitmq_auth_backend_oauth2, oauth_providers]), + OAuthProvider = maps:get(<<"B">>, OAuthProviders0, []), + Jwks = ?config(fixture_staticB, Config), + SigningKeys = #{ + ?UTIL_MOD:token_key(Jwks) => {json, Jwks} + }, + OAuthProviders1 = maps:put(<<"B">>, [ + {signing_keys, SigningKeys}, + {jwks_uri, strict_jwks_url(Config, "/jwksB")} | OAuthProvider], + OAuthProviders0), + + ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, + [rabbitmq_auth_backend_oauth2, oauth_providers, OAuthProviders1]), + Config; + +init_per_group(with_resource_servers_rabbitmq3_with_oauth_provider_C, Config) -> + ResourceServersConfig0 = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, + [rabbitmq_auth_backend_oauth2, resource_servers, #{}]), + Resource0 = maps:get(<<"rabbitmq3">>, ResourceServersConfig0, [ + {id, <<"rabbitmq3">>},{oauth_provider_id, <<"C">>}]), + ResourceServersConfig1 = maps:put(<<"rabbitmq3">>, Resource0, ResourceServersConfig0), + + ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, + [rabbitmq_auth_backend_oauth2, resource_servers, ResourceServersConfig1]); + +init_per_group(with_oauth_providers_C_with_default_key_static_key_1, Config) -> + {ok, OAuthProviders0} = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, + [rabbitmq_auth_backend_oauth2, oauth_providers]), + OAuthProvider = maps:get(<<"C">>, OAuthProviders0, []), + Jwks = ?config(fixture_staticC_1, Config), + OAuthProviders1 = maps:put(<<"C">>, [ + {default_key, ?UTIL_MOD:token_key(Jwks)} | OAuthProvider], + OAuthProviders0), + + ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, + [rabbitmq_auth_backend_oauth2, oauth_providers, OAuthProviders1]), Config; init_per_group(_Group, Config) -> - add_vhosts(Config), - ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, [rabbitmq_auth_backend_oauth2, resource_server_id, ?RESOURCE_SERVER_ID]), + ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, + [rabbitmq_auth_backend_oauth2, resource_server_id, ?RESOURCE_SERVER_ID]), Config. +end_per_group(without_kid, Config) -> + rabbit_ct_helpers:delete_config(Config, include_kid); + end_per_group(no_peer_verification, Config) -> - delete_vhosts(Config), KeyConfig = rabbit_ct_helpers:set_config(?config(key_config, Config), [{jwks_url, ?config(strict_jwks_url, Config)}, {peer_verification, verify_peer}]), ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, [rabbitmq_auth_backend_oauth2, key_config, KeyConfig]), rabbit_ct_helpers:set_config(Config, {key_config, KeyConfig}); +end_per_group(with_default_oauth_provider_B, Config) -> + ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, unset_env, + [rabbitmq_auth_backend_oauth2, default_oauth_provider]); + +end_per_group(with_root_oauth_provider_with_default_key_1, Config) -> + KeyConfig = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, + [rabbitmq_auth_backend_oauth2, key_config, []]), + KeyConfig1 = proplists:delete(default_key, KeyConfig), + ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, + [rabbitmq_auth_backend_oauth2, key_config, KeyConfig1]), + Config; +end_per_group(with_root_oauth_provider_with_default_jwks_key, Config) -> + KeyConfig = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, + [rabbitmq_auth_backend_oauth2, key_config, []]), + KeyConfig1 = proplists:delete(default_key, KeyConfig), + ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, + [rabbitmq_auth_backend_oauth2, key_config, KeyConfig1]), + Config; + end_per_group(_Group, Config) -> - delete_vhosts(Config), Config. add_vhosts(Config) -> %% The broker is managed by {init,end}_per_testcase(). lists:foreach(fun(Value) -> rabbit_ct_broker_helpers:add_vhost(Config, Value) end, [<<"vhost1">>, <<"vhost2">>, <<"vhost3">>, <<"vhost4">>]). + %rabbit_ct_helpers:set_config(Config, []). delete_vhosts(Config) -> %% The broker is managed by {init,end}_per_testcase(). @@ -211,6 +440,7 @@ preconfigure_node(Config) -> [rabbit, auth_backends, [rabbit_auth_backend_oauth2]]), ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, [rabbitmq_auth_backend_oauth2, resource_server_id, ?RESOURCE_SERVER_ID]), + add_vhosts(Config), Config. start_jwks_server(Config0) -> @@ -218,6 +448,11 @@ start_jwks_server(Config0) -> Jwk1 = ?UTIL_MOD:fixture_jwk(<<"token-key-1">>), Jwk2 = ?UTIL_MOD:fixture_jwk(<<"token-key-2">>), Jwk3 = ?UTIL_MOD:fixture_jwk(<<"token-key-3">>), + Jwk4 = ?UTIL_MOD:fixture_jwk(<<"token-key-4">>), + Jwk5 = ?UTIL_MOD:fixture_jwk(<<"token-key-5">>), + Jwk6 = ?UTIL_MOD:fixture_jwk(<<"token-key-6">>), + Jwk7 = ?UTIL_MOD:fixture_jwk(<<"token-key-7">>), + Jwk8 = ?UTIL_MOD:fixture_jwk(<<"token-key-8">>), %% Assume we don't have more than 100 ports allocated for tests PortBase = rabbit_ct_broker_helpers:get_node_config(Config0, 0, tcp_ports_base), JwksServerPort = PortBase + 100, @@ -232,7 +467,10 @@ start_jwks_server(Config0) -> {ok, _} = application:ensure_all_started(cowboy), CertsDir = ?config(rmq_certsdir, Config), ok = jwks_http_app:start(JwksServerPort, CertsDir, - [ {"/jwks", [Jwk]}, + [ {"/jwksA", [Jwk]}, + {"/jwksB", [Jwk1, Jwk3]}, + {"/jwksRoot", [Jwk2]}, + {"/jwks", [Jwk]}, {"/jwks1", [Jwk1, Jwk3]}, {"/jwks2", [Jwk2]} ]), @@ -246,6 +484,14 @@ start_jwks_server(Config0) -> {non_strict_jwks_url, NonStrictJwksUrl}, {strict_jwks_url, StrictJwksUrl}, {key_config, KeyConfig}, + {fixture_static_1, Jwk7}, + {fixture_static_2, Jwk8}, + {fixture_staticB, Jwk4}, + {fixture_staticC_1, Jwk5}, + {fixture_staticC_2, Jwk6}, + {fixture_jwksB_1, Jwk1}, + {fixture_jwksB_2, Jwk3}, + {fixture_jwksA, Jwk}, {fixture_jwk, Jwk}, {fixture_jwks_1, [Jwk1, Jwk3]}, {fixture_jwks_2, [Jwk2]} @@ -277,12 +523,13 @@ generate_valid_token(Config, Scopes, Audience) -> end, generate_valid_token(Config, Jwk, Scopes, Audience). -generate_valid_token(_Config, Jwk, Scopes, Audience) -> +generate_valid_token(Config, Jwk, Scopes, Audience) -> Token = case Audience of undefined -> ?UTIL_MOD:fixture_token_with_scopes(Scopes); DefinedAudience -> maps:put(<<"aud">>, DefinedAudience, ?UTIL_MOD:fixture_token_with_scopes(Scopes)) end, - ?UTIL_MOD:sign_token_hs(Token, Jwk). + IncludeKid = rabbit_ct_helpers:get_config(Config, include_kid, true), + ?UTIL_MOD:sign_token_hs(Token, Jwk, IncludeKid). generate_valid_token_with_extra_fields(Config, ExtraFields) -> Jwk = case rabbit_ct_helpers:get_config(Config, fixture_jwk) of @@ -290,7 +537,7 @@ generate_valid_token_with_extra_fields(Config, ExtraFields) -> Value -> Value end, Token = maps:merge(?UTIL_MOD:fixture_token_with_scopes([]), ExtraFields), - ?UTIL_MOD:sign_token_hs(Token, Jwk). + ?UTIL_MOD:sign_token_hs(Token, Jwk, rabbit_ct_helpers:get_config(Config, include_kid, true)). generate_expired_token(Config) -> generate_expired_token(Config, ?UTIL_MOD:full_permission_scopes()). @@ -300,7 +547,8 @@ generate_expired_token(Config, Scopes) -> undefined -> ?UTIL_MOD:fixture_jwk(); Value -> Value end, - ?UTIL_MOD:sign_token_hs(?UTIL_MOD:expired_token_with_scopes(Scopes), Jwk). + ?UTIL_MOD:sign_token_hs(?UTIL_MOD:expired_token_with_scopes(Scopes), Jwk, + rabbit_ct_helpers:get_config(Config, include_kid, true)). generate_expirable_token(Config, Seconds) -> generate_expirable_token(Config, ?UTIL_MOD:full_permission_scopes(), Seconds). @@ -311,7 +559,8 @@ generate_expirable_token(Config, Scopes, Seconds) -> Value -> Value end, Expiration = os:system_time(seconds) + Seconds, - ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_scopes_and_expiration(Scopes, Expiration), Jwk). + ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_scopes_and_expiration(Scopes, Expiration), + Jwk, rabbit_ct_helpers:get_config(Config, include_kid, true)). preconfigure_token(Config) -> Token = generate_valid_token(Config), @@ -321,7 +570,117 @@ preconfigure_token(Config) -> %% %% Test Cases %% - +test_successful_connection_for_rabbitmq1_audience_signed_by_provider_A(Config) -> + Jwks = ?config(fixture_jwksA, Config), + Scopes = <<"rabbitmq1.configure:*/* rabbitmq1.write:*/* rabbitmq1.read:*/*">>, + Audience = <<"rabbitmq1">>, + test_queue_declare(Config, Jwks, Scopes, Audience). +test_unsuccessful_connection_for_rabbitmq1_signed_by_provider_A(Config) -> + Jwks = ?config(fixture_jwksA, Config), + Scopes = <<"rabbitmq1.configure:*/* rabbitmq1.write:*/* rabbitmq1.read:*/*">>, + Audience = <<"rabbitmq1">>, + {_Alg, Token} = generate_valid_token( + Config, + Jwks, + Scopes, + [Audience] + ), + ?assertMatch({error, {auth_failure, _}}, + open_unmanaged_connection(Config, 0, <<"vhost1">>, <<"username">>, Token)). + +test_successful_connection_for_rabbitmq2_audience_signed_by_provider_B_with_static_key(Config) -> + Jwks = ?config(fixture_staticB, Config), + Scopes = <<"rabbitmq2.configure:*/* rabbitmq2.write:*/* rabbitmq2.read:*/*">>, + Audience = <<"rabbitmq2">>, + test_queue_declare(Config, Jwks, Scopes, Audience). +test_successful_connection_for_rabbitmq2_audience_signed_by_provider_B_with_jwks_key_1(Config) -> + Jwks = ?config(fixture_jwksB_1, Config), + Scopes = <<"rabbitmq2.configure:*/* rabbitmq2.write:*/* rabbitmq2.read:*/*">>, + Audience = <<"rabbitmq2">>, + test_queue_declare(Config, Jwks, Scopes, Audience). +test_successful_connection_for_rabbitmq2_audience_signed_by_provider_B_with_jwks_key_2(Config) -> + Jwks = ?config(fixture_jwksB_2, Config), + Scopes = <<"rabbitmq2.configure:*/* rabbitmq2.write:*/* rabbitmq2.read:*/*">>, + Audience = <<"rabbitmq2">>, + test_queue_declare(Config, Jwks, Scopes, Audience). +test_successful_connection_for_rabbitmq3_audience_signed_by_provider_C_with_static_key_1(Config) -> + Jwks = ?config(fixture_staticC_1, Config), + Scopes = <<"rabbitmq3.configure:*/* rabbitmq3.write:*/* rabbitmq3.read:*/*">>, + Audience = <<"rabbitmq3">>, + test_queue_declare(Config, Jwks, Scopes, Audience). +test_successful_connection_for_rabbitmq3_audience_signed_by_provider_C_with_static_key_2(Config) -> + Jwks = ?config(fixture_staticC_2, Config), + Scopes = <<"rabbitmq3.configure:*/* rabbitmq3.write:*/* rabbitmq3.read:*/*">>, + Audience = <<"rabbitmq3">>, + test_queue_declare(Config, Jwks, Scopes, Audience). +test_successful_connection_for_rabbitmq_audience_signed_by_root_oauth_provider_with_static_key_1(Config) -> + Jwks = ?config(fixture_static_1, Config), + Scopes = <<"rabbitmq.configure:*/* rabbitmq.write:*/* rabbitmq.read:*/*">>, + Audience = <<"rabbitmq">>, + test_queue_declare(Config, Jwks, Scopes, Audience). +test_successful_connection_for_rabbitmq_audience_signed_by_root_oauth_provider_with_static_key_2(Config) -> + Jwks = ?config(fixture_static_2, Config), + Scopes = <<"rabbitmq.configure:*/* rabbitmq.write:*/* rabbitmq.read:*/*">>, + Audience = <<"rabbitmq">>, + test_queue_declare(Config, Jwks, Scopes, Audience). +test_successful_connection_for_rabbitmq_audience_signed_by_root_oauth_provider_with_jwks_key(Config) -> + Jwks = ?config(fixture_jwk, Config), + Scopes = <<"rabbitmq.configure:*/* rabbitmq.write:*/* rabbitmq.read:*/*">>, + Audience = <<"rabbitmq">>, + test_queue_declare(Config, Jwks, Scopes, Audience). +test_successful_connection_for_rabbitmq2_audience_signed_by_root_oauth_provider_with_jwks_key(Config) -> + Jwks = ?config(fixture_jwk, Config), + Scopes = <<"rabbitmq2.configure:*/* rabbitmq2.write:*/* rabbitmq2.read:*/*">>, + Audience = <<"rabbitmq2">>, + test_queue_declare(Config, Jwks, Scopes, Audience). +test_unsuccessful_connection_for_rabbitmq2_audience_signed_by_root_oauth_provider_with_jwks_key(Config) -> + Jwks = ?config(fixture_jwk, Config), + Scopes = <<"rabbitmq2.configure:*/* rabbitmq2.write:*/* rabbitmq2.read:*/*">>, + Audience = <<"rabbitmq2">>, + {_Alg, Token} = generate_valid_token( + Config, + Jwks, + Scopes, + [Audience] + ), + ?assertMatch({error, {auth_failure, _}}, + open_unmanaged_connection(Config, 0, <<"vhost1">>, <<"username">>, Token)). +test_unsuccessful_connection_for_rabbitmq2_signed_by_provider_B_with_static_key(Config) -> + Jwks = ?config(fixture_staticB, Config), + Scopes = <<"rabbitmq2.configure:*/* rabbitmq2.write:*/* rabbitmq2.read:*/*">>, + Audience = <<"rabbitmq2">>, + {_Alg, Token} = generate_valid_token( + Config, + Jwks, + Scopes, + [Audience] + ), + ?assertMatch({error, {auth_failure, _}}, + open_unmanaged_connection(Config, 0, <<"vhost1">>, <<"username">>, Token)). +test_unsuccessful_connection_for_rabbitmq3_audience_signed_by_provider_C_with_static_key_1(Config) -> + Jwks = ?config(fixture_staticC_1, Config), + Scopes = <<"rabbitmq3.configure:*/* rabbitmq3.write:*/* rabbitmq3.read:*/*">>, + Audience = <<"rabbitmq3">>, + {_Alg, Token} = generate_valid_token( + Config, + Jwks, + Scopes, + [Audience] + ), + ?assertMatch({error, {auth_failure, _}}, + open_unmanaged_connection(Config, 0, <<"vhost1">>, <<"username">>, Token)). +test_unsuccessful_connection_for_rabbitmq_audience_signed_by_root_oauth_provider_with_static_key_1(Config) -> + Jwks = ?config(fixture_static_1, Config), + Scopes = <<"rabbitmq.configure:*/* rabbitmq.write:*/* rabbitmq.read:*/*">>, + Audience = <<"rabbitmq">>, + {_Alg, Token} = generate_valid_token( + Config, + Jwks, + Scopes, + [Audience] + ), + ?assertMatch({error, {auth_failure, _}}, + open_unmanaged_connection(Config, 0, <<"vhost1">>, <<"username">>, Token)). test_successful_connection_with_a_full_permission_token_and_all_defaults(Config) -> {_Algo, Token} = rabbit_ct_helpers:get_config(Config, fixture_jwt), verify_queue_declare_with_token(Config, Token). @@ -333,41 +692,45 @@ verify_queue_declare_with_token(Config, Token) -> amqp_channel:call(Ch, #'queue.declare'{exclusive = true}), close_connection_and_channel(Conn, Ch). -test_m_successful_connection(Config) -> +test_queue_declare(Config, Jwks, Scopes, Audience) -> {_Alg, Token1} = generate_valid_token( Config, - lists:nth(1, ?config(fixture_jwks_1, Config)), - <<"rabbitmq1.configure:*/* rabbitmq1.write:*/* rabbitmq1.read:*/*">>, - [<<"rabbitmq1">>] + Jwks, + Scopes, + [Audience] ), - verify_queue_declare_with_token(Config, Token1), + verify_queue_declare_with_token(Config, Token1). - {_Alg2, Token2} = generate_valid_token( - Config, - lists:nth(2, ?config(fixture_jwks_1, Config)), +c(Config) -> + TestCases = [ + {?config(fixture_jwk, Config), <<"rabbitmq1.configure:*/* rabbitmq1.write:*/* rabbitmq1.read:*/*">>, - [<<"rabbitmq1">>] - ), - verify_queue_declare_with_token(Config, Token2), - - {_Alg3, Token3} = generate_valid_token( - Config, - lists:nth(1, ?config(fixture_jwks_2, Config)), + <<"rabbitmq1">>}, + {?config(fixture_jwk, Config), <<"rabbitmq2.configure:*/* rabbitmq2.write:*/* rabbitmq2.read:*/*">>, - [<<"rabbitmq2">>] - ), - verify_queue_declare_with_token(Config, Token3). + <<"rabbitmq2">>}, + {?config(fixture_jwk, Config), + <<"rabbitmq1.configure:*/* rabbitmq1.write:*/* rabbitmq1.read:*/*">>, + <<"rabbitmq1">>} + ], + [test_queue_declare(Config, Jwks, Scopes, Audience) || + {Jwks, Scopes, Audience} <- TestCases]. -test_m_failed_connection_due_to_missing_key(Config) -> - {_Alg, Token} = generate_valid_token( - Config, - lists:nth(1, ?config(fixture_jwks_2, Config)), %% used signing key for rabbitmq2 instead of rabbitmq1 one +test_successful_queue_declaration_using_multiple_keys_and_audiences(Config) -> + TestCases = [ + {lists:nth(1, ?config(fixture_jwks_1, Config)), <<"rabbitmq1.configure:*/* rabbitmq1.write:*/* rabbitmq1.read:*/*">>, - [<<"rabbitmq1">>] - ), - ?assertMatch({error, {auth_failure, _}}, - open_unmanaged_connection(Config, 0, <<"username">>, Token)). + <<"rabbitmq1">>}, + {lists:nth(2, ?config(fixture_jwks_1, Config)), + <<"rabbitmq1.configure:*/* rabbitmq1.write:*/* rabbitmq1.read:*/*">>, + <<"rabbitmq1">>}, + {lists:nth(1, ?config(fixture_jwks_2, Config)), + <<"rabbitmq2.configure:*/* rabbitmq2.write:*/* rabbitmq2.read:*/*">>, + <<"rabbitmq2">>} + ], + [test_queue_declare(Config, Jwks, Scopes, Audience) || + {Jwks, Scopes, Audience} <- TestCases]. test_successful_connection_with_a_full_permission_token_and_explicitly_configured_vhost(Config) -> diff --git a/deps/rabbitmq_auth_backend_oauth2/test/rabbit_auth_backend_oauth2_test_util.erl b/deps/rabbitmq_auth_backend_oauth2/test/rabbit_auth_backend_oauth2_test_util.erl index cea238a1e857..07fefd9c2c09 100644 --- a/deps/rabbitmq_auth_backend_oauth2/test/rabbit_auth_backend_oauth2_test_util.erl +++ b/deps/rabbitmq_auth_backend_oauth2/test/rabbit_auth_backend_oauth2_test_util.erl @@ -14,15 +14,27 @@ %% API %% -sign_token_hs(Token, #{<<"kid">> := TokenKey} = Jwk) -> - sign_token_hs(Token, Jwk, TokenKey). +sign_token_hs(Token, Jwk) -> + sign_token_hs(Token, Jwk, true). -sign_token_hs(Token, Jwk, TokenKey) -> - Jws = #{ +sign_token_hs(Token, #{<<"kid">> := TokenKey} = Jwk, IncludeKid) -> + sign_token_hs(Token, Jwk, TokenKey, IncludeKid). + +%%sign_token_hs(Token, Jwk, TokenKey) -> +%% sign_token_hs(Token, Jwk, TokenKey, true). + +sign_token_hs(Token, Jwk, TokenKey, IncludeKid) -> + Jws0 = #{ <<"alg">> => <<"HS256">>, <<"kid">> => TokenKey }, - sign_token(Token, Jwk, Jws). + case IncludeKid of + true -> + Jws = maps:put(<<"kid">>, TokenKey, Jws0), + sign_token(Token, Jwk, Jws); + false -> + sign_token_no_kid(Token, Jwk) + end. sign_token_rsa(Token, Jwk, TokenKey) -> Jws = #{ @@ -39,12 +51,15 @@ sign_token(Token, Jwk, Jws) -> Signed = jose_jwt:sign(Jwk, Jws, Token), jose_jws:compact(Signed). +token_key(#{<<"kid">> := TokenKey} = _Token) -> + TokenKey. + fixture_jwk() -> - fixture_jwk(<<"token-key">>). + fixture_jwk(<<"token-key">>). fixture_jwk(TokenKey) -> fixture_jwk(TokenKey, <<"dG9rZW5rZXk">>). - + fixture_jwk(TokenKey, K) -> #{<<"alg">> => <<"HS256">>, <<"k">> => K, diff --git a/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_config_SUITE.erl b/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_config_SUITE.erl index b94f743baba0..10872b6e842c 100644 --- a/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_config_SUITE.erl +++ b/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_config_SUITE.erl @@ -18,610 +18,860 @@ -define(AUTH_PORT, 8000). -all() -> - [ - {group, with_resource_server_id}, - {group, without_resource_server_id}, - {group, with_resource_servers}, - {group, with_resource_servers_and_resource_server_id}, - {group, inheritance_group} - - ]. -groups() -> - [ - {with_rabbitmq_node, [], [ - add_signing_keys_for_top_specific_resource_server, - add_signing_keys_for_top_level_resource_server, - - replace_signing_keys_for_top_level_resource_server, - replace_signing_keys_for_specific_resource_server - ] - }, - - {with_resource_server_id, [], [ - get_default_resource_server_id, - get_allowed_resource_server_ids_returns_resource_server_id, - find_audience_in_resource_server_ids_found_resource_server_id, - get_oauth_provider_should_fail, - {with_jwks_url, [], [ - get_oauth_provider_should_return_root_oauth_provider_with_jwks_uri, - {with_oauth_providers_A_with_jwks_uri, [], [ - get_oauth_provider_should_return_root_oauth_provider_with_jwks_uri, - {with_default_oauth_provider_A, [], [ - get_oauth_provider_should_return_oauth_provider_A_with_jwks_uri - ] - } - ] - }, - {with_oauth_providers_A_B_with_jwks_uri, [], [ - get_oauth_provider_should_return_root_oauth_provider_with_jwks_uri, - {with_default_oauth_provider_B, [], [ - get_oauth_provider_should_return_oauth_provider_B_with_jwks_uri - ] - } - ] - } - ] - }, - {with_oauth_providers_A_with_jwks_uri, [], [ - get_oauth_provider_should_fail, - {with_default_oauth_provider_A, [], [ - get_oauth_provider_should_return_oauth_provider_A_with_jwks_uri - ] - } - ] - }, - {with_issuer, [], [ - get_oauth_provider_should_return_root_oauth_provider_with_all_discovered_endpoints, - {with_oauth_providers_A_with_issuer, [], [ - get_oauth_provider_should_return_root_oauth_provider_with_all_discovered_endpoints, - {with_default_oauth_provider_A, [], [ - get_oauth_provider_should_return_oauth_provider_A_with_all_discovered_endpoints - ] - } - ] - }, - {with_oauth_providers_A_B_with_issuer, [], [ - get_oauth_provider_should_return_root_oauth_provider_with_all_discovered_endpoints, - {with_default_oauth_provider_B, [], [ - get_oauth_provider_should_return_oauth_provider_B_with_all_discovered_endpoints - ] - } - ] - } - ] - } - ] - }, - {without_resource_server_id, [], [ - get_default_resource_server_id_returns_error, - get_allowed_resource_server_ids_returns_empty_list - ] - }, - {with_resource_servers, [], [ - get_allowed_resource_server_ids_returns_resource_servers_ids, - find_audience_in_resource_server_ids_found_one_resource_servers, - index_resource_servers_by_id_else_by_key, - {with_jwks_url, [], [ - get_oauth_provider_for_both_resources_should_return_root_oauth_provider, - {with_oauth_providers_A_with_jwks_uri, [], [ - {with_default_oauth_provider_A, [], [ - get_oauth_provider_for_both_resources_should_return_oauth_provider_A - ] - } - ] - }, - {with_different_oauth_provider_for_each_resource, [], [ - {with_oauth_providers_A_B_with_jwks_uri, [], [ +all() -> [ + {group, with_rabbitmq_node}, + {group, with_resource_server_id}, + {group, without_resource_server_id}, + {group, with_resource_servers}, + {group, with_resource_servers_and_resource_server_id}, + {group, inheritance_group} +]. +groups() -> [ + {with_rabbitmq_node, [], [ + add_signing_keys_for_specific_oauth_provider, + add_signing_keys_for_root_oauth_provider, + + replace_signing_keys_for_root_oauth_provider, + replace_signing_keys_for_specific_oauth_provider, + {with_root_static_signing_keys, [], [ + replace_merge_root_static_keys_with_newly_added_keys, + replace_override_root_static_keys_with_newly_added_keys + ]}, + {with_static_signing_keys_for_specific_oauth_provider, [], [ + replace_merge_static_keys_with_newly_added_keys, + replace_override_static_keys_with_newly_added_keys + ]} + ]}, + {with_resource_server_id, [], [ + get_default_resource_server_id, + get_allowed_resource_server_ids_returns_resource_server_id, + get_resource_server_id_for_rabbit_audience_returns_rabbit, + get_resource_server_id_for_none_audience_should_fail, + get_resource_server_id_for_unknown_audience_should_fail, + {with_verify_aud_false, [], [ + get_resource_server_id_for_rabbit_audience_returns_rabbit, + get_resource_server_id_for_none_audience_returns_rabbit, + get_resource_server_id_for_unknown_audience_returns_rabbit + ]}, + find_audience_in_resource_server_ids_found_resource_server_id, + get_oauth_provider_root_with_jwks_uri_should_fail, + get_default_key_should_fail, + {with_default_key, [], [ + get_default_key + ]}, + {with_static_signing_keys, [], [ + get_signing_keys + ]}, + {with_static_signing_keys_for_oauth_provider_A, [], [ + get_signing_keys_for_oauth_provider_A + ]}, + get_algorithms_should_return_undefined, + {with_algorithms, [], [ + get_algorithms + ]}, + {with_jwks_url, [], [ + get_oauth_provider_should_return_root_oauth_provider_with_jwks_uri, + {with_oauth_providers_A_with_jwks_uri, [], [ + get_oauth_provider_should_return_root_oauth_provider_with_jwks_uri, + {with_default_oauth_provider_A, [], [ + get_oauth_provider_should_return_oauth_provider_A_with_jwks_uri + ]} + ]}, + {with_oauth_providers_A_B_with_jwks_uri, [], [ + get_default_key_for_provider_A_should_fail, + {with_default_key, [], [ + get_default_key_for_provider_A_should_fail + ]}, + {with_default_key_for_provider_A, [], [ + get_default_key_for_provider_A + ]}, + get_algorithms_for_provider_A_should_return_undefined, + {with_algorithms_for_provider_A, [], [ + get_algorithms_for_provider_A + ]}, + get_oauth_provider_should_return_root_oauth_provider_with_jwks_uri, + {with_default_oauth_provider_B, [], [ + get_oauth_provider_should_return_oauth_provider_B_with_jwks_uri + ]} + ]} + ]}, + {with_oauth_providers_A_with_jwks_uri, [], [ + get_oauth_provider_root_with_jwks_uri_should_fail, + {with_default_oauth_provider_A, [], [ + get_oauth_provider_should_return_oauth_provider_A_with_jwks_uri + ]} + ]}, + {with_issuer, [], [ + get_oauth_provider_should_return_root_oauth_provider_with_all_discovered_endpoints, + {with_oauth_providers_A_with_issuer, [], [ + get_oauth_provider_should_return_root_oauth_provider_with_all_discovered_endpoints, + {with_default_oauth_provider_A, [], [ + get_oauth_provider_should_return_oauth_provider_A_with_all_discovered_endpoints + ]} + ]}, + {with_oauth_providers_A_B_with_issuer, [], [ + get_oauth_provider_should_return_root_oauth_provider_with_all_discovered_endpoints, + {with_default_oauth_provider_B, [], [ + get_oauth_provider_should_return_oauth_provider_B_with_all_discovered_endpoints + ]} + ]} + ]} + ]}, + {without_resource_server_id, [], [ + get_default_resource_server_id_returns_error, + get_allowed_resource_server_ids_returns_empty_list + ]}, + {with_resource_servers, [], [ + get_allowed_resource_server_ids_returns_resource_servers_ids, + find_audience_in_resource_server_ids_found_one_resource_servers, + index_resource_servers_by_id_else_by_key, + is_verify_aud_for_resource_two_returns_true, + {with_verify_aud_false_for_resource_two, [], [ + is_verify_aud_for_resource_one_returns_true, + is_verify_aud_for_resource_two_returns_false + ]}, + {with_jwks_url, [], [ + get_oauth_provider_for_both_resources_should_return_root_oauth_provider, + {with_oauth_providers_A_with_jwks_uri, [], [ + {with_default_oauth_provider_A, [], [ + get_oauth_provider_for_both_resources_should_return_oauth_provider_A + ]} + ]}, + {with_different_oauth_provider_for_each_resource, [], [ + {with_oauth_providers_A_B_with_jwks_uri, [], [ get_oauth_provider_for_resource_one_should_return_oauth_provider_A, get_oauth_provider_for_resource_two_should_return_oauth_provider_B - ]} - ] - } - ] - } - ] - }, - {with_resource_servers_and_resource_server_id, [], [ - get_allowed_resource_server_ids_returns_all_resource_servers_ids, - find_audience_in_resource_server_ids_found_resource_server_id, - find_audience_in_resource_server_ids_found_one_resource_servers, - find_audience_in_resource_server_ids_using_binary_audience - - ] - }, - - {inheritance_group, [], [ - get_key_config, - get_additional_scopes_key, - get_additional_scopes_key_when_not_defined, - is_verify_aud, - is_verify_aud_when_is_false, - get_default_preferred_username_claims, - get_preferred_username_claims, - get_scope_prefix, - get_scope_prefix_when_not_defined, - get_resource_server_type, - get_resource_server_type_when_not_defined, - has_scope_aliases, - has_scope_aliases_when_not_defined, - get_scope_aliases - ] - } - - ]. + ]} + ]} + ]} + ]}, + {with_resource_servers_and_resource_server_id, [], [ + get_allowed_resource_server_ids_returns_all_resource_servers_ids, + find_audience_in_resource_server_ids_found_resource_server_id, + find_audience_in_resource_server_ids_found_one_resource_servers, + find_audience_in_resource_server_ids_using_binary_audience + ]}, + + {inheritance_group, [], [ + get_additional_scopes_key, + get_additional_scopes_key_when_not_defined, + is_verify_aud, + is_verify_aud_when_is_false, + get_default_preferred_username_claims, + get_preferred_username_claims, + get_scope_prefix, + get_scope_prefix_when_not_defined, + get_resource_server_type, + get_resource_server_type_when_not_defined, + has_scope_aliases, + has_scope_aliases_when_not_defined, + get_scope_aliases + ]} +]. init_per_suite(Config) -> - rabbit_ct_helpers:log_environment(), - rabbit_ct_helpers:run_setup_steps(Config). + rabbit_ct_helpers:log_environment(), + rabbit_ct_helpers:run_setup_steps(Config). end_per_suite(Config) -> - rabbit_ct_helpers:run_teardown_steps(Config). + rabbit_ct_helpers:run_teardown_steps(Config). init_per_group(with_rabbitmq_node, Config) -> - Config1 = rabbit_ct_helpers:set_config(Config, [ - {rmq_nodename_suffix, with_rabbitmq_node}, - {rmq_nodes_count, 1} - ]), - rabbit_ct_helpers:run_steps(Config1, rabbit_ct_broker_helpers:setup_steps()); - + Config1 = rabbit_ct_helpers:set_config(Config, [ + {rmq_nodename_suffix, with_rabbitmq_node}, + {rmq_nodes_count, 1} + ]), + rabbit_ct_helpers:run_steps(Config1, rabbit_ct_broker_helpers:setup_steps()); +init_per_group(with_default_key, Config) -> + KeyConfig = application:get_env(rabbitmq_auth_backend_oauth2, key_config, []), + application:set_env(rabbitmq_auth_backend_oauth2, key_config, + proplists:delete(default_key, KeyConfig) ++ [{default_key,<<"default-key">>}]), + Config; +init_per_group(with_root_static_signing_keys, Config) -> + KeyConfig = call_get_env(Config, key_config, []), + SigningKeys = #{ + <<"mykey-root-1">> => <<"some key root-1">>, + <<"mykey-root-2">> => <<"some key root-2">> + }, + call_set_env(Config, key_config, + proplists:delete(default_key, KeyConfig) ++ [{signing_keys,SigningKeys}]), + Config; +init_per_group(with_static_signing_keys_for_specific_oauth_provider, Config) -> + OAuthProviders = call_get_env(Config, oauth_providers, #{}), + OAuthProvider = maps:get(<<"A">>, OAuthProviders, []), + SigningKeys = #{ + <<"mykey-root-1">> => <<"some key root-1">>, + <<"mykey-root-2">> => <<"some key root-2">> + }, + OAuthProvider1 = proplists:delete(signing_keys, OAuthProvider) ++ [{signing_keys, SigningKeys}], + + call_set_env(Config, oauth_providers, maps:put(<<"A">>, OAuthProvider1, OAuthProviders)), + Config; + +init_per_group(with_default_key_for_provider_A, Config) -> + OAuthProviders = application:get_env(rabbitmq_auth_backend_oauth2, oauth_providers, #{}), + OAuthProvider = maps:get(<<"A">>, OAuthProviders, []), + application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, maps:put(<<"A">>, + proplists:delete(default_key, OAuthProvider) ++ [{default_key,<<"A-default-key">>}], + OAuthProviders)), + Config; +init_per_group(with_static_signing_keys, Config) -> + KeyConfig = application:get_env(rabbitmq_auth_backend_oauth2, key_config, []), + SigningKeys = #{<<"mykey-1-1">> => <<"some key 1-1">>, + <<"mykey-1-2">> => <<"some key 1-2">>}, + application:set_env(rabbitmq_auth_backend_oauth2, key_config, + proplists:delete(signing_keys, KeyConfig) ++ [{signing_keys, SigningKeys}]), + Config; +init_per_group(with_static_signing_keys_for_oauth_provider_A, Config) -> + OAuthProviders = application:get_env(rabbitmq_auth_backend_oauth2, oauth_providers, #{}), + OAuthProvider = maps:get(<<"A">>, OAuthProviders, []), + SigningKeys = #{<<"A-mykey-1-1">> => <<"A-some key 1-1">>, + <<"A-mykey-1-2">> => <<"A-some key 1-2">>}, + + OAuthProvider0 = proplists:delete(signing_keys, OAuthProvider) ++ + [{signing_keys, SigningKeys}], + application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, + maps:put(<<"A">>, OAuthProvider0, OAuthProviders)), + Config; init_per_group(with_jwks_url, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, key_config, [{jwks_url,build_url_to_oauth_provider(<<"/keys">>)}]), - Config; + KeyConfig = application:get_env(rabbitmq_auth_backend_oauth2, key_config, []), + application:set_env(rabbitmq_auth_backend_oauth2, key_config, KeyConfig + ++ [{jwks_url,build_url_to_oauth_provider(<<"/keys">>)}]), + [{key_config_before_group_with_jwks_url, KeyConfig} | Config]; init_per_group(with_issuer, Config) -> - {ok, _} = application:ensure_all_started(inets), - {ok, _} = application:ensure_all_started(ssl), - application:ensure_all_started(cowboy), - CertsDir = ?config(rmq_certsdir, Config), - CaCertFile = filename:join([CertsDir, "testca", "cacert.pem"]), - SslOptions = ssl_options(verify_peer, false, CaCertFile), + {ok, _} = application:ensure_all_started(inets), + {ok, _} = application:ensure_all_started(ssl), + application:ensure_all_started(cowboy), + CertsDir = ?config(rmq_certsdir, Config), + CaCertFile = filename:join([CertsDir, "testca", "cacert.pem"]), + SslOptions = ssl_options(verify_peer, false, CaCertFile), - HttpOauthServerExpectations = get_openid_configuration_expectations(), - ListOfExpectations = maps:values(proplists:to_map(HttpOauthServerExpectations)), + HttpOauthServerExpectations = get_openid_configuration_expectations(), + ListOfExpectations = maps:values(proplists:to_map(HttpOauthServerExpectations)), - start_https_oauth_server(?AUTH_PORT, CertsDir, ListOfExpectations), - application:set_env(rabbitmq_auth_backend_oauth2, use_global_locks, false), - application:set_env(rabbitmq_auth_backend_oauth2, issuer, build_url_to_oauth_provider(<<"/">>)), - application:set_env(rabbitmq_auth_backend_oauth2, key_config, SslOptions), + start_https_oauth_server(?AUTH_PORT, CertsDir, ListOfExpectations), + application:set_env(rabbitmq_auth_backend_oauth2, use_global_locks, false), + application:set_env(rabbitmq_auth_backend_oauth2, issuer, build_url_to_oauth_provider(<<"/">>)), + KeyConfig = application:get_env(rabbitmq_auth_backend_oauth2, key_config, []), + application:set_env(rabbitmq_auth_backend_oauth2, key_config, KeyConfig ++ SslOptions), - [{ssl_options, SslOptions} | Config]; + [{key_config_before_group_with_issuer, KeyConfig}, {ssl_options, SslOptions} | Config]; init_per_group(with_oauth_providers_A_with_jwks_uri, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, + application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, #{<<"A">> => [ - {issuer,build_url_to_oauth_provider(<<"/A">>) }, - {jwks_uri,build_url_to_oauth_provider(<<"/A/keys">>) } - ] } ), - Config; + {issuer,build_url_to_oauth_provider(<<"/A">>) }, + {jwks_uri,build_url_to_oauth_provider(<<"/A/keys">>) } + ] } ), + Config; init_per_group(with_oauth_providers_A_with_issuer, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, + application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, #{<<"A">> => [ - {issuer,build_url_to_oauth_provider(<<"/A">>) }, - {https, ?config(ssl_options, Config)} - ] } ), - Config; + {issuer,build_url_to_oauth_provider(<<"/A">>) }, + {https, ?config(ssl_options, Config)} + ] } ), + Config; init_per_group(with_oauth_providers_A_B_with_jwks_uri, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, + application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, #{ <<"A">> => [ - {issuer,build_url_to_oauth_provider(<<"/A">>) }, - {jwks_uri,build_url_to_oauth_provider(<<"/A/keys">>)} - ], - <<"B">> => [ - {issuer,build_url_to_oauth_provider(<<"/B">>) }, - {jwks_uri,build_url_to_oauth_provider(<<"/B/keys">>)} - ] }), - Config; + {issuer,build_url_to_oauth_provider(<<"/A">>) }, + {jwks_uri,build_url_to_oauth_provider(<<"/A/keys">>)} + ], + <<"B">> => [ + {issuer,build_url_to_oauth_provider(<<"/B">>) }, + {jwks_uri,build_url_to_oauth_provider(<<"/B/keys">>)} + ] }), + Config; init_per_group(with_oauth_providers_A_B_with_issuer, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, + application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, #{ <<"A">> => [ - {issuer,build_url_to_oauth_provider(<<"/A">>) }, - {https, ?config(ssl_options, Config)} - ], - <<"B">> => [ - {issuer,build_url_to_oauth_provider(<<"/B">>) }, - {https, ?config(ssl_options, Config)} - ] }), - Config; + {issuer,build_url_to_oauth_provider(<<"/A">>) }, + {https, ?config(ssl_options, Config)} + ], + <<"B">> => [ + {issuer,build_url_to_oauth_provider(<<"/B">>) }, + {https, ?config(ssl_options, Config)} + ] }), + Config; init_per_group(with_default_oauth_provider_A, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, default_oauth_provider, <<"A">>), - Config; + application:set_env(rabbitmq_auth_backend_oauth2, default_oauth_provider, <<"A">>), + Config; init_per_group(with_default_oauth_provider_B, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, default_oauth_provider, <<"B">>), - Config; + application:set_env(rabbitmq_auth_backend_oauth2, default_oauth_provider, <<"B">>), + Config; init_per_group(with_resource_server_id, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, ?RABBITMQ), - Config; + application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, ?RABBITMQ), + Config; + +init_per_group(with_verify_aud_false, Config) -> + application:set_env(rabbitmq_auth_backend_oauth2, verify_aud, false), + Config; +init_per_group(with_verify_aud_false_for_resource_two, Config) -> + ResourceServers = application:get_env(rabbitmq_auth_backend_oauth2, resource_servers, #{}), + Proplist = maps:get(?RABBITMQ_RESOURCE_TWO, ResourceServers, []), + application:set_env(rabbitmq_auth_backend_oauth2, resource_servers, + maps:put(?RABBITMQ_RESOURCE_TWO, [{verify_aud, false} | proplists:delete(verify_aud, Proplist)], ResourceServers)), + Config; +init_per_group(with_algorithms, Config) -> + KeyConfig = application:get_env(rabbitmq_auth_backend_oauth2, key_config, []), + application:set_env(rabbitmq_auth_backend_oauth2, key_config, KeyConfig ++ + [{algorithms, [<<"HS256">>, <<"RS256">>]}]), + [{algorithms, [<<"HS256">>, <<"RS256">>]} | Config]; +init_per_group(with_algorithms_for_provider_A, Config) -> + OAuthProviders = application:get_env(rabbitmq_auth_backend_oauth2, oauth_providers, #{}), + OAuthProvider = maps:get(<<"A">>, OAuthProviders, []), + application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, + maps:put(<<"A">>, [{algorithms, [<<"HS256">>, <<"RS256">>]} | OAuthProvider], OAuthProviders)), + [{algorithms, [<<"HS256">>, <<"RS256">>]} | Config]; init_per_group(with_resource_servers_and_resource_server_id, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, ?RABBITMQ), - application:set_env(rabbitmq_auth_backend_oauth2, key_config, [{jwks_url,<<"https://oauth-for-rabbitmq">> }]), - application:set_env(rabbitmq_auth_backend_oauth2, resource_servers, - #{?RABBITMQ_RESOURCE_ONE => [ { key_config, [ - {jwks_url,<<"https://oauth-for-rabbitmq1">> } - ] - } - - ], - ?RABBITMQ_RESOURCE_TWO => [ { key_config, [ - {jwks_url,<<"https://oauth-for-rabbitmq2">> } - ] - } - ] - }), - Config; + application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, ?RABBITMQ), + application:set_env(rabbitmq_auth_backend_oauth2, key_config, [{jwks_url,<<"https://oauth-for-rabbitmq">> }]), + application:set_env(rabbitmq_auth_backend_oauth2, resource_servers, + #{?RABBITMQ_RESOURCE_ONE => [ + { key_config, [ + {jwks_url,<<"https://oauth-for-rabbitmq1">> } + ]} + + ], + ?RABBITMQ_RESOURCE_TWO => [ + { key_config, [ + {jwks_url,<<"https://oauth-for-rabbitmq2">> } + ]} + ] + }), + Config; init_per_group(with_different_oauth_provider_for_each_resource, Config) -> - {ok, ResourceServers} = application:get_env(rabbitmq_auth_backend_oauth2, resource_servers), - Rabbit1 = maps:get(?RABBITMQ_RESOURCE_ONE, ResourceServers) ++ [ {oauth_provider_id, <<"A">>} ], - Rabbit2 = maps:get(?RABBITMQ_RESOURCE_TWO, ResourceServers) ++ [ {oauth_provider_id, <<"B">>} ], - ResourceServers1 = maps:update(?RABBITMQ_RESOURCE_ONE, Rabbit1, ResourceServers), - application:set_env(rabbitmq_auth_backend_oauth2, resource_servers, maps:update(?RABBITMQ_RESOURCE_TWO, Rabbit2, ResourceServers1)), - Config; + {ok, ResourceServers} = application:get_env(rabbitmq_auth_backend_oauth2, resource_servers), + Rabbit1 = maps:get(?RABBITMQ_RESOURCE_ONE, ResourceServers) ++ [ {oauth_provider_id, <<"A">>} ], + Rabbit2 = maps:get(?RABBITMQ_RESOURCE_TWO, ResourceServers) ++ [ {oauth_provider_id, <<"B">>} ], + ResourceServers1 = maps:update(?RABBITMQ_RESOURCE_ONE, Rabbit1, ResourceServers), + application:set_env(rabbitmq_auth_backend_oauth2, resource_servers, maps:update(?RABBITMQ_RESOURCE_TWO, Rabbit2, ResourceServers1)), + Config; init_per_group(with_resource_servers, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, resource_servers, - #{?RABBITMQ_RESOURCE_ONE => [ { key_config, [ - {jwks_url,<<"https://oauth-for-rabbitmq1">> } - ] - } - ], - ?RABBITMQ_RESOURCE_TWO => [ { key_config, [ - {jwks_url,<<"https://oauth-for-rabbitmq2">> } - ] - } - ], - <<"0">> => [ {id, <<"rabbitmq-0">> } ], - <<"1">> => [ {id, <<"rabbitmq-1">> } ] + application:set_env(rabbitmq_auth_backend_oauth2, resource_servers, + #{?RABBITMQ_RESOURCE_ONE => [ + { key_config, [ + {jwks_url,<<"https://oauth-for-rabbitmq1">> } + ]} + ], + ?RABBITMQ_RESOURCE_TWO => [ + { key_config, [ + {jwks_url,<<"https://oauth-for-rabbitmq2">> } + ]} + ], + <<"0">> => [ {id, <<"rabbitmq-0">> } ], + <<"1">> => [ {id, <<"rabbitmq-1">> } ] }), Config; init_per_group(inheritance_group, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, ?RABBITMQ), - application:set_env(rabbitmq_auth_backend_oauth2, resource_server_type, <<"rabbitmq-type">>), - application:set_env(rabbitmq_auth_backend_oauth2, scope_prefix, <<"some-prefix-">>), - application:set_env(rabbitmq_auth_backend_oauth2, extra_scopes_source, <<"roles">>), - application:set_env(rabbitmq_auth_backend_oauth2, scope_aliases, #{}), - - application:set_env(rabbitmq_auth_backend_oauth2, key_config, [ {jwks_url,<<"https://oauth-for-rabbitmq">> } ]), - - application:set_env(rabbitmq_auth_backend_oauth2, resource_servers, - #{?RABBITMQ_RESOURCE_ONE => [ { key_config, [ {jwks_url,<<"https://oauth-for-rabbitmq1">> } ] }, - { extra_scopes_source, <<"extra-scope-1">>}, - { verify_aud, false}, - { preferred_username_claims, [<<"email-address">>] }, - { scope_prefix, <<"my-prefix:">> }, - { resource_server_type, <<"my-type">> }, - { scope_aliases, #{} } - ], - ?RABBITMQ_RESOURCE_TWO => [ {id, ?RABBITMQ_RESOURCE_TWO } ] - } + application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, ?RABBITMQ), + application:set_env(rabbitmq_auth_backend_oauth2, resource_server_type, <<"rabbitmq-type">>), + application:set_env(rabbitmq_auth_backend_oauth2, scope_prefix, <<"some-prefix-">>), + application:set_env(rabbitmq_auth_backend_oauth2, extra_scopes_source, <<"roles">>), + application:set_env(rabbitmq_auth_backend_oauth2, scope_aliases, #{}), + + application:set_env(rabbitmq_auth_backend_oauth2, key_config, [ {jwks_url,<<"https://oauth-for-rabbitmq">> } ]), + + application:set_env(rabbitmq_auth_backend_oauth2, resource_servers, + #{?RABBITMQ_RESOURCE_ONE => [ + { extra_scopes_source, <<"extra-scope-1">>}, + { verify_aud, false}, + { preferred_username_claims, [<<"email-address">>] }, + { scope_prefix, <<"my-prefix:">> }, + { resource_server_type, <<"my-type">> }, + { scope_aliases, #{} } + ], + ?RABBITMQ_RESOURCE_TWO => [ {id, ?RABBITMQ_RESOURCE_TWO } ] + } ), - Config; + Config; init_per_group(_any, Config) -> - Config. + Config. end_per_group(with_rabbitmq_node, Config) -> - rabbit_ct_helpers:run_steps(Config, rabbit_ct_broker_helpers:teardown_steps()); + rabbit_ct_helpers:run_steps(Config, rabbit_ct_broker_helpers:teardown_steps()); +end_per_group(with_root_static_signing_keys, Config) -> + KeyConfig = call_get_env(Config, key_config, []), + call_set_env(Config, key_config, KeyConfig), + Config; end_per_group(with_resource_server_id, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, resource_server_id), - Config; + application:unset_env(rabbitmq_auth_backend_oauth2, resource_server_id), + Config; +end_per_group(with_verify_aud_false, Config) -> + application:unset_env(rabbitmq_auth_backend_oauth2, verify_aud), + Config; +end_per_group(with_verify_aud_false_for_resource_two, Config) -> + ResourceServers = application:get_env(rabbitmq_auth_backend_oauth2, resource_servers, #{}), + Proplist = maps:get(?RABBITMQ_RESOURCE_TWO, ResourceServers, []), + application:set_env(rabbitmq_auth_backend_oauth2, resource_servers, + map:put(?RABBITMQ_RESOURCE_TWO, proplists:delete(verify_aud, Proplist))), + Config; +end_per_group(with_default_key, Config) -> + KeyConfig = application:get_env(rabbitmq_auth_backend_oauth2, key_config, []), + application:set_env(rabbitmq_auth_backend_oauth2, key_config, + proplists:delete(default_key, KeyConfig)), + Config; +end_per_group(with_algorithms, Config) -> + KeyConfig = application:get_env(rabbitmq_auth_backend_oauth2, key_config, []), + application:set_env(rabbitmq_auth_backend_oauth2, key_config, + proplists:delete(algorithms, KeyConfig)), + Config; +end_per_group(with_algorithms_for_provider_A, Config) -> + OAuthProviders = application:get_env(rabbitmq_auth_backend_oauth2, oauth_providers, #{}), + OAuthProvider = maps:get(<<"A">>, OAuthProviders, []), + application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, + maps:put(<<"A">>, proplists:delete(algorithms, OAuthProvider),OAuthProviders)), + Config; +end_per_group(with_static_signing_keys_for_oauth_provider_A, Config) -> + OAuthProviders = application:get_env(rabbitmq_auth_backend_oauth2, oauth_providers, #{}), + OAuthProvider = maps:get(<<"A">>, OAuthProviders), + OAuthProvider0 = proplists:delete(signing_keys, OAuthProvider), + application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, + maps:put(<<"A">>, OAuthProvider0, OAuthProviders)), + Config; end_per_group(with_jwks_url, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, key_config), - Config; + KeyConfig = ?config(key_config_before_group_with_jwks_url, Config), + application:set_env(rabbitmq_auth_backend_oauth2, key_config, KeyConfig), + Config; end_per_group(with_issuer, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, issuer), - stop_http_auth_server(), - Config; + KeyConfig = ?config(key_config_before_group_with_issuer, Config), + application:unset_env(rabbitmq_auth_backend_oauth2, issuer), + application:set_env(rabbitmq_auth_backend_oauth2, key_config, KeyConfig), + stop_http_auth_server(), + Config; end_per_group(with_oauth_providers_A_with_jwks_uri, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, oauth_providers), - Config; + application:unset_env(rabbitmq_auth_backend_oauth2, oauth_providers), + Config; end_per_group(with_oauth_providers_A_with_issuer, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, oauth_providers), - Config; + application:unset_env(rabbitmq_auth_backend_oauth2, oauth_providers), + Config; end_per_group(with_oauth_providers_A_B_with_jwks_uri, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, oauth_providers), - Config; + application:unset_env(rabbitmq_auth_backend_oauth2, oauth_providers), + Config; end_per_group(with_oauth_providers_A_B_with_issuer, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, oauth_providers), - Config; + application:unset_env(rabbitmq_auth_backend_oauth2, oauth_providers), + Config; end_per_group(with_oauth_providers_A, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, oauth_providers), - Config; + application:unset_env(rabbitmq_auth_backend_oauth2, oauth_providers), + Config; end_per_group(with_oauth_providers_A_B, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, oauth_providers), - Config; + application:unset_env(rabbitmq_auth_backend_oauth2, oauth_providers), + Config; end_per_group(with_default_oauth_provider_B, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, default_oauth_provider), - Config; + application:unset_env(rabbitmq_auth_backend_oauth2, default_oauth_provider), + Config; end_per_group(with_default_oauth_provider_A, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, default_oauth_provider), - Config; + application:unset_env(rabbitmq_auth_backend_oauth2, default_oauth_provider), + Config; end_per_group(get_oauth_provider_for_resource_server_id, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, resource_server_id), - Config; + application:unset_env(rabbitmq_auth_backend_oauth2, resource_server_id), + Config; end_per_group(with_resource_servers_and_resource_server_id, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, resource_server_id), - Config; + application:unset_env(rabbitmq_auth_backend_oauth2, resource_server_id), + Config; end_per_group(with_resource_servers, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, resource_servers), - Config; + application:unset_env(rabbitmq_auth_backend_oauth2, resource_servers), + Config; end_per_group(with_different_oauth_provider_for_each_resource, Config) -> - {ok, ResourceServers} = application:get_env(rabbitmq_auth_backend_oauth2, resource_servers), - Rabbit1 = proplists:delete(oauth_provider_id, maps:get(?RABBITMQ_RESOURCE_ONE, ResourceServers)), - Rabbit2 = proplists:delete(oauth_provider_id, maps:get(?RABBITMQ_RESOURCE_TWO, ResourceServers)), - ResourceServers1 = maps:update(?RABBITMQ_RESOURCE_ONE, Rabbit1, ResourceServers), - application:set_env(rabbitmq_auth_backend_oauth2, resource_servers, maps:update(?RABBITMQ_RESOURCE_TWO, Rabbit2, ResourceServers1)), - Config; + {ok, ResourceServers} = application:get_env(rabbitmq_auth_backend_oauth2, resource_servers), + Rabbit1 = proplists:delete(oauth_provider_id, maps:get(?RABBITMQ_RESOURCE_ONE, ResourceServers)), + Rabbit2 = proplists:delete(oauth_provider_id, maps:get(?RABBITMQ_RESOURCE_TWO, ResourceServers)), + ResourceServers1 = maps:update(?RABBITMQ_RESOURCE_ONE, Rabbit1, ResourceServers), + application:set_env(rabbitmq_auth_backend_oauth2, resource_servers, maps:update(?RABBITMQ_RESOURCE_TWO, Rabbit2, ResourceServers1)), + Config; end_per_group(inheritance_group, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, resource_server_id), - application:unset_env(rabbitmq_auth_backend_oauth2, scope_prefix), - application:unset_env(rabbitmq_auth_backend_oauth2, extra_scopes_source), + application:unset_env(rabbitmq_auth_backend_oauth2, resource_server_id), + application:unset_env(rabbitmq_auth_backend_oauth2, scope_prefix), + application:unset_env(rabbitmq_auth_backend_oauth2, extra_scopes_source), - application:unset_env(rabbitmq_auth_backend_oauth2, key_config), + application:unset_env(rabbitmq_auth_backend_oauth2, key_config), - application:unset_env(rabbitmq_auth_backend_oauth2, resource_servers), - Config; + application:unset_env(rabbitmq_auth_backend_oauth2, resource_servers), + Config; end_per_group(_any, Config) -> - Config. + Config. init_per_testcase(get_preferred_username_claims, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, preferred_username_claims, [<<"username">>]), - Config; + application:set_env(rabbitmq_auth_backend_oauth2, preferred_username_claims, [<<"username">>]), + Config; init_per_testcase(get_additional_scopes_key_when_not_defined, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, extra_scopes_source), - Config; + application:unset_env(rabbitmq_auth_backend_oauth2, extra_scopes_source), + Config; init_per_testcase(is_verify_aud_when_is_false, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, verify_aud, false), - Config; + application:set_env(rabbitmq_auth_backend_oauth2, verify_aud, false), + Config; init_per_testcase(get_scope_prefix_when_not_defined, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, scope_prefix), - Config; + application:unset_env(rabbitmq_auth_backend_oauth2, scope_prefix), + Config; init_per_testcase(get_resource_server_type_when_not_defined, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, resource_server_type), - Config; + application:unset_env(rabbitmq_auth_backend_oauth2, resource_server_type), + Config; init_per_testcase(has_scope_aliases_when_not_defined, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, scope_aliases), - Config; + application:unset_env(rabbitmq_auth_backend_oauth2, scope_aliases), + Config; init_per_testcase(_TestCase, Config) -> - Config. + Config. end_per_testcase(get_preferred_username_claims, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, preferred_username_claims), - Config; + application:unset_env(rabbitmq_auth_backend_oauth2, preferred_username_claims), + Config; end_per_testcase(_Testcase, Config) -> - Config. + Config. %% ----- +call_set_env(Config, Par, Value) -> + rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, + [rabbitmq_auth_backend_oauth2, Par, Value]). + +call_get_env(Config, Par, Def) -> + rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, + [rabbitmq_auth_backend_oauth2, Par, Def]). + call_add_signing_key(Config, Args) -> - rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_oauth2_config, add_signing_key, Args). + rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_oauth2_config, add_signing_key, Args). call_get_signing_keys(Config, Args) -> - rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_oauth2_config, get_signing_keys, Args). + rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_oauth2_config, get_signing_keys, Args). +call_get_signing_keys(Config) -> + call_get_signing_keys(Config, []). call_get_signing_key(Config, Args) -> - rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_oauth2_config, get_signing_key, Args). + rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_oauth2_config, get_signing_key, Args). call_add_signing_keys(Config, Args) -> - rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_oauth2_config, add_signing_keys, Args). + rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_oauth2_config, add_signing_keys, Args). call_replace_signing_keys(Config, Args) -> - rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_oauth2_config, replace_signing_keys, Args). - -add_signing_keys_for_top_level_resource_server(Config) -> - #{<<"mykey-1">> := <<"some key 1">>} = call_add_signing_key(Config, [<<"mykey-1">>, <<"some key 1">>]), - #{<<"mykey-1">> := <<"some key 1">>} = call_get_signing_keys(Config, []), - - #{<<"mykey-1">> := <<"some key 1">>, <<"mykey-2">> := <<"some key 2">>} = call_add_signing_key(Config, [<<"mykey-2">>, <<"some key 2">>]), - #{<<"mykey-1">> := <<"some key 1">>, <<"mykey-2">> := <<"some key 2">>} = call_get_signing_keys(Config, []), - - ?assertEqual(<<"some key 1">>, call_get_signing_key(Config, [<<"mykey-1">>, ?RABBITMQ])). - -add_signing_keys_for_top_specific_resource_server(Config) -> - #{<<"mykey-3-1">> := <<"some key 3-1">>} = call_add_signing_key(Config, [<<"my-resource-server-3">>, <<"mykey-3-1">>, <<"some key 3-1">>]), - #{<<"mykey-4-1">> := <<"some key 4-1">>} = call_add_signing_key(Config, [<<"my-resource-server-4">>, <<"mykey-4-1">>, <<"some key 4-1">>]), - #{<<"mykey-3-1">> := <<"some key 3-1">>} = call_get_signing_keys(Config, [<<"my-resource-server-3">>]), - #{<<"mykey-4-1">> := <<"some key 4-1">>} = call_get_signing_keys(Config, [<<"my-resource-server-4">>]), - - #{<<"mykey-3-1">> := <<"some key 3-1">>, <<"mykey-3-2">> := <<"some key 3-2">>} = call_add_signing_key(Config, [<<"my-resource-server-3">>, <<"mykey-3-2">>, <<"some key 3-2">>]), + rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_oauth2_config, replace_signing_keys, Args). + + +add_signing_keys_for_root_oauth_provider(Config) -> + #{<<"mykey-1">> := <<"some key 1">>} = + call_add_signing_key(Config, [<<"mykey-1">>, <<"some key 1">>]), + #{<<"mykey-1">> := <<"some key 1">>} = + call_get_signing_keys(Config), + + #{<<"mykey-1">> := <<"some key 1">>, <<"mykey-2">> := <<"some key 2">>} = + call_add_signing_key(Config, [<<"mykey-2">>, <<"some key 2">>]), + #{<<"mykey-1">> := <<"some key 1">>, <<"mykey-2">> := <<"some key 2">>} = + call_get_signing_keys(Config), + + ?assertEqual(<<"some key 1">>, + call_get_signing_key(Config, [<<"mykey-1">>])). + +add_signing_keys_for_specific_oauth_provider(Config) -> + #{<<"mykey-3-1">> := <<"some key 3-1">>} = + call_add_signing_key(Config, + [<<"mykey-3-1">>, <<"some key 3-1">>, <<"my-oauth-provider-3">>]), + #{<<"mykey-4-1">> := <<"some key 4-1">>} = + call_add_signing_key(Config, + [<<"mykey-4-1">>, <<"some key 4-1">>, <<"my-oauth-provider-4">>]), + #{<<"mykey-3-1">> := <<"some key 3-1">>} = + call_get_signing_keys(Config, [<<"my-oauth-provider-3">>]), + #{<<"mykey-4-1">> := <<"some key 4-1">>} = + call_get_signing_keys(Config, [<<"my-oauth-provider-4">>]), + + #{<<"mykey-3-1">> := <<"some key 3-1">>, + <<"mykey-3-2">> := <<"some key 3-2">>} = + call_add_signing_key(Config, [ + <<"mykey-3-2">>, <<"some key 3-2">>, <<"my-oauth-provider-3">>]), + + #{<<"mykey-1">> := <<"some key 1">>} = + call_add_signing_key(Config, [<<"mykey-1">>, <<"some key 1">>]), + #{<<"mykey-1">> := <<"some key 1">>} = + call_get_signing_keys(Config, []), + + ?assertEqual(<<"some key 3-1">>, + call_get_signing_key(Config, [<<"mykey-3-1">> , <<"my-oauth-provider-3">>])). + +replace_merge_root_static_keys_with_newly_added_keys(Config) -> + NewKeys = #{<<"key-2">> => <<"some key 2">>, <<"key-3">> => <<"some key 3">>}, + call_replace_signing_keys(Config, [NewKeys]), + #{ <<"mykey-root-1">> := <<"some key root-1">>, + <<"mykey-root-2">> := <<"some key root-2">>, + <<"key-2">> := <<"some key 2">>, + <<"key-3">> := <<"some key 3">> + } = call_get_signing_keys(Config). +replace_merge_static_keys_with_newly_added_keys(Config) -> + NewKeys = #{<<"key-2">> => <<"some key 2">>, <<"key-3">> => <<"some key 3">>}, + call_replace_signing_keys(Config, [NewKeys, <<"A">>]), + #{ <<"mykey-root-1">> := <<"some key root-1">>, + <<"mykey-root-2">> := <<"some key root-2">>, + <<"key-2">> := <<"some key 2">>, + <<"key-3">> := <<"some key 3">> + } = call_get_signing_keys(Config, [<<"A">>]). +replace_override_root_static_keys_with_newly_added_keys(Config) -> + NewKeys = #{<<"mykey-root-1">> => <<"new key root-1">>, <<"key-3">> => <<"some key 3">>}, + call_replace_signing_keys(Config, [NewKeys]), + #{ <<"mykey-root-1">> := <<"new key root-1">>, + <<"mykey-root-2">> := <<"some key root-2">>, + <<"key-3">> := <<"some key 3">> + } = call_get_signing_keys(Config). +replace_override_static_keys_with_newly_added_keys(Config) -> + NewKeys = #{<<"mykey-root-1">> => <<"new key root-1">>, <<"key-3">> => <<"some key 3">>}, + call_replace_signing_keys(Config, [NewKeys, <<"A">>]), + #{ <<"mykey-root-1">> := <<"new key root-1">>, + <<"mykey-root-2">> := <<"some key root-2">>, + <<"key-3">> := <<"some key 3">> + } = call_get_signing_keys(Config, [<<"A">>]). + +replace_signing_keys_for_root_oauth_provider(Config) -> + call_add_signing_key(Config, [<<"mykey-1">>, <<"some key 1">>]), + NewKeys = #{<<"key-2">> => <<"some key 2">>, <<"key-3">> => <<"some key 3">>}, + call_replace_signing_keys(Config, [NewKeys]), + #{<<"key-2">> := <<"some key 2">>, <<"key-3">> := <<"some key 3">>} = + call_get_signing_keys(Config). + +replace_signing_keys_for_specific_oauth_provider(Config) -> + OAuthProviderId = <<"my-oauth-provider-3">>, + #{<<"mykey-3-1">> := <<"some key 3-1">>} = + call_add_signing_key(Config, + [<<"mykey-3-1">>, <<"some key 3-1">>, OAuthProviderId]), + NewKeys = #{<<"key-2">> => <<"some key 2">>, + <<"key-3">> => <<"some key 3">>}, + call_replace_signing_keys(Config, [NewKeys, OAuthProviderId]), + #{<<"key-2">> := <<"some key 2">>, <<"key-3">> := <<"some key 3">>} = + call_get_signing_keys(Config, [OAuthProviderId]). - #{<<"mykey-1">> := <<"some key 1">>} = call_add_signing_key(Config, [<<"mykey-1">>, <<"some key 1">>]), - #{<<"mykey-1">> := <<"some key 1">>} = call_get_signing_keys(Config, []), - ?assertEqual(<<"some key 3-1">>, call_get_signing_key(Config, [<<"mykey-3-1">> , <<"my-resource-server-3">>])). - -replace_signing_keys_for_top_level_resource_server(Config) -> - call_add_signing_key(Config, [<<"mykey-1">>, <<"some key 1">>]), - NewKeys = #{<<"key-2">> => <<"some key 2">>, <<"key-3">> => <<"some key 3">>}, - call_replace_signing_keys(Config, [NewKeys]), - #{<<"key-2">> := <<"some key 2">>, <<"key-3">> := <<"some key 3">>} = call_get_signing_keys(Config, []). +get_default_resource_server_id_returns_error(_Config) -> + {error, _} = rabbit_oauth2_config:get_default_resource_server_id(). -replace_signing_keys_for_specific_resource_server(Config) -> - ResourceServerId = <<"my-resource-server-3">>, - #{<<"mykey-3-1">> := <<"some key 3-1">>} = call_add_signing_key(Config, [ResourceServerId, <<"mykey-3-1">>, <<"some key 3-1">>]), - NewKeys = #{<<"key-2">> => <<"some key 2">>, <<"key-3">> => <<"some key 3">>}, - call_replace_signing_keys(Config, [ResourceServerId, NewKeys]), - #{<<"key-2">> := <<"some key 2">>, <<"key-3">> := <<"some key 3">>} = call_get_signing_keys(Config, [ResourceServerId]). +get_resource_server_id_for_rabbit_audience_returns_rabbit(_Config) -> + ?assertEqual(?RABBITMQ, rabbit_oauth2_config:get_resource_server_id_for_audience(?RABBITMQ)). +get_resource_server_id_for_none_audience_returns_rabbit(_Config) -> + ?assertEqual(?RABBITMQ, rabbit_oauth2_config:get_resource_server_id_for_audience(none)). +get_resource_server_id_for_unknown_audience_returns_rabbit(_Config) -> + ?assertEqual(?RABBITMQ, rabbit_oauth2_config:get_resource_server_id_for_audience(<<"unknown">>)). -get_default_resource_server_id_returns_error(_Config) -> - {error, _} = rabbit_oauth2_config:get_default_resource_server_id(). +get_resource_server_id_for_none_audience_should_fail(_Config) -> + ?assertEqual({error, no_matching_aud_found}, rabbit_oauth2_config:get_resource_server_id_for_audience(none)). +get_resource_server_id_for_unknown_audience_should_fail(_Config) -> + ?assertEqual({error, no_matching_aud_found}, rabbit_oauth2_config:get_resource_server_id_for_audience(<<"unknown">>)). get_default_resource_server_id(_Config) -> - ?assertEqual(?RABBITMQ, rabbit_oauth2_config:get_default_resource_server_id()). + ?assertEqual(?RABBITMQ, rabbit_oauth2_config:get_default_resource_server_id()). get_allowed_resource_server_ids_returns_empty_list(_Config) -> - [] = rabbit_oauth2_config:get_allowed_resource_server_ids(). + [] = rabbit_oauth2_config:get_allowed_resource_server_ids(). get_allowed_resource_server_ids_returns_resource_server_id(_Config) -> - [?RABBITMQ] = rabbit_oauth2_config:get_allowed_resource_server_ids(). + [?RABBITMQ] = rabbit_oauth2_config:get_allowed_resource_server_ids(). get_allowed_resource_server_ids_returns_all_resource_servers_ids(_Config) -> - [ <<"rabbitmq1">>, <<"rabbitmq2">>, ?RABBITMQ] = rabbit_oauth2_config:get_allowed_resource_server_ids(). + [ <<"rabbitmq1">>, <<"rabbitmq2">>, ?RABBITMQ] = rabbit_oauth2_config:get_allowed_resource_server_ids(). get_allowed_resource_server_ids_returns_resource_servers_ids(_Config) -> - [<<"rabbitmq-0">>, <<"rabbitmq-1">>, <<"rabbitmq1">>, <<"rabbitmq2">> ] = - lists:sort(rabbit_oauth2_config:get_allowed_resource_server_ids()). + [<<"rabbitmq-0">>, <<"rabbitmq-1">>, <<"rabbitmq1">>, <<"rabbitmq2">> ] = + lists:sort(rabbit_oauth2_config:get_allowed_resource_server_ids()). index_resource_servers_by_id_else_by_key(_Config) -> - {error, no_matching_aud_found} = rabbit_oauth2_config:find_audience_in_resource_server_ids(<<"0">>), - {ok, <<"rabbitmq-0">>} = rabbit_oauth2_config:find_audience_in_resource_server_ids([<<"rabbitmq-0">>]), - {ok, <<"rabbitmq-0">>} = rabbit_oauth2_config:find_audience_in_resource_server_ids(<<"rabbitmq-0">>). + {error, no_matching_aud_found} = rabbit_oauth2_config:find_audience_in_resource_server_ids(<<"0">>), + {ok, <<"rabbitmq-0">>} = rabbit_oauth2_config:find_audience_in_resource_server_ids([<<"rabbitmq-0">>]), + {ok, <<"rabbitmq-0">>} = rabbit_oauth2_config:find_audience_in_resource_server_ids(<<"rabbitmq-0">>). find_audience_in_resource_server_ids_returns_key_not_found(_Config) -> - {error, no_matching_aud_found} = rabbit_oauth2_config:find_audience_in_resource_server_ids(?RABBITMQ). + {error, no_matching_aud_found} = rabbit_oauth2_config:find_audience_in_resource_server_ids(?RABBITMQ). find_audience_in_resource_server_ids_returns_found_too_many(_Config) -> - {error, only_one_resource_server_as_audience_found_many} = rabbit_oauth2_config:find_audience_in_resource_server_ids([?RABBITMQ, <<"rabbitmq1">>]). + {error, only_one_resource_server_as_audience_found_many} = rabbit_oauth2_config:find_audience_in_resource_server_ids([?RABBITMQ, <<"rabbitmq1">>]). find_audience_in_resource_server_ids_found_one_resource_servers(_Config) -> - {ok, <<"rabbitmq1">>} = rabbit_oauth2_config:find_audience_in_resource_server_ids(<<"rabbitmq1">>), - {ok, <<"rabbitmq1">>} = rabbit_oauth2_config:find_audience_in_resource_server_ids([<<"rabbitmq1">>, <<"other">>]). + {ok, <<"rabbitmq1">>} = rabbit_oauth2_config:find_audience_in_resource_server_ids(<<"rabbitmq1">>), + {ok, <<"rabbitmq1">>} = rabbit_oauth2_config:find_audience_in_resource_server_ids([<<"rabbitmq1">>, <<"other">>]). find_audience_in_resource_server_ids_found_resource_server_id(_Config) -> - {ok, ?RABBITMQ} = rabbit_oauth2_config:find_audience_in_resource_server_ids(?RABBITMQ), - {ok, ?RABBITMQ} = rabbit_oauth2_config:find_audience_in_resource_server_ids([?RABBITMQ, <<"other">>]). + {ok, ?RABBITMQ} = rabbit_oauth2_config:find_audience_in_resource_server_ids(?RABBITMQ), + {ok, ?RABBITMQ} = rabbit_oauth2_config:find_audience_in_resource_server_ids([?RABBITMQ, <<"other">>]). find_audience_in_resource_server_ids_using_binary_audience(_Config) -> - {ok, ?RABBITMQ} = rabbit_oauth2_config:find_audience_in_resource_server_ids(<<"rabbitmq other">>). - -get_key_config(_Config) -> - RootKeyConfig = rabbit_oauth2_config:get_key_config(<<"rabbitmq-2">>), - ?assertEqual(<<"https://oauth-for-rabbitmq">>, proplists:get_value(jwks_url, RootKeyConfig)), - - KeyConfig = rabbit_oauth2_config:get_key_config(<<"rabbitmq1">>), - ?assertEqual(<<"https://oauth-for-rabbitmq1">>, proplists:get_value(jwks_url, KeyConfig)). + {ok, ?RABBITMQ} = rabbit_oauth2_config:find_audience_in_resource_server_ids(<<"rabbitmq other">>). get_additional_scopes_key(_Config) -> - ?assertEqual({ok, <<"roles">>}, rabbit_oauth2_config:get_additional_scopes_key()), - ?assertEqual({ok, <<"extra-scope-1">>}, rabbit_oauth2_config:get_additional_scopes_key(<<"rabbitmq1">> )), - ?assertEqual(rabbit_oauth2_config:get_additional_scopes_key(), rabbit_oauth2_config:get_additional_scopes_key(<<"rabbitmq2">>)), - ?assertEqual({ok, <<"roles">>}, rabbit_oauth2_config:get_additional_scopes_key(?RABBITMQ)). + ?assertEqual({ok, <<"roles">>}, rabbit_oauth2_config:get_additional_scopes_key()), + ?assertEqual({ok, <<"extra-scope-1">>}, rabbit_oauth2_config:get_additional_scopes_key(<<"rabbitmq1">> )), + ?assertEqual(rabbit_oauth2_config:get_additional_scopes_key(), rabbit_oauth2_config:get_additional_scopes_key(<<"rabbitmq2">>)), + ?assertEqual({ok, <<"roles">>}, rabbit_oauth2_config:get_additional_scopes_key(?RABBITMQ)). get_additional_scopes_key_when_not_defined(_Config) -> - ?assertEqual({error, not_found}, rabbit_oauth2_config:get_additional_scopes_key()), - ?assertEqual(rabbit_oauth2_config:get_additional_scopes_key(), rabbit_oauth2_config:get_additional_scopes_key(<<"rabbitmq2">>)). + ?assertEqual({error, not_found}, rabbit_oauth2_config:get_additional_scopes_key()), + ?assertEqual(rabbit_oauth2_config:get_additional_scopes_key(), rabbit_oauth2_config:get_additional_scopes_key(<<"rabbitmq2">>)). is_verify_aud(_Config) -> - ?assertEqual(true, rabbit_oauth2_config:is_verify_aud()), - ?assertEqual(rabbit_oauth2_config:is_verify_aud(?RABBITMQ), rabbit_oauth2_config:is_verify_aud()), - ?assertEqual(false, rabbit_oauth2_config:is_verify_aud(<<"rabbitmq1">>)), - ?assertEqual(rabbit_oauth2_config:is_verify_aud(), rabbit_oauth2_config:is_verify_aud(<<"rabbitmq2">>)). + ?assertEqual(true, rabbit_oauth2_config:is_verify_aud()), + ?assertEqual(rabbit_oauth2_config:is_verify_aud(?RABBITMQ), rabbit_oauth2_config:is_verify_aud()), + ?assertEqual(false, rabbit_oauth2_config:is_verify_aud(<<"rabbitmq1">>)), + ?assertEqual(rabbit_oauth2_config:is_verify_aud(), rabbit_oauth2_config:is_verify_aud(<<"rabbitmq2">>)). +is_verify_aud_for_resource_one_returns_false(_Config) -> + ?assertEqual(false, rabbit_oauth2_config:is_verify_aud(?RABBITMQ_RESOURCE_ONE)). + +is_verify_aud_for_resource_two_returns_true(_Config) -> + ?assertEqual(true, rabbit_oauth2_config:is_verify_aud(?RABBITMQ_RESOURCE_TWO)). is_verify_aud_when_is_false(_Config) -> - ?assertEqual(false, rabbit_oauth2_config:is_verify_aud()), - ?assertEqual(rabbit_oauth2_config:is_verify_aud(), rabbit_oauth2_config:is_verify_aud(<<"rabbitmq2">>)). + ?assertEqual(false, rabbit_oauth2_config:is_verify_aud()), + ?assertEqual(rabbit_oauth2_config:is_verify_aud(), rabbit_oauth2_config:is_verify_aud(<<"rabbitmq2">>)). + +is_verify_aud_for_resource_one_returns_true(_Config) -> + ?assertEqual(true, rabbit_oauth2_config:is_verify_aud(?RABBITMQ_RESOURCE_ONE)). +is_verify_aud_for_resource_two_returns_false(_Config) -> + ?assertEqual(false, rabbit_oauth2_config:is_verify_aud(?RABBITMQ_RESOURCE_TWO)). get_default_preferred_username_claims(_Config) -> - ?assertEqual(rabbit_oauth2_config:get_default_preferred_username_claims(), rabbit_oauth2_config:get_preferred_username_claims()). + ?assertEqual(rabbit_oauth2_config:get_default_preferred_username_claims(), rabbit_oauth2_config:get_preferred_username_claims()). get_preferred_username_claims(_Config) -> - ?assertEqual([<<"username">>] ++ rabbit_oauth2_config:get_default_preferred_username_claims(), - rabbit_oauth2_config:get_preferred_username_claims()), - ?assertEqual([<<"email-address">>] ++ rabbit_oauth2_config:get_default_preferred_username_claims(), - rabbit_oauth2_config:get_preferred_username_claims(<<"rabbitmq1">>)), - ?assertEqual(rabbit_oauth2_config:get_preferred_username_claims(), - rabbit_oauth2_config:get_preferred_username_claims(<<"rabbitmq2">>)). + ?assertEqual([<<"username">>] ++ rabbit_oauth2_config:get_default_preferred_username_claims(), + rabbit_oauth2_config:get_preferred_username_claims()), + ?assertEqual([<<"email-address">>] ++ rabbit_oauth2_config:get_default_preferred_username_claims(), + rabbit_oauth2_config:get_preferred_username_claims(<<"rabbitmq1">>)), + ?assertEqual(rabbit_oauth2_config:get_preferred_username_claims(), + rabbit_oauth2_config:get_preferred_username_claims(<<"rabbitmq2">>)). get_scope_prefix_when_not_defined(_Config) -> - ?assertEqual(<<"rabbitmq.">>, rabbit_oauth2_config:get_scope_prefix()), - ?assertEqual(<<"rabbitmq2.">>, rabbit_oauth2_config:get_scope_prefix(<<"rabbitmq2">>)). + ?assertEqual(<<"rabbitmq.">>, rabbit_oauth2_config:get_scope_prefix()), + ?assertEqual(<<"rabbitmq2.">>, rabbit_oauth2_config:get_scope_prefix(<<"rabbitmq2">>)). get_scope_prefix(_Config) -> - ?assertEqual(<<"some-prefix-">>, rabbit_oauth2_config:get_scope_prefix()), - ?assertEqual(<<"my-prefix:">>, rabbit_oauth2_config:get_scope_prefix(<<"rabbitmq1">>)), - ?assertEqual(rabbit_oauth2_config:get_scope_prefix(), rabbit_oauth2_config:get_scope_prefix(<<"rabbitmq2">>)). + ?assertEqual(<<"some-prefix-">>, rabbit_oauth2_config:get_scope_prefix()), + ?assertEqual(<<"my-prefix:">>, rabbit_oauth2_config:get_scope_prefix(<<"rabbitmq1">>)), + ?assertEqual(rabbit_oauth2_config:get_scope_prefix(), rabbit_oauth2_config:get_scope_prefix(<<"rabbitmq2">>)). get_resource_server_type_when_not_defined(_Config) -> - ?assertEqual(<<>>, rabbit_oauth2_config:get_resource_server_type()), - ?assertEqual(<<>>, rabbit_oauth2_config:get_resource_server_type(<<"rabbitmq2">>)). + ?assertEqual(<<>>, rabbit_oauth2_config:get_resource_server_type()), + ?assertEqual(<<>>, rabbit_oauth2_config:get_resource_server_type(<<"rabbitmq2">>)). get_resource_server_type(_Config) -> - ?assertEqual(<<"rabbitmq-type">>, rabbit_oauth2_config:get_resource_server_type()), - ?assertEqual(<<"my-type">>, rabbit_oauth2_config:get_resource_server_type(<<"rabbitmq1">>)), - ?assertEqual(rabbit_oauth2_config:get_resource_server_type(), rabbit_oauth2_config:get_resource_server_type(<<"rabbitmq2">>)). + ?assertEqual(<<"rabbitmq-type">>, rabbit_oauth2_config:get_resource_server_type()), + ?assertEqual(<<"my-type">>, rabbit_oauth2_config:get_resource_server_type(<<"rabbitmq1">>)), + ?assertEqual(rabbit_oauth2_config:get_resource_server_type(), rabbit_oauth2_config:get_resource_server_type(<<"rabbitmq2">>)). has_scope_aliases_when_not_defined(_Config) -> - ?assertEqual(false, rabbit_oauth2_config:has_scope_aliases(?RABBITMQ)), - ?assertEqual(true, rabbit_oauth2_config:has_scope_aliases(<<"rabbitmq1">>)), - ?assertEqual(rabbit_oauth2_config:has_scope_aliases(?RABBITMQ), rabbit_oauth2_config:has_scope_aliases(<<"rabbitmq2">>)). + ?assertEqual(false, rabbit_oauth2_config:has_scope_aliases(?RABBITMQ)), + ?assertEqual(true, rabbit_oauth2_config:has_scope_aliases(<<"rabbitmq1">>)), + ?assertEqual(rabbit_oauth2_config:has_scope_aliases(?RABBITMQ), rabbit_oauth2_config:has_scope_aliases(<<"rabbitmq2">>)). has_scope_aliases(_Config) -> - ?assertEqual(true, rabbit_oauth2_config:has_scope_aliases(?RABBITMQ)), - ?assertEqual(true, rabbit_oauth2_config:has_scope_aliases(<<"rabbitmq1">>)), - ?assertEqual(rabbit_oauth2_config:has_scope_aliases(?RABBITMQ), rabbit_oauth2_config:has_scope_aliases(<<"rabbitmq2">>)). + ?assertEqual(true, rabbit_oauth2_config:has_scope_aliases(?RABBITMQ)), + ?assertEqual(true, rabbit_oauth2_config:has_scope_aliases(<<"rabbitmq1">>)), + ?assertEqual(rabbit_oauth2_config:has_scope_aliases(?RABBITMQ), rabbit_oauth2_config:has_scope_aliases(<<"rabbitmq2">>)). get_scope_aliases(_Config) -> - ?assertEqual(#{}, rabbit_oauth2_config:get_scope_aliases(?RABBITMQ)), - ?assertEqual(#{}, rabbit_oauth2_config:get_scope_aliases(<<"rabbitmq1">>)), - ?assertEqual(rabbit_oauth2_config:get_scope_aliases(?RABBITMQ), rabbit_oauth2_config:get_scope_aliases(<<"rabbitmq2">>)). - -get_oauth_provider_should_fail(_Config) -> - {error, _Message} = rabbit_oauth2_config:get_oauth_provider_for_resource_server_id(?RABBITMQ, [jwks_uri]). + ?assertEqual(#{}, rabbit_oauth2_config:get_scope_aliases(?RABBITMQ)), + ?assertEqual(#{}, rabbit_oauth2_config:get_scope_aliases(<<"rabbitmq1">>)), + ?assertEqual(rabbit_oauth2_config:get_scope_aliases(?RABBITMQ), rabbit_oauth2_config:get_scope_aliases(<<"rabbitmq2">>)). + +get_default_key_should_fail(_Config) -> + {error, no_default_key_configured} = rabbit_oauth2_config:get_default_key(). +get_default_key(_Config) -> + {ok, <<"default-key">>} = rabbit_oauth2_config:get_default_key(). +get_default_key_for_provider_A_should_fail(_Config) -> + {error, no_default_key_configured} = rabbit_oauth2_config:get_default_key(<<"A">>). +get_default_key_for_provider_A(_Config) -> + {ok, <<"A-default-key">>} = rabbit_oauth2_config:get_default_key(<<"A">>). + +get_signing_keys(_Config) -> + #{<<"mykey-1-1">> := <<"some key 1-1">>, + <<"mykey-1-2">> := <<"some key 1-2">>} = rabbit_oauth2_config:get_signing_keys(), + <<"some key 1-1">> = rabbit_oauth2_config:get_signing_key(<<"mykey-1-1">>), + undefined = rabbit_oauth2_config:get_signing_key(<<"unknown">>). +get_signing_keys_for_oauth_provider_A(_Config) -> + #{<<"A-mykey-1-1">> := <<"A-some key 1-1">>, + <<"A-mykey-1-2">> := <<"A-some key 1-2">>} = rabbit_oauth2_config:get_signing_keys(<<"A">>), + <<"A-some key 1-1">> = rabbit_oauth2_config:get_signing_key(<<"A-mykey-1-1">>, <<"A">>), + undefined = rabbit_oauth2_config:get_signing_key(<<"unknown">>, <<"A">>). + +get_algorithms_should_return_undefined(_Config) -> + undefined = rabbit_oauth2_config:get_algorithms(). +get_algorithms(Config) -> + ?assertEqual(?config(algorithms, Config), rabbit_oauth2_config:get_algorithms()). +get_algorithms_for_provider_A_should_return_undefined(_Config) -> + undefined = rabbit_oauth2_config:get_algorithms(<<"A">>). +get_algorithms_for_provider_A(Config) -> + ?assertEqual(?config(algorithms, Config), rabbit_oauth2_config:get_algorithms(<<"A">>)). + +get_oauth_provider_root_with_jwks_uri_should_fail(_Config) -> + root = rabbit_oauth2_config:get_oauth_provider_id_for_resource_server_id(?RABBITMQ), + {error, _Message} = rabbit_oauth2_config:get_oauth_provider(root, [jwks_uri]). +get_oauth_provider_A_with_jwks_uri_should_fail(_Config) -> + <<"A">> = rabbit_oauth2_config:get_oauth_provider_id_for_resource_server_id(?RABBITMQ), + {error, _Message} = rabbit_oauth2_config:get_oauth_provider(<<"A">>, [jwks_uri]). get_oauth_provider_should_return_root_oauth_provider_with_jwks_uri(_Config) -> - {ok, OAuthProvider} = rabbit_oauth2_config:get_oauth_provider_for_resource_server_id(?RABBITMQ, [jwks_uri]), - ?assertEqual(build_url_to_oauth_provider(<<"/keys">>), OAuthProvider#oauth_provider.jwks_uri). + root = rabbit_oauth2_config:get_oauth_provider_id_for_resource_server_id(?RABBITMQ), + {ok, OAuthProvider} = rabbit_oauth2_config:get_oauth_provider(root, [jwks_uri]), + ?assertEqual(build_url_to_oauth_provider(<<"/keys">>), OAuthProvider#oauth_provider.jwks_uri). get_oauth_provider_for_both_resources_should_return_root_oauth_provider(_Config) -> - {ok, OAuthProvider} = rabbit_oauth2_config:get_oauth_provider_for_resource_server_id(?RABBITMQ_RESOURCE_ONE, [jwks_uri]), - ?assertEqual(build_url_to_oauth_provider(<<"/keys">>), OAuthProvider#oauth_provider.jwks_uri), - {ok, OAuthProvider} = rabbit_oauth2_config:get_oauth_provider_for_resource_server_id(?RABBITMQ_RESOURCE_TWO, [jwks_uri]), - ?assertEqual(build_url_to_oauth_provider(<<"/keys">>), OAuthProvider#oauth_provider.jwks_uri). + root = rabbit_oauth2_config:get_oauth_provider_id_for_resource_server_id(?RABBITMQ_RESOURCE_ONE), + {ok, OAuthProvider} = rabbit_oauth2_config:get_oauth_provider(root, [jwks_uri]), + ?assertEqual(build_url_to_oauth_provider(<<"/keys">>), OAuthProvider#oauth_provider.jwks_uri), + root = rabbit_oauth2_config:get_oauth_provider_id_for_resource_server_id(?RABBITMQ_RESOURCE_TWO). get_oauth_provider_for_resource_one_should_return_oauth_provider_A(_Config) -> - {ok, ResourceServers} = application:get_env(rabbitmq_auth_backend_oauth2, resource_servers), - ct:log("ResourceServers : ~p", [ResourceServers]), - {ok, OAuthProvider} = rabbit_oauth2_config:get_oauth_provider_for_resource_server_id(?RABBITMQ_RESOURCE_ONE, [jwks_uri]), - ?assertEqual(build_url_to_oauth_provider(<<"/A/keys">>), OAuthProvider#oauth_provider.jwks_uri). + <<"A">> = rabbit_oauth2_config:get_oauth_provider_id_for_resource_server_id(?RABBITMQ_RESOURCE_ONE), + {ok, OAuthProvider} = rabbit_oauth2_config:get_oauth_provider(<<"A">>, [jwks_uri]), + ?assertEqual(build_url_to_oauth_provider(<<"/A/keys">>), OAuthProvider#oauth_provider.jwks_uri). get_oauth_provider_for_both_resources_should_return_oauth_provider_A(_Config) -> - {ok, OAuthProvider} = rabbit_oauth2_config:get_oauth_provider_for_resource_server_id(?RABBITMQ_RESOURCE_ONE, [jwks_uri]), - ?assertEqual(build_url_to_oauth_provider(<<"/A/keys">>), OAuthProvider#oauth_provider.jwks_uri), - {ok, OAuthProvider} = rabbit_oauth2_config:get_oauth_provider_for_resource_server_id(?RABBITMQ_RESOURCE_TWO, [jwks_uri]), - ?assertEqual(build_url_to_oauth_provider(<<"/A/keys">>), OAuthProvider#oauth_provider.jwks_uri). + <<"A">> = rabbit_oauth2_config:get_oauth_provider_id_for_resource_server_id(?RABBITMQ_RESOURCE_ONE), + {ok, OAuthProvider} = rabbit_oauth2_config:get_oauth_provider(<<"A">>, [jwks_uri]), + ?assertEqual(build_url_to_oauth_provider(<<"/A/keys">>), OAuthProvider#oauth_provider.jwks_uri), + <<"A">> = rabbit_oauth2_config:get_oauth_provider_id_for_resource_server_id(?RABBITMQ_RESOURCE_TWO). get_oauth_provider_for_resource_two_should_return_oauth_provider_B(_Config) -> - {ok, OAuthProvider} = rabbit_oauth2_config:get_oauth_provider_for_resource_server_id(?RABBITMQ_RESOURCE_TWO, [jwks_uri]), - ?assertEqual(build_url_to_oauth_provider(<<"/B/keys">>), OAuthProvider#oauth_provider.jwks_uri). + <<"B">> = rabbit_oauth2_config:get_oauth_provider_id_for_resource_server_id(?RABBITMQ_RESOURCE_TWO), + {ok, OAuthProvider} = rabbit_oauth2_config:get_oauth_provider(<<"B">>, [jwks_uri]), + ?assertEqual(build_url_to_oauth_provider(<<"/B/keys">>), OAuthProvider#oauth_provider.jwks_uri). get_oauth_provider_should_return_root_oauth_provider_with_all_discovered_endpoints(_Config) -> - {ok, OAuthProvider} = rabbit_oauth2_config:get_oauth_provider_for_resource_server_id(?RABBITMQ, [jwks_uri]), - ?assertEqual(build_url_to_oauth_provider(<<"/keys">>), OAuthProvider#oauth_provider.jwks_uri), - ?assertEqual(build_url_to_oauth_provider(<<"/">>), OAuthProvider#oauth_provider.issuer). + root = rabbit_oauth2_config:get_oauth_provider_id_for_resource_server_id(?RABBITMQ), + {ok, OAuthProvider} = rabbit_oauth2_config:get_oauth_provider(root, [jwks_uri]), + ?assertEqual(build_url_to_oauth_provider(<<"/keys">>), OAuthProvider#oauth_provider.jwks_uri), + ?assertEqual(build_url_to_oauth_provider(<<"/">>), OAuthProvider#oauth_provider.issuer). append_paths(Path1, Path2) -> - erlang:iolist_to_binary([Path1, Path2]). + erlang:iolist_to_binary([Path1, Path2]). get_oauth_provider_should_return_oauth_provider_B_with_jwks_uri(_Config) -> - {ok, OAuthProvider} = rabbit_oauth2_config:get_oauth_provider_for_resource_server_id(?RABBITMQ, [jwks_uri]), - ?assertEqual(build_url_to_oauth_provider(<<"/B/keys">>), OAuthProvider#oauth_provider.jwks_uri). + <<"B">> = rabbit_oauth2_config:get_oauth_provider_id_for_resource_server_id(?RABBITMQ), + {ok, OAuthProvider} = rabbit_oauth2_config:get_oauth_provider(<<"B">>, [jwks_uri]), + ?assertEqual(build_url_to_oauth_provider(<<"/B/keys">>), OAuthProvider#oauth_provider.jwks_uri). get_oauth_provider_should_return_oauth_provider_B_with_all_discovered_endpoints(_Config) -> - {ok, OAuthProvider} = rabbit_oauth2_config:get_oauth_provider_for_resource_server_id(?RABBITMQ, [jwks_uri]), - ?assertEqual(build_url_to_oauth_provider(<<"/B/keys">>), OAuthProvider#oauth_provider.jwks_uri), - ?assertEqual(build_url_to_oauth_provider(<<"/B">>), OAuthProvider#oauth_provider.issuer). + <<"B">> = rabbit_oauth2_config:get_oauth_provider_id_for_resource_server_id(?RABBITMQ), + {ok, OAuthProvider} = rabbit_oauth2_config:get_oauth_provider(<<"B">>, [jwks_uri]), + ?assertEqual(build_url_to_oauth_provider(<<"/B/keys">>), OAuthProvider#oauth_provider.jwks_uri), + ?assertEqual(build_url_to_oauth_provider(<<"/B">>), OAuthProvider#oauth_provider.issuer). get_oauth_provider_should_return_oauth_provider_A_with_jwks_uri(_Config) -> - {ok, OAuthProvider} = rabbit_oauth2_config:get_oauth_provider_for_resource_server_id(?RABBITMQ, [jwks_uri]), - ?assertEqual(build_url_to_oauth_provider(<<"/A/keys">>), OAuthProvider#oauth_provider.jwks_uri). + <<"A">> = rabbit_oauth2_config:get_oauth_provider_id_for_resource_server_id(?RABBITMQ), + {ok, OAuthProvider} = rabbit_oauth2_config:get_oauth_provider(<<"A">>, [jwks_uri]), + ?assertEqual(build_url_to_oauth_provider(<<"/A/keys">>), OAuthProvider#oauth_provider.jwks_uri). get_oauth_provider_should_return_oauth_provider_A_with_all_discovered_endpoints(_Config) -> - {ok, OAuthProvider} = rabbit_oauth2_config:get_oauth_provider_for_resource_server_id(?RABBITMQ, [jwks_uri]), - ?assertEqual(build_url_to_oauth_provider(<<"/A/keys">>), OAuthProvider#oauth_provider.jwks_uri), - ?assertEqual(build_url_to_oauth_provider(<<"/A">>), OAuthProvider#oauth_provider.issuer). + <<"A">> = rabbit_oauth2_config:get_oauth_provider_id_for_resource_server_id(?RABBITMQ), + {ok, OAuthProvider} = rabbit_oauth2_config:get_oauth_provider(<<"A">>, [jwks_uri]), + ?assertEqual(build_url_to_oauth_provider(<<"/A/keys">>), OAuthProvider#oauth_provider.jwks_uri), + ?assertEqual(build_url_to_oauth_provider(<<"/A">>), OAuthProvider#oauth_provider.issuer). get_openid_configuration_expectations() -> [ {get_root_openid_configuration, @@ -675,31 +925,31 @@ get_openid_configuration_expectations() -> ]. start_https_oauth_server(Port, CertsDir, Expectations) when is_list(Expectations) -> - Dispatch = cowboy_router:compile([ - {'_', [{Path, oauth2_http_mock, Expected} || #{request := #{path := Path}} = Expected <- Expectations ]} - ]), - ct:log("start_https_oauth_server (port:~p) with expectation list : ~p -> dispatch: ~p", [Port, Expectations, Dispatch]), - {ok, Pid} = cowboy:start_tls( - mock_http_auth_listener, + Dispatch = cowboy_router:compile([ + {'_', [{Path, oauth2_http_mock, Expected} || #{request := #{path := Path}} = Expected <- Expectations ]} + ]), + ct:log("start_https_oauth_server (port:~p) with expectation list : ~p -> dispatch: ~p", [Port, Expectations, Dispatch]), + {ok, Pid} = cowboy:start_tls( + mock_http_auth_listener, [{port, Port}, {certfile, filename:join([CertsDir, "server", "cert.pem"])}, {keyfile, filename:join([CertsDir, "server", "key.pem"])} ], #{env => #{dispatch => Dispatch}}), - ct:log("Started on Port ~p and pid ~p", [ranch:get_port(mock_http_auth_listener), Pid]). + ct:log("Started on Port ~p and pid ~p", [ranch:get_port(mock_http_auth_listener), Pid]). build_url_to_oauth_provider(Path) -> - uri_string:recompose(#{scheme => "https", + uri_string:recompose(#{scheme => "https", host => "localhost", port => rabbit_data_coercion:to_integer(?AUTH_PORT), path => Path}). stop_http_auth_server() -> - cowboy:stop_listener(mock_http_auth_listener). + cowboy:stop_listener(mock_http_auth_listener). -spec ssl_options(ssl:verify_type(), boolean(), file:filename()) -> list(). ssl_options(PeerVerification, FailIfNoPeerCert, CaCertFile) -> - [{verify, PeerVerification}, + [{verify, PeerVerification}, {depth, 10}, {fail_if_no_peer_cert, FailIfNoPeerCert}, {crl_check, false}, diff --git a/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_schema_SUITE.erl b/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_schema_SUITE.erl new file mode 100644 index 000000000000..58e69c334d83 --- /dev/null +++ b/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_schema_SUITE.erl @@ -0,0 +1,183 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% +-module(rabbit_oauth2_schema_SUITE). + +-compile(export_all). + +-include_lib("rabbit_common/include/rabbit.hrl"). +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). + + +all() -> + [ + test_without_oauth_providers, + test_with_one_oauth_provider, + test_with_many_oauth_providers, + test_oauth_providers_attributes, + test_oauth_providers_attributes_with_invalid_uri, + test_oauth_providers_algorithms, + test_oauth_providers_https, + test_oauth_providers_https_with_missing_cacertfile, + test_oauth_providers_signing_keys, + test_without_resource_servers, + test_with_one_resource_server, + test_with_many_resource_servers, + test_resource_servers_attributes + + ]. + + +test_without_oauth_providers(_) -> + #{} = rabbit_oauth2_schema:translate_oauth_providers([]). + +test_without_resource_servers(_) -> + #{} = rabbit_oauth2_schema:translate_resource_servers([]). + +test_with_one_oauth_provider(_) -> + Conf = [{["auth_oauth2","oauth_providers","keycloak","issuer"],"https://rabbit"} + ], + #{<<"keycloak">> := [{issuer, <<"https://rabbit">>}] + } = rabbit_oauth2_schema:translate_oauth_providers(Conf). + +test_with_one_resource_server(_) -> + Conf = [{["auth_oauth2","resource_servers","rabbitmq1","id"],"rabbitmq1"} + ], + #{<<"rabbitmq1">> := [{id, <<"rabbitmq1">>}] + } = rabbit_oauth2_schema:translate_resource_servers(Conf). + +test_with_many_oauth_providers(_) -> + Conf = [{["auth_oauth2","oauth_providers","keycloak","issuer"],"https://keycloak"}, + {["auth_oauth2","oauth_providers","uaa","issuer"],"https://uaa"} + ], + #{<<"keycloak">> := [{issuer, <<"https://keycloak">>} + ], + <<"uaa">> := [{issuer, <<"https://uaa">>} + ] + } = rabbit_oauth2_schema:translate_oauth_providers(Conf). + + +test_with_many_resource_servers(_) -> + Conf = [{["auth_oauth2","resource_servers","rabbitmq1","id"],"rabbitmq1"}, + {["auth_oauth2","resource_servers","rabbitmq2","id"],"rabbitmq2"} + ], + #{<<"rabbitmq1">> := [{id, <<"rabbitmq1">>} + ], + <<"rabbitmq2">> := [{id, <<"rabbitmq2">>} + ] + } = rabbit_oauth2_schema:translate_resource_servers(Conf). + +test_oauth_providers_attributes(_) -> + Conf = [{["auth_oauth2","oauth_providers","keycloak","issuer"],"https://keycloak"}, + {["auth_oauth2","oauth_providers","keycloak","default_key"],"token-key"} + ], + #{<<"keycloak">> := [{default_key, <<"token-key">>}, + {issuer, <<"https://keycloak">>} + ] + } = sort_settings(rabbit_oauth2_schema:translate_oauth_providers(Conf)). + +test_resource_servers_attributes(_) -> + Conf = [{["auth_oauth2","resource_servers","rabbitmq1","id"],"rabbitmq1xxx"}, + {["auth_oauth2","resource_servers","rabbitmq1","scope_prefix"],"somescope."}, + {["auth_oauth2","resource_servers","rabbitmq1","additional_scopes_key"],"roles"}, + {["auth_oauth2","resource_servers","rabbitmq1","preferred_username_claims","1"],"userid"}, + {["auth_oauth2","resource_servers","rabbitmq1","preferred_username_claims","2"],"groupid"} + ], + #{<<"rabbitmq1xxx">> := [{additional_scopes_key, <<"roles">>}, + {id, <<"rabbitmq1xxx">>}, + {preferred_username_claims, [<<"userid">>, <<"groupid">>]}, + {scope_prefix, <<"somescope.">>} + ] + } = sort_settings(rabbit_oauth2_schema:translate_resource_servers(Conf)), + + Conf2 = [ + {["auth_oauth2","resource_servers","rabbitmq1","scope_prefix"],"somescope."}, + {["auth_oauth2","resource_servers","rabbitmq1","additional_scopes_key"],"roles"}, + {["auth_oauth2","resource_servers","rabbitmq1","preferred_username_claims","1"],"userid"}, + {["auth_oauth2","resource_servers","rabbitmq1","preferred_username_claims","2"],"groupid"} + ], + #{<<"rabbitmq1">> := [{additional_scopes_key, <<"roles">>}, + {id, <<"rabbitmq1">>}, + {preferred_username_claims, [<<"userid">>, <<"groupid">>]}, + {scope_prefix, <<"somescope.">>} + ] + } = sort_settings(rabbit_oauth2_schema:translate_resource_servers(Conf2)). + +test_oauth_providers_attributes_with_invalid_uri(_) -> + Conf = [{["auth_oauth2","oauth_providers","keycloak","issuer"],"http://keycloak"}, + {["auth_oauth2","oauth_providers","keycloak","default_key"],"token-key"} + ], + try sort_settings(rabbit_oauth2_schema:translate_oauth_providers(Conf)) of + _ -> {throw, should_have_failed} + catch + _ -> ok + end. + +test_oauth_providers_algorithms(_) -> + Conf = [{["auth_oauth2","oauth_providers","keycloak","issuer"],"https://keycloak"}, + {["auth_oauth2","oauth_providers","keycloak","algorithms","2"],"HS256"}, + {["auth_oauth2","oauth_providers","keycloak","algorithms","1"],"RS256"} + ], + #{<<"keycloak">> := [{algorithms, [<<"RS256">>, <<"HS256">>]}, + {issuer, <<"https://keycloak">>} + ] + } = sort_settings(rabbit_oauth2_schema:translate_oauth_providers(Conf)). + +test_oauth_providers_https(Conf) -> + + CuttlefishConf = [{["auth_oauth2","oauth_providers","keycloak","issuer"],"https://keycloak"}, + {["auth_oauth2","oauth_providers","keycloak","https","verify"],verify_none}, + {["auth_oauth2","oauth_providers","keycloak","https","peer_verification"],verify_peer}, + {["auth_oauth2","oauth_providers","keycloak","https","depth"],2}, + {["auth_oauth2","oauth_providers","keycloak","https","hostname_verification"],wildcard}, + {["auth_oauth2","oauth_providers","keycloak","https","crl_check"],false}, + {["auth_oauth2","oauth_providers","keycloak","https","fail_if_no_peer_cert"],true}, + {["auth_oauth2","oauth_providers","keycloak","https","cacertfile"],cert_filename(Conf)} + ], + #{<<"keycloak">> := [{https, [{verify, verify_none}, + {peer_verification, verify_peer}, + {depth, 2}, + {hostname_verification, wildcard}, + {crl_check, false}, + {fail_if_no_peer_cert, true}, + {cacertfile, _CaCertFile} + ]}, + {issuer, <<"https://keycloak">>} + ] + } = sort_settings(rabbit_oauth2_schema:translate_oauth_providers(CuttlefishConf)). + +test_oauth_providers_https_with_missing_cacertfile(_) -> + + Conf = [{["auth_oauth2","oauth_providers","keycloak","issuer"],"https://keycloak"}, + {["auth_oauth2","oauth_providers","keycloak","https","cacertfile"],"/non-existent.pem"} + ], + try sort_settings(rabbit_oauth2_schema:translate_oauth_providers(Conf)) of + _ -> {throw, should_have_failed} + catch + _ -> ok + end. + +test_oauth_providers_signing_keys(Conf) -> + CuttlefishConf = [{["auth_oauth2","oauth_providers","keycloak","issuer"],"https://keycloak"}, + {["auth_oauth2","oauth_providers","keycloak","signing_keys","2"], cert_filename(Conf)}, + {["auth_oauth2","oauth_providers","keycloak","signing_keys","1"], cert_filename(Conf)} + ], + #{<<"keycloak">> := [{issuer, <<"https://keycloak">>}, + {signing_keys, SigningKeys} + ] + } = sort_settings(rabbit_oauth2_schema:translate_oauth_providers(CuttlefishConf)), + ct:log("SigningKey: ~p", [SigningKeys]), + #{<<"1">> := {pem, <<"I'm not a certificate">>}, + <<"2">> := {pem, <<"I'm not a certificate">>} + } = SigningKeys. + +cert_filename(Conf) -> + string:concat(?config(data_dir, Conf), "certs/cert.pem"). + +sort_settings(MapOfListOfSettings) -> + maps:map(fun(_K,List) -> + lists:sort(fun({K1,_}, {K2,_}) -> K1 < K2 end, List) end, MapOfListOfSettings). diff --git a/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_schema_SUITE_data/certs/cacert.pem b/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_schema_SUITE_data/certs/cacert.pem new file mode 100644 index 000000000000..eaf6b67806ce --- /dev/null +++ b/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_schema_SUITE_data/certs/cacert.pem @@ -0,0 +1 @@ +I'm not a certificate diff --git a/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_schema_SUITE_data/certs/cert.pem b/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_schema_SUITE_data/certs/cert.pem new file mode 100644 index 000000000000..eaf6b67806ce --- /dev/null +++ b/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_schema_SUITE_data/certs/cert.pem @@ -0,0 +1 @@ +I'm not a certificate diff --git a/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_schema_SUITE_data/certs/key.pem b/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_schema_SUITE_data/certs/key.pem new file mode 100644 index 000000000000..eaf6b67806ce --- /dev/null +++ b/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_schema_SUITE_data/certs/key.pem @@ -0,0 +1 @@ +I'm not a certificate diff --git a/deps/rabbitmq_auth_backend_oauth2/test/unit_SUITE.erl b/deps/rabbitmq_auth_backend_oauth2/test/unit_SUITE.erl index 2efc81f0fe98..d02de0f3cd60 100644 --- a/deps/rabbitmq_auth_backend_oauth2/test/unit_SUITE.erl +++ b/deps/rabbitmq_auth_backend_oauth2/test/unit_SUITE.erl @@ -59,6 +59,7 @@ groups() -> test_successful_access_with_a_token_that_uses_multiple_scope_aliases_in_extra_scope_source_field, test_post_process_token_payload_complex_claims, test_successful_access_with_a_token_that_uses_single_scope_alias_in_scope_field_and_custom_scope_prefix + ]} ]. @@ -1128,7 +1129,7 @@ test_incorrect_kid(_) -> Username = <<"username">>, Jwk = ?UTIL_MOD:fixture_jwk(), application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>), - Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk, AltKid), + Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk, AltKid, true), ?assertMatch({refused, "Authentication using an OAuth 2/JWT token failed: ~tp", [{error,{missing_oauth_provider_attributes, [issuer]}}]}, rabbit_auth_backend_oauth2:user_login_authentication(Username, #{password => Token})).