summaryrefslogtreecommitdiff
path: root/deps/rabbitmq_auth_backend_oauth2/test
diff options
context:
space:
mode:
authordcorbacho <dparracorbacho@piotal.io>2020-11-18 14:27:41 +0000
committerdcorbacho <dparracorbacho@piotal.io>2020-11-18 14:27:41 +0000
commitf23a51261d9502ec39df0f8db47ba6b22aa7659f (patch)
tree53dcdf46e7dc2c14e81ee960bce8793879b488d3 /deps/rabbitmq_auth_backend_oauth2/test
parentafa2c2bf6c7e0e9b63f4fb53dc931c70388e1c82 (diff)
parent9f6d64ec4a4b1eeac24d7846c5c64fd96798d892 (diff)
downloadrabbitmq-server-git-stream-timestamp-offset.tar.gz
Merge remote-tracking branch 'origin/master' into stream-timestamp-offsetstream-timestamp-offset
Diffstat (limited to 'deps/rabbitmq_auth_backend_oauth2/test')
-rw-r--r--deps/rabbitmq_auth_backend_oauth2/test/add_uaa_key_command_SUITE.erl75
-rw-r--r--deps/rabbitmq_auth_backend_oauth2/test/rabbit_auth_backend_oauth2_test_util.erl99
-rw-r--r--deps/rabbitmq_auth_backend_oauth2/test/scope_SUITE.erl341
-rw-r--r--deps/rabbitmq_auth_backend_oauth2/test/system_SUITE.erl373
-rw-r--r--deps/rabbitmq_auth_backend_oauth2/test/unit_SUITE.erl551
-rw-r--r--deps/rabbitmq_auth_backend_oauth2/test/wildcard_match_SUITE.erl105
6 files changed, 1544 insertions, 0 deletions
diff --git a/deps/rabbitmq_auth_backend_oauth2/test/add_uaa_key_command_SUITE.erl b/deps/rabbitmq_auth_backend_oauth2/test/add_uaa_key_command_SUITE.erl
new file mode 100644
index 0000000000..ba46715db1
--- /dev/null
+++ b/deps/rabbitmq_auth_backend_oauth2/test/add_uaa_key_command_SUITE.erl
@@ -0,0 +1,75 @@
+%% 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-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+-module(add_uaa_key_command_SUITE).
+
+-compile(export_all).
+
+-include_lib("rabbit_common/include/rabbit.hrl").
+-include_lib("common_test/include/ct.hrl").
+
+-define(COMMAND, 'Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand').
+
+all() ->
+ [validate_arguments,
+ validate_json_key,
+ validate_pem_key,
+ validate_pem_file_key
+ ].
+
+
+init_per_suite(Config) ->
+ rabbit_ct_helpers:run_setup_steps(Config, []).
+
+end_per_suite(Config) ->
+ rabbit_ct_helpers:run_teardown_steps(Config, []).
+
+
+validate_arguments(_) ->
+ {validation_failure, too_many_args} =
+ ?COMMAND:validate([<<"one">>, <<"two">>], #{json => <<"{}">>}),
+ {validation_failure, not_enough_args} =
+ ?COMMAND:validate([], #{json => <<"{}">>}),
+ {validation_failure, {bad_argument, <<"No key specified">>}} =
+ ?COMMAND:validate([<<"foo">>], #{}),
+ {validation_failure, {bad_argument, <<"There can be only one key type">>}} =
+ ?COMMAND:validate([<<"foo">>], #{json => <<"{}">>, pem => <<"pem">>}),
+ {validation_failure, {bad_argument, <<"There can be only one key type">>}} =
+ ?COMMAND:validate([<<"foo">>], #{json => <<"{}">>, pem_file => <<"/tmp/key.pem">>}),
+ {validation_failure, {bad_argument, <<"There can be only one key type">>}} =
+ ?COMMAND:validate([<<"foo">>], #{pem => <<"pem">>, pem_file => <<"/tmp/key.pem">>}).
+
+validate_json_key(_) ->
+ {validation_failure, {bad_argument, <<"Invalid JSON">>}} =
+ ?COMMAND:validate([<<"foo">>], #{json => <<"foobar">>}),
+ {validation_failure, {bad_argument, <<"Json key should contain \"kty\" field">>}} =
+ ?COMMAND:validate([<<"foo">>], #{json => <<"{}">>}),
+ {validation_failure, {bad_argument, _}} =
+ ?COMMAND:validate([<<"foo">>], #{json => <<"{\"kty\": \"oct\"}">>}),
+ ValidJson = <<"{\"alg\":\"HS256\",\"k\":\"dG9rZW5rZXk\",\"kid\":\"token-key\",\"kty\":\"oct\",\"use\":\"sig\",\"value\":\"tokenkey\"}">>,
+ ok = ?COMMAND:validate([<<"foo">>], #{json => ValidJson}).
+
+validate_pem_key(Config) ->
+ {validation_failure, <<"Unable to read a key from the PEM string">>} =
+ ?COMMAND:validate([<<"foo">>], #{pem => <<"not a key">>}),
+ CertsDir = ?config(rmq_certsdir, Config),
+ Keyfile = filename:join([CertsDir, <<"client">>, <<"key.pem">>]),
+ {ok, Key} = file:read_file(Keyfile),
+ ok = ?COMMAND:validate([<<"foo">>], #{pem => Key}).
+
+validate_pem_file_key(Config) ->
+ {validation_failure, {bad_argument, <<"PEM file not found">>}} =
+ ?COMMAND:validate([<<"foo">>], #{pem_file => <<"non_existent_file">>}),
+ file:write_file("empty.pem", <<"">>),
+ {validation_failure, <<"Unable to read a key from the PEM file">>} =
+ ?COMMAND:validate([<<"foo">>], #{pem_file => <<"empty.pem">>}),
+ file:write_file("not_pem.pem", <<"">>),
+ {validation_failure, _} =
+ ?COMMAND:validate([<<"foo">>], #{pem_file => <<"not_pem.pem">>}),
+ CertsDir = ?config(rmq_certsdir, Config),
+ Keyfile = filename:join([CertsDir, <<"client">>, <<"key.pem">>]),
+ ok = ?COMMAND:validate([<<"foo">>], #{pem_file => Keyfile}).
+
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
new file mode 100644
index 0000000000..5b8ed5f837
--- /dev/null
+++ b/deps/rabbitmq_auth_backend_oauth2/test/rabbit_auth_backend_oauth2_test_util.erl
@@ -0,0 +1,99 @@
+%% 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-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+-module(rabbit_auth_backend_oauth2_test_util).
+
+-compile(export_all).
+
+-define(DEFAULT_EXPIRATION_IN_SECONDS, 2).
+
+%%
+%% API
+%%
+
+sign_token_hs(Token, #{<<"kid">> := TokenKey} = Jwk) ->
+ sign_token_hs(Token, Jwk, TokenKey).
+
+sign_token_hs(Token, Jwk, TokenKey) ->
+ Jws = #{
+ <<"alg">> => <<"HS256">>,
+ <<"kid">> => TokenKey
+ },
+ sign_token(Token, Jwk, Jws).
+
+sign_token_rsa(Token, Jwk, TokenKey) ->
+ Jws = #{
+ <<"alg">> => <<"RS256">>,
+ <<"kid">> => TokenKey
+ },
+ sign_token(Token, Jwk, Jws).
+
+sign_token_no_kid(Token, Jwk) ->
+ Signed = jose_jwt:sign(Jwk, Token),
+ jose_jws:compact(Signed).
+
+sign_token(Token, Jwk, Jws) ->
+ Signed = jose_jwt:sign(Jwk, Jws, Token),
+ jose_jws:compact(Signed).
+
+fixture_jwk() ->
+ #{<<"alg">> => <<"HS256">>,
+ <<"k">> => <<"dG9rZW5rZXk">>,
+ <<"kid">> => <<"token-key">>,
+ <<"kty">> => <<"oct">>,
+ <<"use">> => <<"sig">>,
+ <<"value">> => <<"tokenkey">>}.
+
+full_permission_scopes() ->
+ [<<"rabbitmq.configure:*/*">>,
+ <<"rabbitmq.write:*/*">>,
+ <<"rabbitmq.read:*/*">>].
+
+expirable_token() ->
+ expirable_token(?DEFAULT_EXPIRATION_IN_SECONDS).
+
+expirable_token(Seconds) ->
+ TokenPayload = fixture_token(),
+ %% expiration is a timestamp with precision in seconds
+ TokenPayload#{<<"exp">> := os:system_time(seconds) + Seconds}.
+
+wait_for_token_to_expire() ->
+ timer:sleep(timer:seconds(?DEFAULT_EXPIRATION_IN_SECONDS)).
+
+wait_for_token_to_expire(DurationInMs) ->
+ timer:sleep(DurationInMs).
+
+expired_token() ->
+ expired_token_with_scopes(full_permission_scopes()).
+
+expired_token_with_scopes(Scopes) ->
+ token_with_scopes_and_expiration(Scopes, os:system_time(seconds) - 10).
+
+fixture_token_with_scopes(Scopes) ->
+ token_with_scopes_and_expiration(Scopes, os:system_time(seconds) + 10).
+
+token_with_scopes_and_expiration(Scopes, Expiration) ->
+ %% expiration is a timestamp with precision in seconds
+ #{<<"exp">> => Expiration,
+ <<"kid">> => <<"token-key">>,
+ <<"iss">> => <<"unit_test">>,
+ <<"foo">> => <<"bar">>,
+ <<"aud">> => [<<"rabbitmq">>],
+ <<"scope">> => Scopes}.
+
+fixture_token() ->
+ fixture_token([]).
+
+fixture_token(ExtraScopes) ->
+ Scopes = [<<"rabbitmq.configure:vhost/foo">>,
+ <<"rabbitmq.write:vhost/foo">>,
+ <<"rabbitmq.read:vhost/foo">>,
+ <<"rabbitmq.read:vhost/bar">>,
+ <<"rabbitmq.read:vhost/bar/%23%2Ffoo">>] ++ ExtraScopes,
+ fixture_token_with_scopes(Scopes).
+
+fixture_token_with_full_permissions() ->
+ fixture_token_with_scopes(full_permission_scopes()).
diff --git a/deps/rabbitmq_auth_backend_oauth2/test/scope_SUITE.erl b/deps/rabbitmq_auth_backend_oauth2/test/scope_SUITE.erl
new file mode 100644
index 0000000000..1338f28f50
--- /dev/null
+++ b/deps/rabbitmq_auth_backend_oauth2/test/scope_SUITE.erl
@@ -0,0 +1,341 @@
+%% 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-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+-module(scope_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() ->
+ [
+ permission_all,
+ permission_vhost,
+ permission_resource,
+ permission_topic
+ ].
+
+permission_all(_Config) ->
+ WildcardScopeWrite = <<"write:*/*">>,
+ WildcardScopeWriteTopic = <<"write:*/*/*">>,
+ WildcardScopeRead = <<"read:*/*">>,
+ WildcardScopeReadTopic = <<"read:*/*/*">>,
+ WildcardScopeConfigure = <<"configure:*/*">>,
+ WildcardScopeConfigureTopic = <<"configure:*/*/*">>,
+
+ ReadScopes = [WildcardScopeRead, WildcardScopeReadTopic],
+ WriteScopes = [WildcardScopeWrite, WildcardScopeWriteTopic],
+ ConfigureScopes = [WildcardScopeConfigure, WildcardScopeConfigureTopic],
+
+ ExampleVhosts = [<<"/">>, <<"foo">>, <<"*">>, <<"foo/bar">>, <<"юникод"/utf8>>],
+ ExampleResources = [<<"foo">>, <<"foo/bar">>, <<"*">>, <<"*/*">>, <<"юникод"/utf8>>],
+
+
+ [ vhost_allowed(<<"/">>, Scope) ||
+ Scope <- ReadScopes ++ WriteScopes ++ ConfigureScopes ],
+ [ vhost_allowed(<<"foo">>, Scope) ||
+ Scope <- ReadScopes ++ WriteScopes ++ ConfigureScopes ],
+ [ vhost_allowed(<<"*">>, Scope) ||
+ Scope <- ReadScopes ++ WriteScopes ++ ConfigureScopes ],
+ [ vhost_allowed(<<"foo/bar">>, Scope) ||
+ Scope <- ReadScopes ++ WriteScopes ++ ConfigureScopes ],
+ [ vhost_allowed(<<"юникод"/utf8>>, Scope) ||
+ Scope <- ReadScopes ++ WriteScopes ++ ConfigureScopes ],
+
+ [ read_allowed(Vhost, Resource, Scope) ||
+ Scope <- ReadScopes,
+ Vhost <- ExampleVhosts,
+ Resource <- ExampleResources ],
+
+ [ write_allowed(Vhost, Resource, Scope) ||
+ Scope <- WriteScopes,
+ Vhost <- ExampleVhosts,
+ Resource <- ExampleResources ],
+
+ [ configure_allowed(Vhost, Resource, Scope) ||
+ Scope <- ConfigureScopes,
+ Vhost <- ExampleVhosts,
+ Resource <- ExampleResources ],
+
+ [ read_refused(Vhost, Resource, Scope) ||
+ Scope <- WriteScopes ++ ConfigureScopes,
+ Vhost <- ExampleVhosts,
+ Resource <- ExampleResources ],
+
+ [ write_refused(Vhost, Resource, Scope) ||
+ Scope <- ReadScopes ++ ConfigureScopes,
+ Vhost <- ExampleVhosts,
+ Resource <- ExampleResources ],
+
+ [ configure_refused(Vhost, Resource, Scope) ||
+ Scope <- WriteScopes ++ ReadScopes,
+ Vhost <- ExampleVhosts,
+ Resource <- ExampleResources ].
+
+permission_vhost(_Config) ->
+ FooScopeWrite = <<"write:foo/*">>,
+ FooScopeWriteTopic = <<"write:foo/*/*">>,
+ FooScopeRead = <<"read:foo/*">>,
+ FooScopeReadTopic = <<"read:foo/*/*">>,
+ FooScopeConfigure = <<"configure:foo/*">>,
+ FooScopeConfigureTopic = <<"configure:foo/*/*">>,
+
+ ComplexVHost = <<"foo/bar/*/">>,
+ EncodedVhost = cow_qs:urlencode(ComplexVHost),
+
+ EncodedScopeWrite = <<"write:", EncodedVhost/binary, "/*">>,
+ EncodedScopeWriteTopic = <<"write:", EncodedVhost/binary, "/*/*">>,
+ EncodedScopeRead = <<"read:", EncodedVhost/binary, "/*">>,
+ EncodedScopeReadTopic = <<"read:", EncodedVhost/binary, "/*/*">>,
+ EncodedScopeConfigure = <<"configure:", EncodedVhost/binary, "/*">>,
+ EncodedScopeConfigureTopic = <<"configure:", EncodedVhost/binary, "/*/*">>,
+
+ FooReadScopes = [FooScopeRead, FooScopeReadTopic],
+ EncodedReadScopes = [EncodedScopeRead, EncodedScopeReadTopic],
+
+ FooWriteScopes = [FooScopeWrite, FooScopeWriteTopic],
+ EncodedWriteScopes = [EncodedScopeWrite, EncodedScopeWriteTopic],
+
+ FooConfigureScopes = [FooScopeConfigure, FooScopeConfigureTopic],
+ EncodedConfigureScopes = [EncodedScopeConfigure, EncodedScopeConfigureTopic],
+
+ ExampleResources = [<<"foo">>, <<"foo/bar">>, <<"*">>, <<"*/*">>, <<"юникод"/utf8>>],
+
+ Tags = [<<"tag:management">>, <<"tag:policymaker">>],
+
+ [ vhost_allowed(<<"foo">>, Scope) ||
+ Scope <- FooReadScopes ++ FooWriteScopes ++ FooConfigureScopes ],
+ [ vhost_allowed(ComplexVHost, [Scope] ++ Tags) ||
+ Scope <- EncodedReadScopes ++ EncodedWriteScopes ++ EncodedConfigureScopes ],
+
+ [ read_allowed(<<"foo">>, Resource, [Scope] ++ Tags) ||
+ Scope <- FooReadScopes,
+ Resource <- ExampleResources ],
+
+ [ write_allowed(<<"foo">>, Resource, [Scope] ++ Tags) ||
+ Scope <- FooWriteScopes,
+ Resource <- ExampleResources ],
+
+ [ configure_allowed(<<"foo">>, Resource, [Scope] ++ Tags) ||
+ Scope <- FooConfigureScopes,
+ Resource <- ExampleResources ],
+
+ [ read_refused(<<"foo">>, Resource, [Scope] ++ Tags) ||
+ Scope <- FooWriteScopes ++ FooConfigureScopes ++
+ EncodedWriteScopes ++ EncodedConfigureScopes,
+ Resource <- ExampleResources ],
+
+ [ write_refused(<<"foo">>, Resource, [Scope] ++ Tags) ||
+ Scope <- FooReadScopes ++ FooConfigureScopes ++
+ EncodedReadScopes ++ EncodedConfigureScopes,
+ Resource <- ExampleResources ],
+
+ [ configure_refused(<<"foo">>, Resource, [Scope] ++ Tags) ||
+ Scope <- FooWriteScopes ++ FooReadScopes ++
+ EncodedWriteScopes ++ EncodedReadScopes,
+ Resource <- ExampleResources ],
+
+ [ read_allowed(ComplexVHost, Resource, Scope) ||
+ Scope <- EncodedReadScopes,
+ Resource <- ExampleResources ],
+
+ [ write_allowed(ComplexVHost, Resource, Scope) ||
+ Scope <- EncodedWriteScopes,
+ Resource <- ExampleResources ],
+
+ [ configure_allowed(ComplexVHost, Resource, Scope) ||
+ Scope <- EncodedConfigureScopes,
+ Resource <- ExampleResources ],
+
+ [ read_refused(ComplexVHost, Resource, Scope) ||
+ Scope <- EncodedWriteScopes ++ EncodedConfigureScopes ++
+ FooWriteScopes ++ FooConfigureScopes,
+ Resource <- ExampleResources ],
+
+ [ write_refused(ComplexVHost, Resource, Scope) ||
+ Scope <- EncodedReadScopes ++ EncodedConfigureScopes ++
+ FooReadScopes ++ FooConfigureScopes,
+ Resource <- ExampleResources ],
+
+ [ configure_refused(ComplexVHost, Resource, Scope) ||
+ Scope <- EncodedWriteScopes ++ EncodedReadScopes ++
+ FooWriteScopes ++ FooReadScopes,
+ Resource <- ExampleResources ].
+
+permission_resource(_Config) ->
+ ComplexResource = <<"bar*/baz">>,
+ EncodedResource = cow_qs:urlencode(ComplexResource),
+
+ ScopeWrite = <<"write:*/", EncodedResource/binary>>,
+ ScopeWriteTopic = <<"write:*/", EncodedResource/binary, "/*">>,
+ ScopeRead = <<"read:*/", EncodedResource/binary>>,
+ ScopeReadTopic = <<"read:*/", EncodedResource/binary, "/*">>,
+ ScopeConfigure = <<"configure:*/", EncodedResource/binary>>,
+ ScopeConfigureTopic = <<"configure:*/", EncodedResource/binary, "/*">>,
+
+ ExampleVhosts = [<<"/">>, <<"foo">>, <<"*">>, <<"foo/bar">>, <<"юникод"/utf8>>],
+ ExampleResources = [<<"foo">>, <<"foo/bar">>, <<"*">>, <<"*/*">>, <<"юникод"/utf8>>],
+
+ %% Resource access is allowed for complex resource with any vhost
+ [ read_allowed(Vhost, ComplexResource, Scope) ||
+ Scope <- [ScopeRead, ScopeReadTopic],
+ Vhost <- ExampleVhosts ],
+ [ write_allowed(Vhost, ComplexResource, Scope) ||
+ Scope <- [ScopeWrite, ScopeWriteTopic],
+ Vhost <- ExampleVhosts ],
+ [ configure_allowed(Vhost, ComplexResource, Scope) ||
+ Scope <- [ScopeConfigure, ScopeConfigureTopic],
+ Vhost <- ExampleVhosts ],
+
+ %% Resource access is refused for any other resource
+ [ read_refused(Vhost, Resource, Scope) ||
+ Scope <- [ScopeWrite, ScopeWriteTopic,
+ ScopeRead, ScopeReadTopic,
+ ScopeConfigure, ScopeConfigureTopic],
+ Vhost <- ExampleVhosts,
+ Resource <- ExampleResources ],
+
+ [ write_refused(Vhost, Resource, Scope) ||
+ Scope <- [ScopeWrite, ScopeWriteTopic,
+ ScopeRead, ScopeReadTopic,
+ ScopeConfigure, ScopeConfigureTopic],
+ Vhost <- ExampleVhosts,
+ Resource <- ExampleResources ],
+
+ [ configure_refused(Vhost, Resource, Scope) ||
+ Scope <- [ScopeWrite, ScopeWriteTopic,
+ ScopeRead, ScopeReadTopic,
+ ScopeConfigure, ScopeConfigureTopic],
+ Vhost <- ExampleVhosts,
+ Resource <- ExampleResources ].
+
+permission_topic(_Config) ->
+ TopicWildcardRead = <<"read:*/*/*">>,
+ TopicVhostRead = <<"read:vhost/*/*">>,
+ TopicResourceRead = <<"read:*/exchange/*">>,
+ TopicRoutingKeyRead = <<"read:*/*/rout">>,
+ TopicRoutingSuffixKeyRead = <<"read:*/*/*rout">>,
+
+ ExampleVhosts = [<<"/">>, <<"foo">>, <<"*">>, <<"foo/bar">>, <<"юникод"/utf8>>],
+ ExampleResources = [<<"foo">>, <<"foo/bar">>, <<"*">>, <<"*/*">>, <<"юникод"/utf8>>],
+ ExampleRoutingKeys = [<<"rout">>, <<"norout">>, <<"some_other">>],
+
+ [ topic_read_allowed(VHost, Resource, RoutingKey, TopicWildcardRead) ||
+ VHost <- ExampleVhosts,
+ Resource <- ExampleResources,
+ RoutingKey <- ExampleRoutingKeys],
+
+ [ topic_read_allowed(<<"vhost">>, Resource, RoutingKey, TopicVhostRead) ||
+ Resource <- ExampleResources,
+ RoutingKey <- ExampleRoutingKeys],
+
+ [ topic_read_allowed(VHost, <<"exchange">>, RoutingKey, TopicResourceRead) ||
+ VHost <- ExampleVhosts,
+ RoutingKey <- ExampleRoutingKeys],
+
+ [ topic_read_allowed(VHost, Resource, <<"rout">>, TopicRoutingKeyRead) ||
+ VHost <- ExampleVhosts,
+ Resource <- ExampleResources],
+
+ [ topic_read_allowed(VHost, Resource, RoutingKey, TopicRoutingSuffixKeyRead) ||
+ VHost <- ExampleVhosts,
+ Resource <- ExampleResources,
+ RoutingKey <- [<<"rout">>, <<"norout">>, <<"sprout">>]],
+
+ [ topic_read_refused(VHost, Resource, RoutingKey, TopicVhostRead) ||
+ VHost <- ExampleVhosts,
+ Resource <- ExampleResources,
+ RoutingKey <- ExampleRoutingKeys],
+
+ [ topic_read_refused(VHost, Resource, RoutingKey, TopicResourceRead) ||
+ VHost <- ExampleVhosts,
+ Resource <- ExampleResources,
+ RoutingKey <- ExampleRoutingKeys],
+
+ [ topic_read_refused(VHost, Resource, RoutingKey, TopicRoutingKeyRead) ||
+ VHost <- ExampleVhosts,
+ Resource <- ExampleResources,
+ RoutingKey <- [<<"foo">>, <<"bar">>]].
+
+vhost_allowed(Vhost, Scopes) when is_list(Scopes) ->
+ ?assertEqual(true, rabbit_oauth2_scope:vhost_access(Vhost, Scopes));
+
+vhost_allowed(Vhost, Scope) ->
+ vhost_allowed(Vhost, [Scope]).
+
+read_allowed(Vhost, Resource, Scopes) when is_list(Scopes) ->
+ resource_perm(Vhost, Resource, Scopes, read, true);
+
+read_allowed(Vhost, Resource, Scope) ->
+ resource_perm(Vhost, Resource, Scope, read, true).
+
+read_refused(Vhost, Resource, Scopes) when is_list(Scopes) ->
+ resource_perm(Vhost, Resource, Scopes, read, false);
+
+read_refused(Vhost, Resource, Scope) ->
+ resource_perm(Vhost, Resource, Scope, read, false).
+
+write_allowed(Vhost, Resource, Scopes) when is_list(Scopes) ->
+ resource_perm(Vhost, Resource, Scopes, write, true);
+
+write_allowed(Vhost, Resource, Scope) ->
+ resource_perm(Vhost, Resource, Scope, write, true).
+
+write_refused(Vhost, Resource, Scopes) when is_list(Scopes) ->
+ resource_perm(Vhost, Resource, Scopes, write, false);
+
+write_refused(Vhost, Resource, Scope) ->
+ resource_perm(Vhost, Resource, Scope, write, false).
+
+configure_allowed(Vhost, Resource, Scopes) when is_list(Scopes) ->
+ resource_perm(Vhost, Resource, Scopes, configure, true);
+
+configure_allowed(Vhost, Resource, Scope) ->
+ resource_perm(Vhost, Resource, Scope, configure, true).
+
+configure_refused(Vhost, Resource, Scopes) when is_list(Scopes) ->
+ resource_perm(Vhost, Resource, Scopes, configure, false);
+
+configure_refused(Vhost, Resource, Scope) ->
+ resource_perm(Vhost, Resource, Scope, configure, false).
+
+
+resource_perm(Vhost, Resource, Scopes, Permission, Result) when is_list(Scopes) ->
+ [ ?assertEqual(Result, rabbit_oauth2_scope:resource_access(
+ #resource{virtual_host = Vhost,
+ kind = Kind,
+ name = Resource},
+ Permission,
+ Scopes)) || Kind <- [queue, exchange] ];
+
+resource_perm(Vhost, Resource, Scope, Permission, Result) ->
+ resource_perm(Vhost, Resource, [Scope], Permission, Result).
+
+topic_read_allowed(Vhost, Resource, RoutingKey, Scopes) when is_list(Scopes) ->
+ topic_perm(Vhost, Resource, RoutingKey, Scopes, read, true);
+
+topic_read_allowed(Vhost, Resource, RoutingKey, Scope) ->
+ topic_perm(Vhost, Resource, RoutingKey, Scope, read, true).
+
+topic_read_refused(Vhost, Resource, RoutingKey, Scopes) when is_list(Scopes) ->
+ topic_perm(Vhost, Resource, RoutingKey, Scopes, read, false);
+
+topic_read_refused(Vhost, Resource, RoutingKey, Scope) ->
+ topic_perm(Vhost, Resource, RoutingKey, Scope, read, false).
+
+topic_perm(Vhost, Resource, RoutingKey, Scopes, Permission, Result) when is_list(Scopes) ->
+ ?assertEqual(Result, rabbit_oauth2_scope:topic_access(
+ #resource{virtual_host = Vhost,
+ kind = topic,
+ name = Resource},
+ Permission,
+ #{routing_key => RoutingKey},
+ Scopes));
+
+topic_perm(Vhost, Resource, RoutingKey, Scope, Permission, Result) ->
+ topic_perm(Vhost, Resource, RoutingKey, [Scope], Permission, Result).
diff --git a/deps/rabbitmq_auth_backend_oauth2/test/system_SUITE.erl b/deps/rabbitmq_auth_backend_oauth2/test/system_SUITE.erl
new file mode 100644
index 0000000000..bb98b469a3
--- /dev/null
+++ b/deps/rabbitmq_auth_backend_oauth2/test/system_SUITE.erl
@@ -0,0 +1,373 @@
+%% 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-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(system_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+-include_lib("eunit/include/eunit.hrl").
+
+-import(rabbit_ct_client_helpers, [close_connection/1, close_channel/1,
+ open_unmanaged_connection/4, open_unmanaged_connection/5,
+ close_connection_and_channel/2]).
+-import(rabbit_mgmt_test_util, [amqp_port/1]).
+
+all() ->
+ [
+ {group, happy_path},
+ {group, unhappy_path}
+ ].
+
+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_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_token_refresh_case1,
+ test_failed_token_refresh_case2
+ ]}
+ ].
+
+%%
+%% Setup and Teardown
+%%
+
+-define(UTIL_MOD, rabbit_auth_backend_oauth2_test_util).
+-define(RESOURCE_SERVER_ID, <<"rabbitmq">>).
+-define(EXTRA_SCOPES_SOURCE, <<"additional_rabbitmq_scopes">>).
+
+init_per_suite(Config) ->
+ rabbit_ct_helpers:log_environment(),
+ rabbit_ct_helpers:run_setup_steps(Config,
+ rabbit_ct_broker_helpers:setup_steps() ++ [
+ fun preconfigure_node/1,
+ fun preconfigure_token/1
+ ]).
+
+end_per_suite(Config) ->
+ rabbit_ct_helpers:run_teardown_steps(Config, rabbit_ct_broker_helpers:teardown_steps()).
+
+
+init_per_group(_Group, 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">>]),
+ Config.
+
+end_per_group(_Group, Config) ->
+ %% The broker is managed by {init,end}_per_testcase().
+ lists:foreach(fun(Value) ->
+ rabbit_ct_broker_helpers:delete_vhost(Config, Value)
+ end,
+ [<<"vhost1">>, <<"vhost2">>, <<"vhost3">>, <<"vhost4">>]),
+ Config.
+
+
+init_per_testcase(Testcase, Config) when Testcase =:= test_successful_connection_with_a_full_permission_token_and_explicitly_configured_vhost orelse
+ Testcase =:= test_successful_token_refresh ->
+ rabbit_ct_broker_helpers:add_vhost(Config, <<"vhost1">>),
+ rabbit_ct_helpers:testcase_started(Config, Testcase),
+ Config;
+
+init_per_testcase(Testcase, Config) when Testcase =:= test_failed_token_refresh_case1 orelse
+ Testcase =:= test_failed_token_refresh_case2 ->
+ rabbit_ct_broker_helpers:add_vhost(Config, <<"vhost4">>),
+ rabbit_ct_helpers:testcase_started(Config, Testcase),
+ Config;
+
+init_per_testcase(Testcase, Config) when Testcase =:= test_successful_connection_with_complex_claim_as_a_map orelse
+ Testcase =:= test_successful_connection_with_complex_claim_as_a_list orelse
+ Testcase =:= test_successful_connection_with_complex_claim_as_a_binary ->
+ ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env,
+ [rabbitmq_auth_backend_oauth2, extra_scopes_source, ?EXTRA_SCOPES_SOURCE]),
+ rabbit_ct_helpers:testcase_started(Config, Testcase),
+ Config;
+
+init_per_testcase(Testcase, Config) ->
+ rabbit_ct_helpers:testcase_started(Config, Testcase),
+ Config.
+
+end_per_testcase(Testcase, Config) when Testcase =:= test_failed_token_refresh_case1 orelse
+ Testcase =:= test_failed_token_refresh_case2 ->
+ rabbit_ct_broker_helpers:delete_vhost(Config, <<"vhost4">>),
+ rabbit_ct_helpers:testcase_started(Config, Testcase),
+ Config;
+
+end_per_testcase(Testcase, Config) when Testcase =:= test_successful_connection_with_complex_claim_as_a_map orelse
+ Testcase =:= test_successful_connection_with_complex_claim_as_a_list orelse
+ Testcase =:= test_successful_connection_with_complex_claim_as_a_binary ->
+ rabbit_ct_broker_helpers:delete_vhost(Config, <<"vhost1">>),
+ ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env,
+ [rabbitmq_auth_backend_oauth2, extra_scopes_source, undefined]),
+ rabbit_ct_helpers:testcase_started(Config, Testcase),
+ Config;
+
+end_per_testcase(Testcase, Config) ->
+ rabbit_ct_broker_helpers:delete_vhost(Config, <<"vhost1">>),
+ rabbit_ct_helpers:testcase_finished(Config, Testcase),
+ Config.
+
+preconfigure_node(Config) ->
+ ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env,
+ [rabbit, auth_backends, [rabbit_auth_backend_oauth2]]),
+ Jwk = ?UTIL_MOD:fixture_jwk(),
+ KeyConfig = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}],
+ ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env,
+ [rabbitmq_auth_backend_oauth2, key_config, KeyConfig]),
+ ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env,
+ [rabbitmq_auth_backend_oauth2, resource_server_id, ?RESOURCE_SERVER_ID]),
+
+ rabbit_ct_helpers:set_config(Config, {fixture_jwk, Jwk}).
+
+generate_valid_token(Config) ->
+ generate_valid_token(Config, ?UTIL_MOD:full_permission_scopes()).
+
+generate_valid_token(Config, Scopes) ->
+ generate_valid_token(Config, Scopes, undefined).
+
+generate_valid_token(Config, Scopes, Audience) ->
+ Jwk = case rabbit_ct_helpers:get_config(Config, fixture_jwk) of
+ undefined -> ?UTIL_MOD:fixture_jwk();
+ Value -> Value
+ end,
+ 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).
+
+generate_valid_token_with_extra_fields(Config, ExtraFields) ->
+ Jwk = case rabbit_ct_helpers:get_config(Config, fixture_jwk) of
+ undefined -> ?UTIL_MOD:fixture_jwk();
+ Value -> Value
+ end,
+ Token = maps:merge(?UTIL_MOD:fixture_token_with_scopes([]), ExtraFields),
+ ?UTIL_MOD:sign_token_hs(Token, Jwk).
+
+generate_expired_token(Config) ->
+ generate_expired_token(Config, ?UTIL_MOD:full_permission_scopes()).
+
+generate_expired_token(Config, Scopes) ->
+ Jwk = case rabbit_ct_helpers:get_config(Config, fixture_jwk) of
+ undefined -> ?UTIL_MOD:fixture_jwk();
+ Value -> Value
+ end,
+ ?UTIL_MOD:sign_token_hs(?UTIL_MOD:expired_token_with_scopes(Scopes), Jwk).
+
+generate_expirable_token(Config, Seconds) ->
+ generate_expirable_token(Config, ?UTIL_MOD:full_permission_scopes(), Seconds).
+
+generate_expirable_token(Config, Scopes, Seconds) ->
+ Jwk = case rabbit_ct_helpers:get_config(Config, fixture_jwk) of
+ undefined -> ?UTIL_MOD:fixture_jwk();
+ Value -> Value
+ end,
+ Expiration = os:system_time(seconds) + Seconds,
+ ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_scopes_and_expiration(Scopes, Expiration), Jwk).
+
+preconfigure_token(Config) ->
+ Token = generate_valid_token(Config),
+ rabbit_ct_helpers:set_config(Config, {fixture_jwt, Token}).
+
+%%
+%% Test Cases
+%%
+
+test_successful_connection_with_a_full_permission_token_and_all_defaults(Config) ->
+ {_Algo, Token} = rabbit_ct_helpers:get_config(Config, fixture_jwt),
+ Conn = open_unmanaged_connection(Config, 0, <<"username">>, Token),
+ {ok, Ch} = amqp_connection:open_channel(Conn),
+ #'queue.declare_ok'{queue = _} =
+ amqp_channel:call(Ch, #'queue.declare'{exclusive = true}),
+ close_connection_and_channel(Conn, Ch).
+
+test_successful_connection_with_a_full_permission_token_and_explicitly_configured_vhost(Config) ->
+ {_Algo, Token} = generate_valid_token(Config, [<<"rabbitmq.configure:vhost1/*">>,
+ <<"rabbitmq.write:vhost1/*">>,
+ <<"rabbitmq.read:vhost1/*">>]),
+ Conn = open_unmanaged_connection(Config, 0, <<"vhost1">>, <<"username">>, Token),
+ {ok, Ch} = amqp_connection:open_channel(Conn),
+ #'queue.declare_ok'{queue = _} =
+ amqp_channel:call(Ch, #'queue.declare'{exclusive = true}),
+ close_connection_and_channel(Conn, Ch).
+
+test_successful_connection_with_simple_strings_for_aud_and_scope(Config) ->
+ {_Algo, Token} = generate_valid_token(
+ Config,
+ <<"rabbitmq.configure:*/* rabbitmq.write:*/* rabbitmq.read:*/*">>,
+ <<"hare rabbitmq">>
+ ),
+ Conn = open_unmanaged_connection(Config, 0, <<"username">>, Token),
+ {ok, Ch} = amqp_connection:open_channel(Conn),
+ #'queue.declare_ok'{queue = _} =
+ amqp_channel:call(Ch, #'queue.declare'{exclusive = true}),
+ close_connection_and_channel(Conn, Ch).
+
+test_successful_connection_with_complex_claim_as_a_map(Config) ->
+ {_Algo, Token} = generate_valid_token_with_extra_fields(
+ Config,
+ #{<<"additional_rabbitmq_scopes">> => #{<<"rabbitmq">> => [<<"configure:*/*">>, <<"read:*/*">>, <<"write:*/*">>]}}
+ ),
+ Conn = open_unmanaged_connection(Config, 0, <<"username">>, Token),
+ {ok, Ch} = amqp_connection:open_channel(Conn),
+ #'queue.declare_ok'{queue = _} =
+ amqp_channel:call(Ch, #'queue.declare'{exclusive = true}),
+ close_connection_and_channel(Conn, Ch).
+
+test_successful_connection_with_complex_claim_as_a_list(Config) ->
+ {_Algo, Token} = generate_valid_token_with_extra_fields(
+ Config,
+ #{<<"additional_rabbitmq_scopes">> => [<<"rabbitmq.configure:*/*">>, <<"rabbitmq.read:*/*">>, <<"rabbitmq.write:*/*">>]}
+ ),
+ Conn = open_unmanaged_connection(Config, 0, <<"username">>, Token),
+ {ok, Ch} = amqp_connection:open_channel(Conn),
+ #'queue.declare_ok'{queue = _} =
+ amqp_channel:call(Ch, #'queue.declare'{exclusive = true}),
+ close_connection_and_channel(Conn, Ch).
+
+test_successful_connection_with_complex_claim_as_a_binary(Config) ->
+ {_Algo, Token} = generate_valid_token_with_extra_fields(
+ Config,
+ #{<<"additional_rabbitmq_scopes">> => <<"rabbitmq.configure:*/* rabbitmq.read:*/*" "rabbitmq.write:*/*">>}
+ ),
+ Conn = open_unmanaged_connection(Config, 0, <<"username">>, Token),
+ {ok, Ch} = amqp_connection:open_channel(Conn),
+ #'queue.declare_ok'{queue = _} =
+ amqp_channel:call(Ch, #'queue.declare'{exclusive = true}),
+ close_connection_and_channel(Conn, Ch).
+
+test_successful_connection_with_keycloak_token(Config) ->
+ {_Algo, Token} = generate_valid_token_with_extra_fields(
+ Config,
+ #{<<"authorization">> => #{<<"permissions">> =>
+ [#{<<"rsid">> => <<"2c390fe4-02ad-41c7-98a2-cebb8c60ccf1">>,
+ <<"rsname">> => <<"allvhost">>,
+ <<"scopes">> => [<<"rabbitmq.configure:*/*">>]},
+ #{<<"rsid">> => <<"e7f12e94-4c34-43d8-b2b1-c516af644cee">>,
+ <<"rsname">> => <<"vhost1">>,
+ <<"scopes">> => [<<"rabbitmq.write:*/*">>]},
+ #{<<"rsid">> => <<"12ac3d1c-28c2-4521-8e33-0952eff10bd9">>,
+ <<"rsname">> => <<"Default Resource">>,
+ <<"scopes">> => [<<"rabbitmq.read:*/*">>]},
+ %% this one won't be used because of the resource id
+ #{<<"rsid">> => <<"bee8fac6-c3ec-11e9-aa8c-2a2ae2dbcce4">>,
+ <<"rsname">> => <<"Default Resource">>,
+ <<"scopes">> => [<<"rabbitmq-resource-read">>]}]}}
+ ),
+ Conn = open_unmanaged_connection(Config, 0, <<"username">>, Token),
+ {ok, Ch} = amqp_connection:open_channel(Conn),
+ #'queue.declare_ok'{queue = _} =
+ amqp_channel:call(Ch, #'queue.declare'{exclusive = true}),
+ close_connection_and_channel(Conn, Ch).
+
+test_successful_token_refresh(Config) ->
+ Duration = 5,
+ {_Algo, Token} = generate_expirable_token(Config, [<<"rabbitmq.configure:vhost1/*">>,
+ <<"rabbitmq.write:vhost1/*">>,
+ <<"rabbitmq.read:vhost1/*">>],
+ Duration),
+ Conn = open_unmanaged_connection(Config, 0, <<"vhost1">>, <<"username">>, Token),
+ {ok, Ch} = amqp_connection:open_channel(Conn),
+
+ {_Algo, Token2} = generate_valid_token(Config, [<<"rabbitmq.configure:vhost1/*">>,
+ <<"rabbitmq.write:vhost1/*">>,
+ <<"rabbitmq.read:vhost1/*">>]),
+ ?UTIL_MOD:wait_for_token_to_expire(timer:seconds(Duration)),
+ ?assertEqual(ok, amqp_connection:update_secret(Conn, Token2, <<"token refresh">>)),
+
+ {ok, Ch2} = amqp_connection:open_channel(Conn),
+
+ #'queue.declare_ok'{queue = _} =
+ amqp_channel:call(Ch, #'queue.declare'{exclusive = true}),
+ #'queue.declare_ok'{queue = _} =
+ amqp_channel:call(Ch2, #'queue.declare'{exclusive = true}),
+
+ amqp_channel:close(Ch2),
+ close_connection_and_channel(Conn, Ch).
+
+
+test_failed_connection_with_expired_token(Config) ->
+ {_Algo, Token} = generate_expired_token(Config, [<<"rabbitmq.configure:vhost1/*">>,
+ <<"rabbitmq.write:vhost1/*">>,
+ <<"rabbitmq.read:vhost1/*">>]),
+ ?assertMatch({error, {auth_failure, _}},
+ open_unmanaged_connection(Config, 0, <<"vhost1">>, <<"username">>, Token)).
+
+test_failed_connection_with_a_non_token(Config) ->
+ ?assertMatch({error, {auth_failure, _}},
+ open_unmanaged_connection(Config, 0, <<"vhost1">>, <<"username">>, <<"a-non-token-value">>)).
+
+test_failed_connection_with_a_token_with_insufficient_vhost_permission(Config) ->
+ {_Algo, Token} = generate_valid_token(Config, [<<"rabbitmq.configure:alt-vhost/*">>,
+ <<"rabbitmq.write:alt-vhost/*">>,
+ <<"rabbitmq.read:alt-vhost/*">>]),
+ ?assertEqual({error, not_allowed},
+ open_unmanaged_connection(Config, 0, <<"off-limits-vhost">>, <<"username">>, Token)).
+
+test_failed_connection_with_a_token_with_insufficient_resource_permission(Config) ->
+ {_Algo, Token} = generate_valid_token(Config, [<<"rabbitmq.configure:vhost2/jwt*">>,
+ <<"rabbitmq.write:vhost2/jwt*">>,
+ <<"rabbitmq.read:vhost2/jwt*">>]),
+ Conn = open_unmanaged_connection(Config, 0, <<"vhost2">>, <<"username">>, Token),
+ {ok, Ch} = amqp_connection:open_channel(Conn),
+ ?assertExit({{shutdown, {server_initiated_close, 403, _}}, _},
+ amqp_channel:call(Ch, #'queue.declare'{queue = <<"alt-prefix.eq.1">>, exclusive = true})),
+ close_connection(Conn).
+
+test_failed_token_refresh_case1(Config) ->
+ {_Algo, Token} = generate_valid_token(Config, [<<"rabbitmq.configure:vhost4/*">>,
+ <<"rabbitmq.write:vhost4/*">>,
+ <<"rabbitmq.read:vhost4/*">>]),
+ Conn = open_unmanaged_connection(Config, 0, <<"vhost4">>, <<"username">>, Token),
+ {ok, Ch} = amqp_connection:open_channel(Conn),
+ #'queue.declare_ok'{queue = _} =
+ amqp_channel:call(Ch, #'queue.declare'{exclusive = true}),
+
+ {_Algo, Token2} = generate_expired_token(Config, [<<"rabbitmq.configure:vhost4/*">>,
+ <<"rabbitmq.write:vhost4/*">>,
+ <<"rabbitmq.read:vhost4/*">>]),
+ %% the error is communicated asynchronously via a connection-level error
+ ?assertEqual(ok, amqp_connection:update_secret(Conn, Token2, <<"token refresh">>)),
+
+ {ok, Ch2} = amqp_connection:open_channel(Conn),
+ ?assertExit({{shutdown, {server_initiated_close, 403, _}}, _},
+ amqp_channel:call(Ch2, #'queue.declare'{queue = <<"a.q">>, exclusive = true})),
+
+ close_connection(Conn).
+
+test_failed_token_refresh_case2(Config) ->
+ {_Algo, Token} = generate_valid_token(Config, [<<"rabbitmq.configure:vhost4/*">>,
+ <<"rabbitmq.write:vhost4/*">>,
+ <<"rabbitmq.read:vhost4/*">>]),
+ Conn = open_unmanaged_connection(Config, 0, <<"vhost4">>, <<"username">>, Token),
+ {ok, Ch} = amqp_connection:open_channel(Conn),
+ #'queue.declare_ok'{queue = _} =
+ amqp_channel:call(Ch, #'queue.declare'{exclusive = true}),
+
+ %% the error is communicated asynchronously via a connection-level error
+ ?assertEqual(ok, amqp_connection:update_secret(Conn, <<"not-a-token-^^^^5%">>, <<"token refresh">>)),
+
+ ?assertExit({{shutdown, {connection_closing, {server_initiated_close, 530, _}}}, _},
+ amqp_connection:open_channel(Conn)),
+
+ close_connection(Conn).
diff --git a/deps/rabbitmq_auth_backend_oauth2/test/unit_SUITE.erl b/deps/rabbitmq_auth_backend_oauth2/test/unit_SUITE.erl
new file mode 100644
index 0000000000..f1ed34fabf
--- /dev/null
+++ b/deps/rabbitmq_auth_backend_oauth2/test/unit_SUITE.erl
@@ -0,0 +1,551 @@
+%% 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-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+-module(unit_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_own_scope,
+ test_validate_payload_resource_server_id_mismatch,
+ test_validate_payload,
+ test_successful_access_with_a_token,
+ test_successful_access_with_a_token_that_has_tag_scopes,
+ test_unsuccessful_access_with_a_bogus_token,
+ test_restricted_vhost_access_with_a_valid_token,
+ test_insufficient_permissions_in_a_valid_token,
+ test_command_json,
+ test_command_pem,
+ test_command_pem_no_kid,
+ test_token_expiration,
+ test_incorrect_kid,
+ test_post_process_token_payload,
+ test_post_process_token_payload_keycloak,
+ test_post_process_token_payload_complex_claims
+ ].
+
+init_per_suite(Config) ->
+ application:load(rabbitmq_auth_backend_oauth2),
+ Env = application:get_all_env(rabbitmq_auth_backend_oauth2),
+ Config1 = rabbit_ct_helpers:set_config(Config, {env, Env}),
+ rabbit_ct_helpers:run_setup_steps(Config1, []).
+
+end_per_suite(Config) ->
+ Env = ?config(env, Config),
+ lists:foreach(
+ fun({K, V}) ->
+ application:set_env(rabbitmq_auth_backend_oauth2, K, V)
+ end,
+ Env),
+ rabbit_ct_helpers:run_teardown_steps(Config).
+
+init_per_testcase(test_post_process_token_payload_complex_claims, Config) ->
+ application:set_env(rabbitmq_auth_backend_oauth2, extra_scopes_source, <<"additional_rabbitmq_scopes">>),
+ application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
+ Config;
+
+init_per_testcase(_, Config) ->
+ Config.
+
+end_per_testcase(test_post_process_token_payload_complex_claims, Config) ->
+ application:set_env(rabbitmq_auth_backend_oauth2, extra_scopes_source, undefined),
+ application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, undefined),
+ Config;
+end_per_testcase(_, Config) ->
+ Config.
+
+%%
+%% Test Cases
+%%
+
+-define(UTIL_MOD, rabbit_auth_backend_oauth2_test_util).
+-define(RESOURCE_SERVER_ID, <<"rabbitmq">>).
+
+test_post_process_token_payload(_) ->
+ ArgumentsExpections = [
+ {{[<<"rabbitmq">>, <<"hare">>], [<<"read">>, <<"write">>, <<"configure">>]},
+ {[<<"rabbitmq">>, <<"hare">>], [<<"read">>, <<"write">>, <<"configure">>]}},
+ {{<<"rabbitmq hare">>, <<"read write configure">>},
+ {[<<"rabbitmq">>, <<"hare">>], [<<"read">>, <<"write">>, <<"configure">>]}},
+ {{<<"rabbitmq">>, <<"read">>},
+ {[<<"rabbitmq">>], [<<"read">>]}}
+ ],
+ lists:foreach(
+ fun({{Aud, Scope}, {ExpectedAud, ExpectedScope}}) ->
+ Payload = post_process_token_payload(Aud, Scope),
+ ?assertEqual(ExpectedAud, maps:get(<<"aud">>, Payload)),
+ ?assertEqual(ExpectedScope, maps:get(<<"scope">>, Payload))
+ end, ArgumentsExpections).
+
+post_process_token_payload(Audience, Scopes) ->
+ Jwk = ?UTIL_MOD:fixture_jwk(),
+ Token = maps:put(<<"aud">>, Audience, ?UTIL_MOD:fixture_token_with_scopes(Scopes)),
+ {_, EncodedToken} = ?UTIL_MOD:sign_token_hs(Token, Jwk),
+ {true, Payload} = uaa_jwt_jwt:decode_and_verify(Jwk, EncodedToken),
+ rabbit_auth_backend_oauth2:post_process_payload(Payload).
+
+test_post_process_token_payload_keycloak(_) ->
+ Pairs = [
+ %% common case
+ {
+ #{<<"permissions">> =>
+ [#{<<"rsid">> => <<"2c390fe4-02ad-41c7-98a2-cebb8c60ccf1">>,
+ <<"rsname">> => <<"allvhost">>,
+ <<"scopes">> => [<<"rabbitmq-resource.read:*/*">>]},
+ #{<<"rsid">> => <<"e7f12e94-4c34-43d8-b2b1-c516af644cee">>,
+ <<"rsname">> => <<"vhost1">>,
+ <<"scopes">> => [<<"rabbitmq-resource-read">>]},
+ #{<<"rsid">> => <<"12ac3d1c-28c2-4521-8e33-0952eff10bd9">>,
+ <<"rsname">> => <<"Default Resource">>}]},
+ [<<"rabbitmq-resource.read:*/*">>, <<"rabbitmq-resource-read">>]
+ },
+
+ %% one scopes field with a string instead of an array
+ {
+ #{<<"permissions">> =>
+ [#{<<"rsid">> => <<"2c390fe4-02ad-41c7-98a2-cebb8c60ccf1">>,
+ <<"rsname">> => <<"allvhost">>,
+ <<"scopes">> => <<"rabbitmq-resource.read:*/*">>},
+ #{<<"rsid">> => <<"e7f12e94-4c34-43d8-b2b1-c516af644cee">>,
+ <<"rsname">> => <<"vhost1">>,
+ <<"scopes">> => [<<"rabbitmq-resource-read">>]},
+ #{<<"rsid">> => <<"12ac3d1c-28c2-4521-8e33-0952eff10bd9">>,
+ <<"rsname">> => <<"Default Resource">>}]},
+ [<<"rabbitmq-resource.read:*/*">>, <<"rabbitmq-resource-read">>]
+ },
+
+ %% no scopes field in permissions
+ {
+ #{<<"permissions">> =>
+ [#{<<"rsid">> => <<"2c390fe4-02ad-41c7-98a2-cebb8c60ccf1">>,
+ <<"rsname">> => <<"allvhost">>},
+ #{<<"rsid">> => <<"e7f12e94-4c34-43d8-b2b1-c516af644cee">>,
+ <<"rsname">> => <<"vhost1">>},
+ #{<<"rsid">> => <<"12ac3d1c-28c2-4521-8e33-0952eff10bd9">>,
+ <<"rsname">> => <<"Default Resource">>}]},
+ []
+ },
+
+ %% no permissions
+ {
+ #{<<"permissions">> => []},
+ []
+ },
+ %% missing permissions key
+ {#{}, []}
+ ],
+ lists:foreach(
+ fun({Authorization, ExpectedScope}) ->
+ Payload = post_process_payload_with_keycloak_authorization(Authorization),
+ ?assertEqual(ExpectedScope, maps:get(<<"scope">>, Payload))
+ end, Pairs).
+
+post_process_payload_with_keycloak_authorization(Authorization) ->
+ Jwk = ?UTIL_MOD:fixture_jwk(),
+ Token = maps:put(<<"authorization">>, Authorization, ?UTIL_MOD:fixture_token_with_scopes([])),
+ {_, EncodedToken} = ?UTIL_MOD:sign_token_hs(Token, Jwk),
+ {true, Payload} = uaa_jwt_jwt:decode_and_verify(Jwk, EncodedToken),
+ rabbit_auth_backend_oauth2:post_process_payload(Payload).
+
+
+
+test_post_process_token_payload_complex_claims(_) ->
+ Pairs = [
+ %% claims in form of binary
+ {
+ <<"rabbitmq.rabbitmq-resource.read:*/* rabbitmq.rabbitmq-resource-read">>,
+ [<<"rabbitmq.rabbitmq-resource.read:*/*">>, <<"rabbitmq.rabbitmq-resource-read">>]
+ },
+ %% claims in form of binary - empty result
+ {<<>>, []},
+ %% claims in form of list
+ {
+ [<<"rabbitmq.rabbitmq-resource.read:*/*">>,
+ <<"rabbitmq2.rabbitmq-resource-read">>],
+ [<<"rabbitmq.rabbitmq-resource.read:*/*">>, <<"rabbitmq2.rabbitmq-resource-read">>]
+ },
+ %% claims in form of list - empty result
+ {[], []},
+ %% claims are map with list content
+ {
+ #{<<"rabbitmq">> =>
+ [<<"rabbitmq-resource.read:*/*">>,
+ <<"rabbitmq-resource-read">>],
+ <<"rabbitmq3">> =>
+ [<<"rabbitmq-resource.write:*/*">>,
+ <<"rabbitmq-resource-write">>]},
+ [<<"rabbitmq.rabbitmq-resource.read:*/*">>, <<"rabbitmq.rabbitmq-resource-read">>]
+ },
+ %% claims are map with list content - empty result
+ {
+ #{<<"rabbitmq2">> =>
+ [<<"rabbitmq-resource.read:*/*">>,
+ <<"rabbitmq-resource-read">>]},
+ []
+ },
+ %% claims are map with binary content
+ {
+ #{<<"rabbitmq">> => <<"rabbitmq-resource.read:*/* rabbitmq-resource-read">>,
+ <<"rabbitmq3">> => <<"rabbitmq-resource.write:*/* rabbitmq-resource-write">>},
+ [<<"rabbitmq.rabbitmq-resource.read:*/*">>, <<"rabbitmq.rabbitmq-resource-read">>]
+ },
+ %% claims are map with binary content - empty result
+ {
+ #{<<"rabbitmq2">> => <<"rabbitmq-resource.read:*/* rabbitmq-resource-read">>}, []
+ },
+ %% claims are map with empty binary content - empty result
+ {
+ #{<<"rabbitmq">> => <<>>}, []
+ },
+ %% claims are map with empty list content - empty result
+ {
+ #{<<"rabbitmq">> => []}, []
+ },
+ %% no extra claims provided
+ {[], []},
+ %% no extra claims provided
+ {#{}, []}
+ ],
+ lists:foreach(
+ fun({Authorization, ExpectedScope}) ->
+ Payload = post_process_payload_with_complex_claim_authorization(Authorization),
+ ?assertEqual(ExpectedScope, maps:get(<<"scope">>, Payload))
+ end, Pairs).
+
+post_process_payload_with_complex_claim_authorization(Authorization) ->
+ Jwk = ?UTIL_MOD:fixture_jwk(),
+ Token = maps:put(<<"additional_rabbitmq_scopes">>, Authorization, ?UTIL_MOD:fixture_token_with_scopes([])),
+ {_, EncodedToken} = ?UTIL_MOD:sign_token_hs(Token, Jwk),
+ {true, Payload} = uaa_jwt_jwt:decode_and_verify(Jwk, EncodedToken),
+ rabbit_auth_backend_oauth2:post_process_payload(Payload).
+
+test_successful_access_with_a_token(_) ->
+ %% Generate a token with JOSE
+ %% Check authorization with the token
+ %% Check user access granted by token
+ Jwk = ?UTIL_MOD:fixture_jwk(),
+ UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}],
+ application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv),
+ application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
+ Username = <<"username">>,
+ Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:fixture_token(), Jwk),
+
+ {ok, #auth_user{username = Username} = User} =
+ rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]),
+ {ok, #auth_user{username = Username} = User} =
+ rabbit_auth_backend_oauth2:user_login_authentication(Username, #{password => Token}),
+
+ ?assertEqual(true, rabbit_auth_backend_oauth2:check_vhost_access(User, <<"vhost">>, none)),
+ ?assertEqual(true, rabbit_auth_backend_oauth2:check_resource_access(
+ User,
+ #resource{virtual_host = <<"vhost">>,
+ kind = queue,
+ name = <<"foo">>},
+ configure,
+ #{})),
+ ?assertEqual(true, rabbit_auth_backend_oauth2:check_resource_access(
+ User,
+ #resource{virtual_host = <<"vhost">>,
+ kind = exchange,
+ name = <<"foo">>},
+ write,
+ #{})),
+
+ ?assertEqual(true, rabbit_auth_backend_oauth2:check_resource_access(
+ User,
+ #resource{virtual_host = <<"vhost">>,
+ kind = custom,
+ name = <<"bar">>},
+ read,
+ #{})),
+
+ ?assertEqual(true, rabbit_auth_backend_oauth2:check_topic_access(
+ User,
+ #resource{virtual_host = <<"vhost">>,
+ kind = topic,
+ name = <<"bar">>},
+ read,
+ #{routing_key => <<"#/foo">>})).
+
+test_successful_access_with_a_token_that_has_tag_scopes(_) ->
+ Jwk = ?UTIL_MOD:fixture_jwk(),
+ UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}],
+ application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv),
+ application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
+ Username = <<"username">>,
+ Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:fixture_token([<<"rabbitmq.tag:management">>,
+ <<"rabbitmq.tag:policymaker">>]), Jwk),
+
+ {ok, #auth_user{username = Username, tags = [management, policymaker]}} =
+ rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]).
+
+test_unsuccessful_access_with_a_bogus_token(_) ->
+ Username = <<"username">>,
+ application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
+
+ Jwk0 = ?UTIL_MOD:fixture_jwk(),
+ Jwk = Jwk0#{<<"k">> => <<"bm90b2tlbmtleQ">>},
+ UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}],
+ application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv),
+
+ ?assertMatch({refused, _, _},
+ rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, <<"not a token">>}])).
+
+test_restricted_vhost_access_with_a_valid_token(_) ->
+ Username = <<"username">>,
+ application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
+
+ Jwk = ?UTIL_MOD:fixture_jwk(),
+ Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:fixture_token(), Jwk),
+ UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}],
+ application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv),
+
+ %% this user can authenticate successfully and access certain vhosts
+ {ok, #auth_user{username = Username, tags = []} = User} =
+ rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]),
+
+ %% access to a different vhost
+ ?assertEqual(false, rabbit_auth_backend_oauth2:check_vhost_access(User, <<"different vhost">>, none)).
+
+test_insufficient_permissions_in_a_valid_token(_) ->
+ Username = <<"username">>,
+ application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
+
+ Jwk = ?UTIL_MOD:fixture_jwk(),
+ Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:fixture_token(), Jwk),
+ UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}],
+ application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv),
+
+ {ok, #auth_user{username = Username} = User} =
+ rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]),
+
+ %% access to these resources is not granted
+ ?assertEqual(false, rabbit_auth_backend_oauth2:check_resource_access(
+ User,
+ #resource{virtual_host = <<"vhost">>,
+ kind = queue,
+ name = <<"foo1">>},
+ configure,
+ #{})),
+ ?assertEqual(false, rabbit_auth_backend_oauth2:check_resource_access(
+ User,
+ #resource{virtual_host = <<"vhost">>,
+ kind = custom,
+ name = <<"bar">>},
+ write,
+ #{})),
+ ?assertEqual(false, rabbit_auth_backend_oauth2:check_topic_access(
+ User,
+ #resource{virtual_host = <<"vhost">>,
+ kind = topic,
+ name = <<"bar">>},
+ read,
+ #{routing_key => <<"foo/#">>})).
+
+test_token_expiration(_) ->
+ Username = <<"username">>,
+ Jwk = ?UTIL_MOD:fixture_jwk(),
+ UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}],
+ application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv),
+ application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
+ TokenData = ?UTIL_MOD:expirable_token(),
+ Username = <<"username">>,
+ Token = ?UTIL_MOD:sign_token_hs(TokenData, Jwk),
+ {ok, #auth_user{username = Username} = User} =
+ rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]),
+ ?assertEqual(true, rabbit_auth_backend_oauth2:check_resource_access(
+ User,
+ #resource{virtual_host = <<"vhost">>,
+ kind = queue,
+ name = <<"foo">>},
+ configure,
+ #{})),
+ ?assertEqual(true, rabbit_auth_backend_oauth2:check_resource_access(
+ User,
+ #resource{virtual_host = <<"vhost">>,
+ kind = exchange,
+ name = <<"foo">>},
+ write,
+ #{})),
+
+ ?UTIL_MOD:wait_for_token_to_expire(),
+ #{<<"exp">> := Exp} = TokenData,
+ ExpectedError = "Provided JWT token has expired at timestamp " ++ integer_to_list(Exp) ++ " (validated at " ++ integer_to_list(Exp) ++ ")",
+ ?assertEqual({error, ExpectedError},
+ rabbit_auth_backend_oauth2:check_resource_access(
+ User,
+ #resource{virtual_host = <<"vhost">>,
+ kind = queue,
+ name = <<"foo">>},
+ configure,
+ #{})),
+
+ ?assertMatch({refused, _, _},
+ rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}])).
+
+test_incorrect_kid(_) ->
+ AltKid = <<"other-token-key">>,
+ Username = <<"username">>,
+ Jwk = ?UTIL_MOD:fixture_jwk(),
+ Jwk1 = Jwk#{<<"kid">> := AltKid},
+ application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
+ Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:fixture_token(), Jwk1),
+
+ ?assertMatch({refused, "Authentication using an OAuth 2/JWT token failed: ~p", [{error,key_not_found}]},
+ rabbit_auth_backend_oauth2:user_login_authentication(Username, #{password => Token})).
+
+test_command_json(_) ->
+ Username = <<"username">>,
+ Jwk = ?UTIL_MOD:fixture_jwk(),
+ Json = rabbit_json:encode(Jwk),
+ 'Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand':run(
+ [<<"token-key">>],
+ #{node => node(), json => Json}),
+ application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
+ Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:fixture_token(), Jwk),
+ {ok, #auth_user{username = Username} = User} =
+ rabbit_auth_backend_oauth2:user_login_authentication(Username, #{password => Token}),
+
+ ?assertEqual(true, rabbit_auth_backend_oauth2:check_vhost_access(User, <<"vhost">>, none)).
+
+test_command_pem_file(Config) ->
+ Username = <<"username">>,
+ application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
+ CertsDir = ?config(rmq_certsdir, Config),
+ Keyfile = filename:join([CertsDir, "client", "key.pem"]),
+ Jwk = jose_jwk:from_pem_file(Keyfile),
+
+ PublicJwk = jose_jwk:to_public(Jwk),
+ PublicKeyFile = filename:join([CertsDir, "client", "public.pem"]),
+ jose_jwk:to_pem_file(PublicKeyFile, PublicJwk),
+
+ 'Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand':run(
+ [<<"token-key">>],
+ #{node => node(), pem_file => PublicKeyFile}),
+
+ Token = ?UTIL_MOD:sign_token_rsa(?UTIL_MOD:fixture_token(), Jwk, <<"token-key">>),
+ {ok, #auth_user{username = Username} = User} =
+ rabbit_auth_backend_oauth2:user_login_authentication(Username, #{password => Token}),
+
+ ?assertEqual(true, rabbit_auth_backend_oauth2:check_vhost_access(User, <<"vhost">>, none)).
+
+
+test_command_pem_file_no_kid(Config) ->
+ Username = <<"username">>,
+ application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
+ CertsDir = ?config(rmq_certsdir, Config),
+ Keyfile = filename:join([CertsDir, "client", "key.pem"]),
+ Jwk = jose_jwk:from_pem_file(Keyfile),
+
+ PublicJwk = jose_jwk:to_public(Jwk),
+ PublicKeyFile = filename:join([CertsDir, "client", "public.pem"]),
+ jose_jwk:to_pem_file(PublicKeyFile, PublicJwk),
+
+ 'Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand':run(
+ [<<"token-key">>],
+ #{node => node(), pem_file => PublicKeyFile}),
+
+ %% Set default key
+ {ok, UaaEnv0} = application:get_env(rabbitmq_auth_backend_oauth2, key_config),
+ UaaEnv1 = proplists:delete(default_key, UaaEnv0),
+ UaaEnv2 = [{default_key, <<"token-key">>} | UaaEnv1],
+ application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv2),
+
+ Token = ?UTIL_MOD:sign_token_no_kid(?UTIL_MOD:fixture_token(), Jwk),
+ {ok, #auth_user{username = Username} = User} =
+ rabbit_auth_backend_oauth2:user_login_authentication(Username, #{password => Token}),
+
+ ?assertEqual(true, rabbit_auth_backend_oauth2:check_vhost_access(User, <<"vhost">>, none)).
+
+test_command_pem(Config) ->
+ Username = <<"username">>,
+ application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
+ CertsDir = ?config(rmq_certsdir, Config),
+ Keyfile = filename:join([CertsDir, "client", "key.pem"]),
+ Jwk = jose_jwk:from_pem_file(Keyfile),
+
+ Pem = jose_jwk:to_pem(jose_jwk:to_public(Jwk)),
+
+ 'Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand':run(
+ [<<"token-key">>],
+ #{node => node(), pem => Pem}),
+
+ Token = ?UTIL_MOD:sign_token_rsa(?UTIL_MOD:fixture_token(), Jwk, <<"token-key">>),
+ {ok, #auth_user{username = Username} = User} =
+ rabbit_auth_backend_oauth2:user_login_authentication(Username, #{password => Token}),
+
+ ?assertEqual(true, rabbit_auth_backend_oauth2:check_vhost_access(User, <<"vhost">>, none)).
+
+
+test_command_pem_no_kid(Config) ->
+ Username = <<"username">>,
+ application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
+ CertsDir = ?config(rmq_certsdir, Config),
+ Keyfile = filename:join([CertsDir, "client", "key.pem"]),
+ Jwk = jose_jwk:from_pem_file(Keyfile),
+
+ Pem = jose_jwk:to_pem(jose_jwk:to_public(Jwk)),
+
+ 'Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand':run(
+ [<<"token-key">>],
+ #{node => node(), pem => Pem}),
+
+ %% This is the default key
+ {ok, UaaEnv0} = application:get_env(rabbitmq_auth_backend_oauth2, key_config),
+ UaaEnv1 = proplists:delete(default_key, UaaEnv0),
+ UaaEnv2 = [{default_key, <<"token-key">>} | UaaEnv1],
+ application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv2),
+
+ Token = ?UTIL_MOD:sign_token_no_kid(?UTIL_MOD:fixture_token(), Jwk),
+ {ok, #auth_user{username = Username} = User} =
+ rabbit_auth_backend_oauth2:user_login_authentication(Username, #{password => Token}),
+
+ ?assertEqual(true, rabbit_auth_backend_oauth2:check_vhost_access(User, <<"vhost">>, none)).
+
+
+test_own_scope(_) ->
+ Examples = [
+ {<<"foo">>, [<<"foo">>, <<"foo.bar">>, <<"bar.foo">>,
+ <<"one.two">>, <<"foobar">>, <<"foo.other.third">>],
+ [<<"bar">>, <<"other.third">>]},
+ {<<"foo">>, [], []},
+ {<<"foo">>, [<<"foo">>, <<"other.foo.bar">>], []},
+ {<<"">>, [<<"foo">>, <<"bar">>], [<<"foo">>, <<"bar">>]}
+ ],
+ lists:map(
+ fun({ResId, Src, Dest}) ->
+ Dest = rabbit_auth_backend_oauth2:filter_scopes(Src, ResId)
+ end,
+ Examples).
+
+test_validate_payload_resource_server_id_mismatch(_) ->
+ NoKnownResourceServerId = #{<<"aud">> => [<<"foo">>, <<"bar">>],
+ <<"scope">> => [<<"foo">>, <<"foo.bar">>,
+ <<"bar.foo">>, <<"one.two">>,
+ <<"foobar">>, <<"foo.other.third">>]},
+ EmptyAud = #{<<"aud">> => [],
+ <<"scope">> => [<<"foo.bar">>, <<"bar.foo">>]},
+
+ ?assertEqual({refused, {invalid_aud, {resource_id_not_found_in_aud, ?RESOURCE_SERVER_ID,
+ [<<"foo">>,<<"bar">>]}}},
+ rabbit_auth_backend_oauth2:validate_payload(NoKnownResourceServerId, ?RESOURCE_SERVER_ID)),
+
+ ?assertEqual({refused, {invalid_aud, {resource_id_not_found_in_aud, ?RESOURCE_SERVER_ID, []}}},
+ rabbit_auth_backend_oauth2:validate_payload(EmptyAud, ?RESOURCE_SERVER_ID)).
+
+test_validate_payload(_) ->
+ KnownResourceServerId = #{<<"aud">> => [?RESOURCE_SERVER_ID],
+ <<"scope">> => [<<"foo">>, <<"rabbitmq.bar">>,
+ <<"bar.foo">>, <<"one.two">>,
+ <<"foobar">>, <<"rabbitmq.other.third">>]},
+ ?assertEqual({ok, #{<<"aud">> => [?RESOURCE_SERVER_ID],
+ <<"scope">> => [<<"bar">>, <<"other.third">>]}},
+ rabbit_auth_backend_oauth2:validate_payload(KnownResourceServerId, ?RESOURCE_SERVER_ID)).
diff --git a/deps/rabbitmq_auth_backend_oauth2/test/wildcard_match_SUITE.erl b/deps/rabbitmq_auth_backend_oauth2/test/wildcard_match_SUITE.erl
new file mode 100644
index 0000000000..6996a95f80
--- /dev/null
+++ b/deps/rabbitmq_auth_backend_oauth2/test/wildcard_match_SUITE.erl
@@ -0,0 +1,105 @@
+%% 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-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(wildcard_match_SUITE).
+
+-compile(export_all).
+
+-include_lib("eunit/include/eunit.hrl").
+
+all() ->
+ [
+ exact_match,
+ prefix_match,
+ suffix_match,
+ mixed_match
+ ].
+
+exact_match(_Config) ->
+ ?assertEqual(true, wildcard:match(<<"value">>, <<"value">>)),
+ ?assertEqual(true, wildcard:match(<<"string with / special % characters">>,
+ <<"string%20with%20%2F%20special%20%25%20characters">>)),
+ ?assertEqual(true, wildcard:match(<<"pattern with plus spaces">>,
+ <<"pattern+with++plus++spaces">>)),
+ ?assertEqual(true, wildcard:match(<<"pattern with plus spaces and * wildcard encoded">>,
+ <<"pattern+with+plus+spaces+and+%2A+wildcard+encoded">>)),
+ ?assertEqual(true, wildcard:match(<<"case with * special / characters">>,
+ <<"case+with+%2a+special+%2f+characters">>)),
+
+ ?assertEqual(false, wildcard:match(<<"casesensitive">>, <<"CaseSensitive">>)),
+
+ ?assertEqual(false, wildcard:match(<<"foo">>, <<"fo">>)),
+ ?assertEqual(false, wildcard:match(<<"foo">>, <<"fooo">>)),
+
+ %% Special characters and spaces should be %-encoded.
+ ?assertEqual(false, wildcard:match(<<"string with unescaped % character">>,
+ <<"string with unescaped % character">>)),
+
+ %% Here a wildcard is matched by another wildcard, so to match exactly the '*' character
+ %% we need to %-encode it
+ ?assertEqual(true, wildcard:match(<<"wildcard * is matched by wildcard">>,
+ <<"wildcard * is matched by wildcard">>)),
+
+ ?assertEqual(true, wildcard:match(<<"wildcard * and anything else is matched by wildcard">>,
+ <<"wildcard * is matched by wildcard">>)),
+
+ ?assertEqual(true, wildcard:match(<<"wildcard * is matched by urlencoded">>,
+ <<"wildcard+%2a+is+matched+by+urlencoded">>)),
+ ?assertEqual(false, wildcard:match(<<"wildcard * and extra content is not matched by urlencoded">>,
+ <<"wildcard+%2a+is+not+matched+by+urlencoded">>)),
+
+ %% Spaces do not interfere with parsing
+ ?assertEqual(true, wildcard:match(<<"pattern with spaces">>,
+ <<"pattern with spaces">>)).
+
+suffix_match(_Config) ->
+ ?assertEqual(true, wildcard:match(<<"foo">>, <<"*oo">>)),
+ %% Empty prefix
+ ?assertEqual(true, wildcard:match(<<"foo">>, <<"*foo">>)),
+ %% Anything goes
+ ?assertEqual(true, wildcard:match(<<"foo">>, <<"*">>)),
+ ?assertEqual(true, wildcard:match(<<"line * with * special * characters">>, <<"*+characters">>)),
+ ?assertEqual(true, wildcard:match(<<"line * with * special * characters">>, <<"*special+%2A+characters">>)),
+
+ ?assertEqual(false, wildcard:match(<<"foo">>, <<"*r">>)),
+ ?assertEqual(false, wildcard:match(<<"foo">>, <<"*foobar">>)).
+
+prefix_match(_Config) ->
+ ?assertEqual(true, wildcard:match(<<"foo">>, <<"fo*">>)),
+ %% Empty suffix
+ ?assertEqual(true, wildcard:match(<<"foo">>, <<"foo*">>)),
+ ?assertEqual(true, wildcard:match(<<"line * with * special * characters">>, <<"line+*">>)),
+ ?assertEqual(true, wildcard:match(<<"line * with * special * characters">>, <<"line+%2a*">>)),
+
+ %% Wildcard matches '*' character
+ ?assertEqual(true, wildcard:match(<<"string with unescaped *">>,
+ <<"string+with+unescaped+*">>)),
+
+ ?assertEqual(false, wildcard:match(<<"foo">>, <<"b*">>)),
+ ?assertEqual(false, wildcard:match(<<"foo">>, <<"barfoo*">>)).
+
+mixed_match(_Config) ->
+ %% Empty wildcards
+ ?assertEqual(true, wildcard:match(<<"string">>, <<"*str*ing*">>)),
+ ?assertEqual(true, wildcard:match(<<"str*ing">>, <<"*str*ing*">>)),
+
+ ?assertEqual(true, wildcard:match(<<"some long string">>, <<"some*string">>)),
+ ?assertEqual(true, wildcard:match(<<"some long string">>, <<"some*long*string">>)),
+ ?assertEqual(true, wildcard:match(<<"some long string">>, <<"*some*string*">>)),
+ ?assertEqual(true, wildcard:match(<<"some long string">>, <<"*long*">>)),
+ %% Matches two spaces (3 or more words)
+ ?assertEqual(true, wildcard:match(<<"some long string">>, <<"*+*+*">>)),
+
+ ?assertEqual(false, wildcard:match(<<"some string">>, <<"*+*+*">>)),
+ ?assertEqual(false, wildcard:match(<<"some long string">>, <<"some*other*string">>)),
+
+ ?assertEqual(true, wildcard:match(<<"some long string">>, <<"s*e*str*">>)),
+ %% The z doesn't appear in the subject
+ ?assertEqual(false, wildcard:match(<<"some long string">>, <<"s*z*str*">>)),
+
+ ?assertEqual(false, wildcard:match(<<"string">>, <<"*some*">>)),
+ ?assertEqual(false, wildcard:match(<<"string">>, <<"*some*string*">>)).