Create Oauth2 client

This commit is contained in:
Marcial Rosales 2024-01-03 09:28:36 +01:00
parent c50565c53a
commit d827b72ce1
154 changed files with 6315 additions and 1234 deletions

21
deps/oauth2_client/.gitignore vendored Normal file
View File

@ -0,0 +1,21 @@
.sw?
.*.sw?
*.beam
*.plt
/.erlang.mk/
/cover/
/deps/
/doc/
/ebin/
/escript/
/escript.lock
/logs/
/plugins/
/plugins.lock
/sbin/
/sbin.lock
/xrefr
elvis
/*.coverdata
rabbitmq_oauth2_client.d

134
deps/oauth2_client/BUILD.bazel vendored Normal file
View File

@ -0,0 +1,134 @@
load("@rules_erlang//:eunit2.bzl", "eunit")
load("@rules_erlang//:xref2.bzl", "xref")
load("@rules_erlang//:dialyze.bzl", "dialyze", "plt")
load(
"//:rabbitmq.bzl",
"BROKER_VERSION_REQUIREMENTS_ANY",
"RABBITMQ_DIALYZER_OPTS",
"assert_suites",
"broker_for_integration_suites",
"rabbitmq_app",
"rabbitmq_integration_suite",
"rabbitmq_suite",
)
load(
":app.bzl",
"all_beam_files",
"all_srcs",
"all_test_beam_files",
"test_suite_beam_files",
)
APP_NAME = "oauth2_client"
APP_DESCRIPTION = "OAuth 2.0 client from the RabbitMQ Project"
APP_MODULE = "oauth2_client_app"
APP_EXTRA_KEYS = """%% Hex.pm package informations.
{licenses, ["MPL-2.0"]},
{links, [
{"Website", "https://www.rabbitmq.com/"},
{"GitHub", "https://github.com/rabbitmq/rabbitmq-server/tree/main/deps/oauth2_client"}
]},
{build_tools, ["make", "rebar3"]},
{files, [
"erlang.mk",
"git-revisions.txt",
"include",
"LICENSE*",
"Makefile",
"rabbitmq-components.mk",
"README",
"README.md",
"src"
]}
"""
# gazelle:erlang_app_extra_app ssl
# gazelle:erlang_app_extra_app inets
# gazelle:erlang_app_extra_app crypto
# gazelle:erlang_app_extra_app public_key
rabbitmq_app(
name = "erlang_app",
srcs = [":all_srcs"],
hdrs = [":public_hdrs"],
app_description = APP_DESCRIPTION,
app_extra_keys = APP_EXTRA_KEYS,
# app_module = APP_MODULE,
app_name = APP_NAME,
beam_files = [":beam_files"],
extra_apps = [
"crypto",
"inets",
"ssl",
"public_key",
],
license_files = [":license_files"],
priv = [":priv"],
deps = [
"//deps/rabbit:erlang_app",
"//deps/rabbit_common:erlang_app",
],
)
xref(
name = "xref",
target = ":erlang_app",
)
plt(
name = "deps_plt",
for_target = ":erlang_app",
ignore_warnings = True,
plt = "//:base_plt",
)
dialyze(
name = "dialyze",
dialyzer_opts = RABBITMQ_DIALYZER_OPTS,
plt = ":deps_plt",
target = ":erlang_app",
)
eunit(
name = "eunit",
compiled_suites = [":test_oauth_http_mock_beam"],
target = ":test_erlang_app",
)
all_srcs(name = "all_srcs")
all_beam_files(name = "all_beam_files")
all_test_beam_files(name = "all_test_beam_files")
test_suite_beam_files(name = "test_suite_beam_files")
alias(
name = "rabbitmq_oauth2_client",
actual = ":erlang_app",
visibility = ["//visibility:public"],
)
broker_for_integration_suites()
rabbitmq_integration_suite(
name = "system_SUITE",
size = "small",
additional_beam = [
"test/oauth_http_mock.beam",
],
runtime_deps = [
"@cowboy//:erlang_app",
],
)
assert_suites()
alias(
name = "oauth2_client",
actual = ":erlang_app",
visibility = ["//visibility:public"],
)

1
deps/oauth2_client/CODE_OF_CONDUCT.md vendored Symbolic link
View File

@ -0,0 +1 @@
../../CODE_OF_CONDUCT.md

1
deps/oauth2_client/CONTRIBUTING.md vendored Symbolic link
View File

@ -0,0 +1 @@
../../CONTRIBUTING.md

4
deps/oauth2_client/LICENSE vendored Normal file
View File

@ -0,0 +1,4 @@
This package is licensed under the MPL 2.0. For the MPL 2.0, please see LICENSE-MPL-RabbitMQ.
If you have any questions regarding licensing, please contact us at
rabbitmq-core@groups.vmware.com.

373
deps/oauth2_client/LICENSE-MPL-RabbitMQ vendored Normal file
View File

@ -0,0 +1,373 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
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 http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

77
deps/oauth2_client/Makefile vendored Normal file
View File

@ -0,0 +1,77 @@
PROJECT = oauth2_client
PROJECT_DESCRIPTION = OAuth2 client from the RabbitMQ Project
PROJECT_MOD = oauth2_client_app
define PROJECT_APP_EXTRA_KEYS
%% Hex.pm package informations.
{licenses, ["MPL-2.0"]},
{links, [
{"Website", "https://www.rabbitmq.com/"},
{"GitHub", "https://github.com/rabbitmq/rabbitmq-server/tree/main/deps/oauth2_client"}
]},
{build_tools, ["make", "rebar3"]},
{files, [
$(RABBITMQ_HEXPM_DEFAULT_FILES)
]}
endef
define HEX_TARBALL_EXTRA_METADATA
#{
licenses => [<<"MPL-2.0">>],
links => #{
<<"Website">> => <<"https://www.rabbitmq.com">>,
<<"GitHub">> => <<"https://github.com/rabbitmq/rabbitmq-server/tree/main/deps/oauth2_client">>
}
}
endef
# Release artifacts are put in $(PACKAGES_DIR).
PACKAGES_DIR ?= $(abspath PACKAGES)
BUILD_DEPS = rabbit_common elvis_mk
DEPS = cowlib
TEST_DEPS = rabbit rabbitmq_ct_helpers cowboy
LOCAL_DEPS = ssl inets crypto public_key
DEP_EARLY_PLUGINS = rabbit_common/mk/rabbitmq-early-test.mk
DEP_PLUGINS = rabbit_common/mk/rabbitmq-macros.mk \
rabbit_common/mk/rabbitmq-build.mk \
rabbit_common/mk/rabbitmq-hexpm.mk \
rabbit_common/mk/rabbitmq-dist.mk \
rabbit_common/mk/rabbitmq-run.mk \
rabbit_common/mk/rabbitmq-test.mk \
rabbit_common/mk/rabbitmq-tools.mk
DEP_PLUGINS += elvis_mk
dep_elvis_mk = git https://github.com/inaka/elvis.mk.git master
include rabbitmq-components.mk
include erlang.mk
HEX_TARBALL_FILES += rabbitmq-components.mk \
git-revisions.txt
# --------------------------------------------------------------------
# Compiler flags.
# --------------------------------------------------------------------
# gen_fsm is deprecated starting from Erlang 20, but we want to support
# Erlang 19 as well.
ERTS_VER := $(shell erl -version 2>&1 | sed -E 's/.* version //')
ERLANG_20_ERTS_VER := 9.0
ifeq ($(call compare_version,$(ERTS_VER),$(ERLANG_20_ERTS_VER),>=),true)
ERLC_OPTS += -Dnowarn_deprecated_gen_fsm
endif
# Dialyze the tests.
DIALYZER_OPTS += --src -r test
# --------------------------------------------------------------------
# ActiveMQ for the testsuite.
# --------------------------------------------------------------------
tests:: $(ACTIVEMQ)
ct ct-system: $(ACTIVEMQ)

7
deps/oauth2_client/README.md vendored Normal file
View File

@ -0,0 +1,7 @@
# Erlang OAuth 2.0 client
This is an [Erlang client for the OAuth 2.0](https://www.amqp.org/resources/specifications) protocol.
It's primary purpose is to be used in RabbitMQ related projects.
## Usage

90
deps/oauth2_client/app.bzl vendored Normal file
View File

@ -0,0 +1,90 @@
load("@rules_erlang//:erlang_bytecode2.bzl", "erlang_bytecode")
load("@rules_erlang//:filegroup.bzl", "filegroup")
def all_beam_files(name = "all_beam_files"):
filegroup(
name = "beam_files",
srcs = [":other_beam"],
)
erlang_bytecode(
name = "other_beam",
srcs = [
"src/oauth2_client.erl",
],
hdrs = [":public_and_private_hdrs"],
app_name = "rabbitmq_oauth2_client",
dest = "ebin",
erlc_opts = "//:erlc_opts",
)
def all_test_beam_files(name = "all_test_beam_files"):
filegroup(
name = "test_beam_files",
testonly = True,
srcs = [":test_other_beam"],
)
erlang_bytecode(
name = "test_other_beam",
testonly = True,
srcs = [
"src/oauth2_client.erl",
],
hdrs = [":public_and_private_hdrs"],
app_name = "rabbitmq_oauth2_client",
dest = "test",
erlc_opts = "//:test_erlc_opts",
)
def all_srcs(name = "all_srcs"):
filegroup(
name = "all_srcs",
srcs = [":public_and_private_hdrs", ":srcs"],
)
filegroup(
name = "public_and_private_hdrs",
srcs = [":private_hdrs", ":public_hdrs"],
)
filegroup(
name = "priv",
)
filegroup(
name = "srcs",
srcs = [
"src/oauth2_client.erl",
],
)
filegroup(
name = "private_hdrs",
)
filegroup(
name = "public_hdrs",
srcs = ["include/oauth2_client.hrl"],
)
filegroup(
name = "license_files",
srcs = [
"LICENSE",
"LICENSE-MPL-RabbitMQ",
],
)
def test_suite_beam_files(name = "test_suite_beam_files"):
erlang_bytecode(
name = "test_oauth_http_mock_beam",
testonly = True,
srcs = ["test/oauth_http_mock.erl"],
outs = ["test/oauth_http_mock.beam"],
app_name = "rabbitmq_oauth2_client",
erlc_opts = "//:test_erlc_opts",
)
erlang_bytecode(
name = "system_SUITE_beam_files",
testonly = True,
srcs = ["test/system_SUITE.erl"],
outs = ["test/system_SUITE.beam"],
hdrs = ["include/oauth2_client.hrl"],
app_name = "rabbitmq_oauth2_client",
erlc_opts = "//:test_erlc_opts",
deps = ["//deps/rabbit_common:erlang_app"],
)

1
deps/oauth2_client/erlang.mk vendored Symbolic link
View File

@ -0,0 +1 @@
../../erlang.mk

View File

@ -0,0 +1,88 @@
%% 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) 2020-2023 VMware, Inc. or its affiliates. All rights reserved.
%%
% define access token request common constants
-define(DEFAULT_HTTP_TIMEOUT, 60000).
-define(DEFAULT_OPENID_CONFIGURATION_PATH, <<"/.well-known/openid-configuration">>).
% define access token request constants
-define(CONTENT_URLENCODED, "application/x-www-form-urlencoded").
-define(CONTENT_JSON, "application/json").
-define(REQUEST_GRANT_TYPE, "grant_type").
-define(CLIENT_CREDENTIALS_GRANT_TYPE, "client_credentials").
-define(REFRESH_TOKEN_GRANT_TYPE, "refresh_token").
-define(REQUEST_CLIENT_ID, "client_id").
-define(REQUEST_CLIENT_SECRET, "client_secret").
-define(REQUEST_SCOPE, "scope").
-define(REQUEST_REFRESH_TOKEN, "refresh_token").
% define access token response constants
-define(BEARER_TOKEN_TYPE, <<"Bearer">>).
-define(RESPONSE_ACCESS_TOKEN, <<"access_token">>).
-define(RESPONSE_TOKEN_TYPE, <<"token_type">>).
-define(RESPONSE_EXPIRES_IN, <<"expires_in">>).
-define(RESPONSE_REFRESH_TOKEN, <<"refresh_token">>).
-define(RESPONSE_ERROR, <<"error">>).
-define(RESPONSE_ERROR_DESCRIPTION, <<"error_description">>).
-define(RESPONSE_ISSUER, <<"issuer">>).
-define(RESPONSE_TOKEN_ENDPOINT, <<"token_endpoint">>).
-define(RESPONSE_AUTHORIZATION_ENDPOINT, <<"authorization_endpoint">>).
-define(RESPONSE_JWKS_URI, <<"jwks_uri">>).
-define(RESPONSE_SSL_OPTIONS, <<"ssl_options">>).
-record(oauth_provider, {
issuer :: uri_string:uri_string() | undefined,
token_endpoint :: uri_string:uri_string() | undefined,
authorization_endpoint :: uri_string:uri_string() | undefined,
jwks_uri :: uri_string:uri_string() | undefined,
ssl_options :: list() | undefined
}).
-type oauth_provider() :: #oauth_provider{}.
-type oauth_provider_id() :: binary().
-record(access_token_request, {
client_id :: string() | binary(),
client_secret :: string() | binary(),
scope :: string() | binary() | undefined,
timeout :: integer() | undefined
}).
-type access_token_request() :: #access_token_request{}.
-record(successful_access_token_response, {
access_token :: binary(),
token_type :: binary(),
refresh_token :: binary() | undefined,
expires_in :: integer() | undefined
}).
-type successful_access_token_response() :: #successful_access_token_response{}.
-record(unsuccessful_access_token_response, {
error :: integer(),
error_description :: binary() | string() | undefined
}).
-type unsuccessful_access_token_response() :: #unsuccessful_access_token_response{}.
-record(refresh_token_request, {
client_id :: string() | binary(),
client_secret :: string() | binary(),
scope :: string() | binary() | undefined,
refresh_token :: binary(),
timeout :: integer() | undefined
}).
-type refresh_token_request() :: #refresh_token_request{}.

View File

@ -0,0 +1 @@
../../rabbitmq-components.mk

462
deps/oauth2_client/src/oauth2_client.erl vendored Normal file
View File

@ -0,0 +1,462 @@
-module(oauth2_client).
-export([get_access_token/2,
refresh_access_token/2,
get_oauth_provider/1,get_oauth_provider/2
]).
-include("oauth2_client.hrl").
-define(APP, auth_aouth2).
-spec get_access_token(oauth_provider_id() | oauth_provider(), access_token_request()) ->
{ok, successful_access_token_response()} | {error, unsuccessful_access_token_response() | any()}.
get_access_token(OAuth2ProviderId, Request) when is_binary(OAuth2ProviderId) ->
rabbit_log:debug("get_access_token using OAuth2ProviderId:~p and client_id:~p",
[OAuth2ProviderId, Request#access_token_request.client_id]),
case get_oauth_provider(OAuth2ProviderId, [token_endpoint]) of
{error, _Error } = Error0 -> Error0;
{ok, Provider} -> get_access_token(Provider, Request)
end;
get_access_token(OAuthProvider, Request) ->
rabbit_log:debug("get_access_token using OAuthProvider:~p and client_id:~p",
[OAuthProvider, Request#access_token_request.client_id]),
URL = OAuthProvider#oauth_provider.token_endpoint,
Header = [],
Type = ?CONTENT_URLENCODED,
Body = build_access_token_request_body(Request),
HTTPOptions = get_ssl_options_if_any(OAuthProvider) ++
get_timeout_of_default(Request#access_token_request.timeout),
Options = [],
Response = httpc:request(post, {URL, Header, Type, Body}, HTTPOptions, Options),
parse_access_token_response(Response).
-spec refresh_access_token(oauth_provider(), refresh_token_request()) ->
{ok, successful_access_token_response()} | {error, unsuccessful_access_token_response() | any()}.
refresh_access_token(OAuthProvider, Request) ->
URL = OAuthProvider#oauth_provider.token_endpoint,
Header = [],
Type = ?CONTENT_URLENCODED,
Body = build_refresh_token_request_body(Request),
HTTPOptions = get_ssl_options_if_any(OAuthProvider) ++
get_timeout_of_default(Request#refresh_token_request.timeout),
Options = [],
Response = httpc:request(post, {URL, Header, Type, Body}, HTTPOptions, Options),
parse_access_token_response(Response).
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()}.
get_openid_configuration(IssuerURI, OpenIdConfigurationPath, TLSOptions) ->
URLMap = uri_string:parse(IssuerURI),
Path = append_paths(maps:get(path, URLMap), OpenIdConfigurationPath),
URL = uri_string:resolve(Path, IssuerURI),
rabbit_log:debug("get_openid_configuration issuer URL ~p (~p)", [URL, TLSOptions]),
Options = [],
Response = httpc:request(get, {URL, []}, TLSOptions, Options),
enrich_oauth_provider(parse_openid_configuration_response(Response), TLSOptions).
-spec get_openid_configuration(uri_string:uri_string(), ssl:tls_option() | []) -> {ok, oauth_provider()} | {error, term()}.
get_openid_configuration(IssuerURI, TLSOptions) ->
get_openid_configuration(IssuerURI, ?DEFAULT_OPENID_CONFIGURATION_PATH, TLSOptions).
update_oauth_provider_endpoints_configuration(OAuthProvider) ->
LockId = lock(),
try do_update_oauth_provider_endpoints_configuration(OAuthProvider) of
V -> V
after
unlock(LockId)
end.
update_oauth_provider_endpoints_configuration(OAuthProviderId, OAuthProvider) ->
LockId = lock(),
try do_update_oauth_provider_endpoints_configuration(OAuthProviderId, OAuthProvider) of
V -> V
after
unlock(LockId)
end.
do_update_oauth_provider_endpoints_configuration(OAuthProvider) ->
case OAuthProvider#oauth_provider.token_endpoint of
undefined -> do_nothing;
TokenEndPoint -> application:set_env(rabbitmq_auth_backend_oauth2, token_endpoint, TokenEndPoint)
end,
case OAuthProvider#oauth_provider.authorization_endpoint of
undefined -> do_nothing;
AuthzEndPoint -> application:set_env(rabbitmq_auth_backend_oauth2, authorization_endpoint, AuthzEndPoint)
end,
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]
end,
application:set_env(rabbitmq_auth_backend_oauth2, key_config, ModifiedList),
rabbit_log:debug("Updated oauth_provider details: ~p ", [ 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.jwks_uri of
undefined -> ModifiedList1;
JwksEndPoint -> [{jwks_uri, JwksEndPoint} | ModifiedList1]
end,
ModifiedOAuthProviders = maps:put(OAuthProviderId, ModifiedList2, OAuthProviders),
application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, ModifiedOAuthProviders),
rabbit_log:debug("Replacing oauth_providers ~p", [ ModifiedOAuthProviders]),
OAuthProvider.
use_global_locks_on_all_nodes() ->
case application:get_env(rabbitmq_auth_backend_oauth2, use_global_locks, true) of
true -> {rabbit_nodes:list_running(), rabbit_nodes:lock_retries()};
_ -> {}
end.
lock() ->
case use_global_locks_on_all_nodes() of
{} ->
case global:set_lock({oauth2_config_lock, rabbitmq_auth_backend_oauth2}) of
true -> rabbitmq_auth_backend_oauth2;
false -> undefined
end;
{Nodes, Retries} ->
case global:set_lock({oauth2_config_lock, rabbitmq_auth_backend_oauth2}, Nodes, Retries) of
true -> rabbitmq_auth_backend_oauth2;
false -> undefined
end
end.
unlock(LockId) ->
case LockId of
undefined -> ok;
Value ->
case use_global_locks_on_all_nodes() of
{} -> global:del_lock({oauth2_config_lock, Value});
{Nodes, _Retries} -> global:del_lock({oauth2_config_lock, Value}, Nodes)
end
end.
-spec get_oauth_provider(list()) -> {ok, oauth_provider()} | {error, any()}.
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)
end.
get_oauth_provider_from_keyconfig(ListOfRequiredAttributes) ->
OAuthProvider = lookup_oauth_provider_from_keyconfig(),
rabbit_log:debug("Using oauth_provider ~p from keyconfig", [OAuthProvider]),
case find_missing_attributes(OAuthProvider, ListOfRequiredAttributes) of
[] -> {ok, OAuthProvider};
_ -> 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)};
{error, _} = Error2 -> Error2
end
end,
case Result2 of
{ok, OAuthProvider2} ->
case find_missing_attributes(OAuthProvider2, ListOfRequiredAttributes) of
[] ->
rabbit_log:debug("Resolved oauth_provider ~p", [OAuthProvider]),
{ok, OAuthProvider2};
_ = Attrs->
{error, {missing_oauth_provider_attributes, Attrs}}
end;
{error, _} = Error3 -> Error3
end
end.
-spec get_oauth_provider(oauth_provider_id(), list()) -> {ok, oauth_provider()} | {error, any()}.
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
{error, _} = Error0 ->
rabbit_log:debug("Failed to find oauth_provider ~p configuration due to ~p", [OAuth2ProviderId, 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]),
case find_missing_attributes(OAuthProvider, ListOfRequiredAttributes) of
[] -> {ok, OAuthProvider};
_ -> 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]),
case get_openid_configuration(Issuer, get_ssl_options_if_any(OAuthProvider)) of
{ok, OauthProvider} -> {ok, update_oauth_provider_endpoints_configuration(OAuth2ProviderId, OauthProvider)};
{error, _} = Error2 -> Error2
end
end,
case Result2 of
{ok, OAuthProvider2} ->
case find_missing_attributes(OAuthProvider2, ListOfRequiredAttributes) of
[] ->
rabbit_log:debug("Resolved oauth_provider ~p", [OAuthProvider]),
{ok, OAuthProvider2};
_ = Attrs->
{error, {missing_oauth_provider_attributes, Attrs}}
end;
{error, _} = Error3 -> Error3
end
end
end.
%% HELPER functions
oauth_provider_to_proplists(#oauth_provider{} = OAuthProvider) ->
lists:zip(record_info(fields, oauth_provider), tl(tuple_to_list(OAuthProvider))).
filter_undefined_props(PropList) ->
lists:foldl(fun(Prop, Acc) ->
case Prop of
{Name, undefined} -> Acc ++ [Name];
_ -> Acc
end end, [], PropList).
intersection(S1, S2) -> intersection(S1, S2, []).
is_element(H, [H|_]) -> true;
is_element(H, [_|Set]) -> is_element(H, Set);
is_element(_, []) -> false.
intersection([], _, S) -> S;
intersection([H|T], S1, S) ->
case is_element(H,S1) of
true -> intersection(T, S1, [H|S]);
false -> intersection(T, S1, S)
end.
find_missing_attributes(#oauth_provider{} = OAuthProvider, RequiredAttributes) ->
PropList = oauth_provider_to_proplists(OAuthProvider),
Filtered = filter_undefined_props(PropList),
intersection(Filtered, RequiredAttributes).
lookup_oauth_provider_from_keyconfig() ->
Issuer = application:get_env(rabbitmq_auth_backend_oauth2, issuer, undefined),
TokenEndpoint = application:get_env(rabbitmq_auth_backend_oauth2, token_endpoint, undefined),
Map = maps:from_list(application:get_env(rabbitmq_auth_backend_oauth2, key_config, [])),
#oauth_provider{
issuer=Issuer,
jwks_uri=maps:get(jwks_url, Map, undefined), %% jwks_url not uri . _url is the legacy name
token_endpoint=TokenEndpoint,
ssl_options=extract_ssl_options_as_list(Map)
}.
extract_ssl_options_as_list(Map) ->
Verify = case maps:get(cacertfile, Map, undefined) of
undefined -> verify_none;
_ -> maps:get(peer_verification, Map, verify_peer)
end,
[ {verify, Verify},
{cacertfile, maps:get(cacertfile, Map, "")},
{depth, maps:get(depth, Map, 10)},
{crl_check, maps:get(crl_check, Map, false)},
{fail_if_no_peer_cert, maps:get(fail_if_no_peer_cert, Map, false)}
] ++
case maps:get(hostname_verification, Map, none) of
wildcard ->
[{customize_hostname_check, [{match_fun, public_key:pkix_verify_hostname_match_fun(https)}]}];
none ->
[]
end.
lookup_oauth_provider_config(OAuth2ProviderId) ->
case application:get_env(rabbitmq_auth_backend_oauth2, oauth_providers) of
undefined -> {error, oauth_providers_not_found};
{ok, MapOfProviders} when is_map(MapOfProviders) ->
case maps:get(OAuth2ProviderId, MapOfProviders, undefined) of
undefined ->
{error, {oauth_provider_not_found, OAuth2ProviderId}};
Value -> Value
end;
_ -> {error, invalid_oauth_provider_configuration}
end.
build_access_token_request_body(Request) ->
uri_string:compose_query([
grant_type_request_parameter(?CLIENT_CREDENTIALS_GRANT_TYPE),
client_id_request_parameter(Request#access_token_request.client_id),
client_secret_request_parameter(Request#access_token_request.client_secret)]
++ scope_request_parameter_or_default(Request#access_token_request.scope, [])).
build_refresh_token_request_body(Request) ->
uri_string:compose_query([
grant_type_request_parameter(?REFRESH_TOKEN_GRANT_TYPE),
refresh_token_request_parameter(Request#refresh_token_request.refresh_token),
client_id_request_parameter(Request#refresh_token_request.client_id),
client_secret_request_parameter(Request#refresh_token_request.client_secret)]
++ scope_request_parameter_or_default(Request#refresh_token_request.scope, [])).
grant_type_request_parameter(Type) ->
{?REQUEST_GRANT_TYPE, Type}.
client_id_request_parameter(Client_id) ->
{?REQUEST_CLIENT_ID, binary_to_list(Client_id)}.
client_secret_request_parameter(Client_secret) ->
{?REQUEST_CLIENT_SECRET, binary_to_list(Client_secret)}.
refresh_token_request_parameter(RefreshToken) ->
{?REQUEST_REFRESH_TOKEN, RefreshToken}.
scope_request_parameter_or_default(Scope, Default) ->
case Scope of
undefined -> Default;
<<>> -> Default;
Scope -> [{?REQUEST_SCOPE, Scope}]
end.
get_ssl_options_if_any(OAuthProvider) ->
case OAuthProvider#oauth_provider.ssl_options of
undefined -> [];
Options -> [{ssl, Options}]
end.
get_timeout_of_default(Timeout) ->
case Timeout of
undefined -> [{timeout, ?DEFAULT_HTTP_TIMEOUT}];
Timeout -> [{timeout, Timeout}]
end.
is_json(?CONTENT_JSON) -> true;
is_json(_) -> false.
-spec decode_body(string(), string() | binary() | term()) -> 'false' | 'null' | 'true' |
binary() | [any()] | number() | map() | {error, term()}.
decode_body(_, []) -> [];
decode_body(?CONTENT_JSON, Body) ->
case rabbit_json:try_decode(rabbit_data_coercion:to_binary(Body)) of
{ok, Value} ->
Value;
{error, _} = Error ->
Error
end;
decode_body(MimeType, Body) ->
Items = string:split(MimeType, ";"),
case lists:any(fun is_json/1, Items) of
true -> decode_body(?CONTENT_JSON, Body);
false -> {error, mime_type_is_not_json}
end.
map_to_successful_access_token_response(Json) ->
#successful_access_token_response{
access_token=maps:get(?RESPONSE_ACCESS_TOKEN, Json),
token_type=maps:get(?RESPONSE_TOKEN_TYPE, Json, undefined),
refresh_token=maps:get(?RESPONSE_REFRESH_TOKEN, Json, undefined),
expires_in=maps:get(?RESPONSE_EXPIRES_IN, Json, undefined)
}.
map_to_unsuccessful_access_token_response(Json) ->
#unsuccessful_access_token_response{
error=maps:get(?RESPONSE_ERROR, Json),
error_description=maps:get(?RESPONSE_ERROR_DESCRIPTION, Json, 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),
jwks_uri=maps:get(?RESPONSE_JWKS_URI, Map, undefined)
};
map_to_oauth_provider(PropList) when is_list(PropList) ->
#oauth_provider{
issuer=proplists:get_value(issuer, PropList),
token_endpoint=proplists:get_value(token_endpoint, PropList),
authorization_endpoint=proplists:get_value(authorization_endpoint, PropList, undefined),
jwks_uri=proplists:get_value(jwks_uri, PropList, undefined),
ssl_options=map_ssl_options(proplists:get_value(https, PropList, undefined))
}.
map_ssl_options(undefined) ->
[{verify, verify_none},
{depth, 10},
{fail_if_no_peer_cert, false},
{crl_check, false},
{crl_cache, {ssl_crl_cache, {internal, [{http, 10000}]}}}];
map_ssl_options(Ssl_options) ->
Ssl_options1 = [{verify, proplists:get_value(verify, Ssl_options, verify_none)},
{depth, proplists:get_value(depth, Ssl_options, 10)},
{fail_if_no_peer_cert, proplists:get_value(fail_if_no_peer_cert, Ssl_options, false)},
{crl_check, proplists:get_value(crl_check, Ssl_options, false)},
{crl_cache, {ssl_crl_cache, {internal, [{http, 10000}]}}} | cacertfile(Ssl_options)],
case proplists:get_value(hostname_verification, Ssl_options, none) of
wildcard ->
[{customize_hostname_check, [{match_fun, public_key:pkix_verify_hostname_match_fun(https)}]} | Ssl_options1];
none ->
Ssl_options1
end.
cacertfile(Ssl_options) ->
case proplists:get_value(cacertfile, Ssl_options) of
undefined -> [];
CaCertFile -> [{cacertfile, CaCertFile}]
end.
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}} ->
{error, InternalError};
{error, _} = Error ->
Error;
Value ->
case Code of
200 -> {ok, map_to_successful_access_token_response(Value)};
201 -> {ok, map_to_successful_access_token_response(Value)};
204 -> {ok, []};
400 -> {error, map_to_unsuccessful_access_token_response(Value)};
401 -> {error, map_to_unsuccessful_access_token_response(Value)};
_ -> {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).

View File

@ -0,0 +1,52 @@
-module(oauth_http_mock).
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
-export([init/2]).
%%% CALLBACKS
init(Req, #{request := ExpectedRequest, response := ExpectedResponse} = Expected) ->
match_request(Req, ExpectedRequest),
{Code, Headers, JsonPayload} = produce_expected_response(ExpectedResponse),
{ok, case JsonPayload of
undefined -> cowboy_req:reply(Code, Req);
_ -> cowboy_req:reply(Code, Headers, JsonPayload, Req)
end, Expected}.
match_request_parameters_in_body(Req, #{parameters := Parameters}) ->
?assertEqual(true, cowboy_req:has_body(Req)),
{ok, KeyValues, _Req2} = cowboy_req:read_urlencoded_body(Req),
[ ?assertEqual(Value, proplists:get_value(list_to_binary(Parameter), KeyValues))
|| {Parameter, Value} <- Parameters].
match_request(Req, #{method := Method} = ExpectedRequest) ->
?assertEqual(Method, maps:get(method, Req)),
case maps:is_key(parameters, ExpectedRequest) of
true -> match_request_parameters_in_body(Req, ExpectedRequest);
false -> ok
end.
produce_expected_response(ExpectedResponse) ->
case proplists:is_defined(content_type, ExpectedResponse) of
true ->
Payload = proplists:get_value(payload, ExpectedResponse),
case is_proplist(Payload) of
true ->
{ proplists:get_value(code, ExpectedResponse),
#{<<"content-type">> => proplists:get_value(content_type, ExpectedResponse)},
rabbit_json:encode(Payload)
};
_ ->
{ proplists:get_value(code, ExpectedResponse),
#{<<"content-type">> => proplists:get_value(content_type, ExpectedResponse)},
Payload
}
end;
false -> {proplists:get_value(code, ExpectedResponse), undefined, undefined}
end.
is_proplist([{_Key, _Val}|_] = List) -> lists:all(fun({_K, _V}) -> true; (_) -> false end, List);
is_proplist(_) -> false.

518
deps/oauth2_client/test/system_SUITE.erl vendored Normal file
View File

@ -0,0 +1,518 @@
%% 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) 2017-2023 VMware, Inc. or its affiliates. All rights reserved.
-module(system_SUITE).
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
-include_lib("rabbit_common/include/rabbit.hrl").
-include_lib("oauth2_client.hrl").
-compile(export_all).
-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")},
{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")},
{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">>}
]}
]
}).
all() ->
[
{group, http_up}
,{group, http_down}
,{group, https}
].
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_access_token, [], [
grants_access_token,
denies_access_token,
auth_server_error,
non_json_payload,
grants_refresh_token,
grants_access_token_using_oauth_provider_id
]},
{verify_get_oauth_provider, [], [
get_oauth_provider,
get_oauth_provider_given_oauth_provider_id
]},
{http_down, [], [
connection_error
]},
{https, [], [
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} ]}
| Config].
end_per_suite(Config) ->
Config.
init_per_group(https, Config) ->
{ok, _} = application:ensure_all_started(ssl),
application:ensure_all_started(cowboy),
Config0 = rabbit_ct_helpers:run_setup_steps(Config),
CertsDir = ?config(rmq_certsdir, Config0),
CaCertFile = filename:join([CertsDir, "testca", "cacert.pem"]),
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))},
{issuer, build_issuer("https")},
{oauth_provider_with_wrong_ca, build_https_oauth_provider(WrongCaCertFile)} |
Config0];
init_per_group(http_up, 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];
init_per_group(http_down, Config) ->
[
{issuer, build_issuer("http")},
{oauth_provider_id, <<"uaa">>},
{oauth_provider, build_http_oauth_provider()} | Config];
init_per_group(with_all_oauth_provider_settings, Config) ->
[
{with_all_oauth_provider_settings, true} | Config];
init_per_group(without_all_oauth_providers_settings, Config) ->
[
{with_all_oauth_provider_settings, false} | Config];
init_per_group(_, Config) ->
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
end.
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),
KeyConfig = [ { jwks_url, OAuthProvider#oauth_provider.jwks_uri } ] ++
case OAuthProvider#oauth_provider.ssl_options of
undefined -> [];
_ -> [ {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),
KeyConfig =
case OAuthProvider#oauth_provider.ssl_options of
undefined -> [];
_ -> [ {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).
init_per_testcase(TestCase, Config) ->
application:set_env(rabbitmq_auth_backend_oauth2, use_global_locks, false),
case ?config(with_all_oauth_provider_settings, Config) of
false -> configure_minimum_oauth_provider_settings(Config);
true -> configure_all_oauth_provider_settings(Config);
undefined -> configure_all_oauth_provider_settings(Config)
end,
HttpOauthServerExpectations = get_http_oauth_server_expectations(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
end,
[{oauth_server_expectations, HttpOauthServerExpectations} | Config ].
end_per_testcase(_, Config) ->
application:unset_env(rabbitmq_auth_backend_oauth2, oauth_providers),
application:unset_env(rabbitmq_auth_backend_oauth2, issuer),
application:unset_env(rabbitmq_auth_backend_oauth2, token_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
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(_, Config) ->
Config.
grants_access_token_dynamically_resolving_oauth_provider(Config) ->
#{request := #{parameters := Parameters},
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)),
?assertEqual(proplists:get_value(token_type, JsonPayload), TokenType),
?assertEqual(proplists:get_value(access_token, JsonPayload), AccessToken).
grants_access_token_using_oauth_provider_id(Config) ->
#{request := #{parameters := Parameters},
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)),
?assertEqual(proplists:get_value(token_type, JsonPayload), TokenType),
?assertEqual(proplists:get_value(access_token, JsonPayload), AccessToken).
grants_access_token(Config) ->
#{request := #{parameters := Parameters},
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)),
?assertEqual(proplists:get_value(token_type, JsonPayload), TokenType),
?assertEqual(proplists:get_value(access_token, JsonPayload), AccessToken).
grants_refresh_token(Config) ->
#{request := #{parameters := Parameters},
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)),
?assertEqual(proplists:get_value(token_type, JsonPayload), TokenType),
?assertEqual(proplists:get_value(access_token, JsonPayload), AccessToken).
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)),
?assertEqual(proplists:get_value(error, JsonPayload), Error),
?assertEqual(proplists:get_value(error_description, JsonPayload), ErrorDescription).
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)).
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)).
connection_error(Config) ->
#{request := #{parameters := Parameters}} = lookup_expectation(token_endpoint, Config),
{error, {failed_connect, _ErrorArgs} } = oauth2_client:get_access_token(
?config(oauth_provider, Config), build_access_token_request(Parameters)).
ssl_connection_error(Config) ->
#{request := #{parameters := Parameters}} = lookup_expectation(token_endpoint, 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}} =
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).
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, jwks_uri = Jwks_uri}} =
oauth2_client:get_oauth_provider(?config(oauth_provider_id, Config), [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).
%%% HELPERS
build_issuer(Scheme) ->
uri_string:recompose(#{scheme => Scheme,
host => "localhost",
port => rabbit_data_coercion:to_integer(?AUTH_PORT),
path => ""}).
build_token_endpoint_uri(Scheme) ->
uri_string:recompose(#{scheme => Scheme,
host => "localhost",
port => rabbit_data_coercion:to_integer(?AUTH_PORT),
path => "/token"}).
build_jwks_uri(Scheme) ->
uri_string:recompose(#{scheme => Scheme,
host => "localhost",
port => rabbit_data_coercion:to_integer(?AUTH_PORT),
path => "/certs"}).
build_access_token_request(Request) ->
#access_token_request {
client_id = proplists:get_value(?REQUEST_CLIENT_ID, Request),
client_secret = proplists:get_value(?REQUEST_CLIENT_SECRET, Request)
}.
build_refresh_token_request(Request) ->
#refresh_token_request{
client_id = proplists:get_value(?REQUEST_CLIENT_ID, 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 {
issuer = OauthProvider#oauth_provider.issuer,
ssl_options = OauthProvider#oauth_provider.ssl_options
}.
build_https_oauth_provider(CaCertFile) ->
#oauth_provider {
issuer = build_issuer("https"),
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}) ->
[ { issuer, Issuer}, {token_endpoint, TokenEndpoint},
{ https, SslOptions}, {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}}).
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 ]}
]),
ct:log("start_https_oauth_server with expectation list : ~p -> dispatch: ~p", [Expectations, Expectations]),
{ok, _} = 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}});
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]),
{ok, _} = 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}}).
stop_http_auth_server() ->
cowboy:stop_listener(mock_http_auth_listener).
-spec ssl_options(ssl:verify_type(), boolean(), file:filename()) -> list().
ssl_options(PeerVerification, FailIfNoPeerCert, CaCertFile) ->
[{verify, PeerVerification},
{depth, 10},
{fail_if_no_peer_cert, FailIfNoPeerCert},
{crl_check, false},
{crl_cache, {ssl_crl_cache, {internal, [{http, 10000}]}}},
{cacertfile, CaCertFile}].

View File

@ -1,6 +1,8 @@
-module(auth_http_mock).
-export([init/2]).
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
%%% CALLBACKS

View File

@ -50,6 +50,7 @@ rabbitmq_app(
"@base64url//:erlang_app",
"@cowlib//:erlang_app",
"@jose//:erlang_app",
"//deps/oauth2_client:erlang_app",
],
)
@ -82,6 +83,7 @@ eunit(
compiled_suites = [
":test_jwks_http_app_beam",
":test_jwks_http_handler_beam",
":test_openid_http_handler_beam",
":test_jwks_http_sup_beam",
":test_rabbit_auth_backend_oauth2_test_util_beam",
],
@ -99,16 +101,26 @@ rabbitmq_integration_suite(
size = "small",
)
rabbitmq_integration_suite(
name = "add_signing_key_command_SUITE",
size = "small",
)
rabbitmq_integration_suite(
name = "config_schema_SUITE",
)
rabbitmq_integration_suite(
name = "rabbit_oauth2_config_SUITE",
)
rabbitmq_integration_suite(
name = "jwks_SUITE",
additional_beam = [
"test/rabbit_auth_backend_oauth2_test_util.beam",
"test/jwks_http_app.beam",
"test/jwks_http_handler.beam",
"test/openid_http_handler.beam",
"test/jwks_http_sup.beam",
],
deps = [
@ -124,6 +136,7 @@ rabbitmq_suite(
],
)
rabbitmq_integration_suite(
name = "system_SUITE",
size = "medium",
@ -132,6 +145,7 @@ rabbitmq_integration_suite(
],
runtime_deps = [
"@emqtt//:erlang_app",
"//deps/oauth2_client:erlang_app",
],
)

View File

@ -6,9 +6,9 @@ BUILD_WITHOUT_QUIC=1
export BUILD_WITHOUT_QUIC
LOCAL_DEPS = inets public_key
BUILD_DEPS = rabbit_common
BUILD_DEPS = rabbit_common oauth2_client
DEPS = rabbit cowlib jose base64url
TEST_DEPS = cowboy rabbitmq_web_dispatch rabbitmq_ct_helpers rabbitmq_ct_client_helpers amqp_client rabbitmq_mqtt emqtt
TEST_DEPS = cowboy rabbitmq_web_dispatch rabbitmq_ct_helpers rabbitmq_ct_client_helpers amqp_client rabbitmq_mqtt emqtt oauth2_client
DEP_EARLY_PLUGINS = rabbit_common/mk/rabbitmq-early-plugin.mk
DEP_PLUGINS = rabbit_common/mk/rabbitmq-plugin.mk

View File

@ -9,9 +9,11 @@ def all_beam_files(name = "all_beam_files"):
erlang_bytecode(
name = "other_beam",
srcs = [
"src/Elixir.RabbitMQ.CLI.Ctl.Commands.AddSigningKeyCommand.erl",
"src/Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand.erl",
"src/rabbit_auth_backend_oauth2.erl",
"src/rabbit_auth_backend_oauth2_app.erl",
"src/rabbit_oauth2_config.erl",
"src/rabbit_oauth2_scope.erl",
"src/uaa_jwks.erl",
"src/uaa_jwt.erl",
@ -26,6 +28,7 @@ def all_beam_files(name = "all_beam_files"):
deps = [
"//deps/rabbit_common:erlang_app",
"//deps/rabbitmq_cli:erlang_app",
"//deps/oauth2_client:erlang_app",
"@jose//:erlang_app",
],
)
@ -40,9 +43,11 @@ def all_test_beam_files(name = "all_test_beam_files"):
name = "test_other_beam",
testonly = True,
srcs = [
"src/Elixir.RabbitMQ.CLI.Ctl.Commands.AddSigningKeyCommand.erl",
"src/Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand.erl",
"src/rabbit_auth_backend_oauth2.erl",
"src/rabbit_auth_backend_oauth2_app.erl",
"src/rabbit_oauth2_config.erl",
"src/rabbit_oauth2_scope.erl",
"src/uaa_jwks.erl",
"src/uaa_jwt.erl",
@ -58,6 +63,7 @@ def all_test_beam_files(name = "all_test_beam_files"):
"//deps/rabbit_common:erlang_app",
"//deps/rabbitmq_cli:erlang_app",
"@jose//:erlang_app",
"//deps/oauth2_client:erlang_app",
],
)
@ -82,9 +88,11 @@ def all_srcs(name = "all_srcs"):
filegroup(
name = "srcs",
srcs = [
"src/Elixir.RabbitMQ.CLI.Ctl.Commands.AddSigningKeyCommand.erl",
"src/Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand.erl",
"src/rabbit_auth_backend_oauth2.erl",
"src/rabbit_auth_backend_oauth2_app.erl",
"src/rabbit_oauth2_config.erl",
"src/rabbit_oauth2_scope.erl",
"src/uaa_jwks.erl",
"src/uaa_jwt.erl",
@ -147,7 +155,7 @@ def test_suite_beam_files(name = "test_suite_beam_files"):
outs = ["test/system_SUITE.beam"],
app_name = "rabbitmq_auth_backend_oauth2",
erlc_opts = "//:test_erlc_opts",
deps = ["//deps/amqp_client:erlang_app"],
deps = ["//deps/amqp_client:erlang_app","//deps/oauth2_client:erlang_app"],
)
erlang_bytecode(
name = "test_jwks_http_app_beam",
@ -166,6 +174,15 @@ def test_suite_beam_files(name = "test_suite_beam_files"):
erlc_opts = "//:test_erlc_opts",
deps = ["@cowboy//:erlang_app"],
)
erlang_bytecode(
name = "test_openid_http_handler_beam",
testonly = True,
srcs = ["test/openid_http_handler.erl"],
outs = ["test/openid_http_handler.beam"],
app_name = "rabbitmq_auth_backend_oauth2",
erlc_opts = "//:test_erlc_opts",
deps = ["@cowboy//:erlang_app"],
)
erlang_bytecode(
name = "test_jwks_http_sup_beam",
testonly = True,
@ -199,3 +216,20 @@ def test_suite_beam_files(name = "test_suite_beam_files"):
app_name = "rabbitmq_auth_backend_oauth2",
erlc_opts = "//:test_erlc_opts",
)
erlang_bytecode(
name = "rabbit_oauth2_config_SUITE_beam_files",
testonly = True,
srcs = ["test/rabbit_oauth2_config_SUITE.erl"],
outs = ["test/rabbit_oauth2_config_SUITE.beam"],
app_name = "rabbitmq_auth_backend_oauth2",
erlc_opts = "//:test_erlc_opts",
)
erlang_bytecode(
name = "add_signing_key_command_SUITE_beam_files",
testonly = True,
srcs = ["test/add_signing_key_command_SUITE.erl"],
outs = ["test/add_signing_key_command_SUITE.beam"],
app_name = "rabbitmq_auth_backend_oauth2",
erlc_opts = "//:test_erlc_opts",
deps = ["//deps/rabbit_common:erlang_app"],
)

View File

@ -73,6 +73,7 @@
list_to_binary(cuttlefish:conf_get("auth_oauth2.additional_scopes_key", Conf))
end}.
%% Configure the plugin to skip validation of the aud field
%%
%% {verify_aud, true},
@ -98,9 +99,11 @@
"rabbitmq_auth_backend_oauth2.preferred_username_claims",
fun(Conf) ->
Settings = cuttlefish_variable:filter_by_prefix("auth_oauth2.preferred_username_claims", Conf),
[list_to_binary(V) || {_, V} <- Settings]
[list_to_binary(V) || {_, V} <- lists:reverse(Settings)]
end}.
%% ID of the default signing key
%%
%% {default_key, <<"key-1">>},
@ -145,6 +148,16 @@
maps:from_list(SigningKeys)
end}.
{mapping,
"auth_oauth2.issuer",
"rabbitmq_auth_backend_oauth2.issuer",
[{datatype, string}, {validators, ["uri", "https_uri"]}]}.
{mapping,
"auth_oauth2.token_endpoint",
"rabbitmq_auth_backend_oauth2.token_endpoint",
[{datatype, string}, {validators, ["uri", "https_uri"]}]}.
{mapping,
"auth_oauth2.jwks_url",
"rabbitmq_auth_backend_oauth2.key_config.jwks_url",
@ -193,3 +206,149 @@
Settings = cuttlefish_variable:filter_by_prefix("auth_oauth2.algorithms", Conf),
[list_to_binary(V) || {_, V} <- Settings]
end}.
%% This setting is only required when there are +1 auth_oauth2.oauth_providers
%% If this setting is omitted, its default to the first oauth_provider
{mapping,
"auth_oauth2.default_oauth_provider",
"rabbitmq_auth_backend_oauth2.default_oauth_provider",
[{datatype, string}]}.
{mapping,
"auth_oauth2.oauth_providers.$name.issuer",
"rabbitmq_auth_backend_oauth2.oauth_providers",
[{datatype, string}, {validators, ["uri", "https_uri"]}]
}.
{mapping,
"auth_oauth2.oauth_providers.$name.token_endpoint",
"rabbitmq_auth_backend_oauth2.oauth_providers",
[{datatype, string}, {validators, ["uri", "https_uri"]}]
}.
{mapping,
"auth_oauth2.oauth_providers.$name.jwks_uri",
"rabbitmq_auth_backend_oauth2.oauth_providers",
[{datatype, string}, {validators, ["uri", "https_uri"]}]
}.
{mapping,
"auth_oauth2.oauth_providers.$name.https.verify",
"rabbitmq_auth_backend_oauth2.oauth_providers",
[{datatype, {enum, [verify_peer, verify_none]}}]}.
{mapping,
"auth_oauth2.oauth_providers.$name.https.cacertfile",
"rabbitmq_auth_backend_oauth2.oauth_providers",
[{datatype, file}, {validators, ["file_accessible"]}]}.
{mapping,
"auth_oauth2.oauth_providers.$name.https.depth",
"rabbitmq_auth_backend_oauth2.oauth_providers",
[{datatype, integer}]}.
{mapping,
"auth_oauth2.oauth_providers.$name.https.hostname_verification",
"rabbitmq_auth_backend_oauth2.oauth_providers",
[{datatype, {enum, [wildcard, none]}}]}.
{mapping,
"auth_oauth2.oauth_providers.$name.https.crl_check",
"rabbitmq_auth_backend_oauth2.oauth_providers",
[{datatype, {enum, [true, false, peer, best_effort]}}]}.
{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)
end}.
{mapping,
"auth_oauth2.resource_servers.$name.id",
"rabbitmq_auth_backend_oauth2.resource_servers",
[{datatype, string}]
}.
{mapping,
"auth_oauth2.resource_servers.$name.scope_prefix",
"rabbitmq_auth_backend_oauth2.resource_servers",
[{datatype, string}]
}.
{mapping,
"auth_oauth2.resource_servers.$name.additional_scopes_key",
"rabbitmq_auth_backend_oauth2.resource_servers",
[{datatype, string}]
}.
{mapping,
"auth_oauth2.resource_servers.$name.resource_server_type",
"rabbitmq_auth_backend_oauth2.resource_servers",
[{datatype, string}]
}.
{mapping,
"auth_oauth2.resource_servers.$name.oauth_provider_id",
"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)
end}.

View File

@ -0,0 +1,137 @@
%% 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-2023 VMware, Inc. or its affiliates. All rights reserved.
%%
-module('Elixir.RabbitMQ.CLI.Ctl.Commands.AddSigningKeyCommand').
-behaviour('Elixir.RabbitMQ.CLI.CommandBehaviour').
-export([
usage/0,
validate/2,
merge_defaults/2,
banner/2,
run/2,
switches/0,
aliases/0,
output/2,
description/0,
formatter/0
]).
usage() ->
<<"add_signing_key <name> [--json=<json_key>] [--pem=<public_key>] [--pem-file=<pem_file>]">>.
description() -> <<"Add signing key required to validate JWT's digital signatures">>.
switches() ->
[{json, string},
{pem, string},
{pem_file, string}].
aliases() -> [].
validate([], _Options) -> {validation_failure, not_enough_args};
validate([_,_|_], _Options) -> {validation_failure, too_many_args};
validate([_], Options) ->
Json = maps:get(json, Options, undefined),
Pem = maps:get(pem, Options, undefined),
PemFile = maps:get(pem_file, Options, undefined),
case {is_binary(Json), is_binary(Pem), is_binary(PemFile)} of
{false, false, false} ->
{validation_failure,
{bad_argument, <<"No key specified">>}};
{true, false, false} ->
validate_json(Json);
{false, true, false} ->
validate_pem(Pem);
{false, false, true} ->
validate_pem_file(PemFile);
{_, _, _} ->
{validation_failure,
{bad_argument, <<"There can be only one key type">>}}
end.
validate_json(Json) ->
case rabbit_json:try_decode(Json) of
{ok, _} ->
case uaa_jwt:verify_signing_key(json, Json) of
ok -> ok;
{error, {fields_missing_for_kty, Kty}} ->
{validation_failure,
{bad_argument,
<<"Key fields are missing fot kty \"", Kty/binary, "\"">>}};
{error, unknown_kty} ->
{validation_failure,
{bad_argument, <<"\"kty\" field is invalid">>}};
{error, no_kty} ->
{validation_failure,
{bad_argument, <<"Json key should contain \"kty\" field">>}};
{error, Err} ->
{validation_failure, {bad_argument, Err}}
end;
{error, _} ->
{validation_failure, {bad_argument, <<"Invalid JSON">>}}
end.
validate_pem(Pem) ->
case uaa_jwt:verify_signing_key(pem, Pem) of
ok -> ok;
{error, invalid_pem_string} ->
{validation_failure, <<"Unable to read a key from the PEM string">>};
{error, Err} ->
{validation_failure, Err}
end.
validate_pem_file(PemFile) ->
case uaa_jwt:verify_signing_key(pem_file, PemFile) of
ok -> ok;
{error, enoent} ->
{validation_failure, {bad_argument, <<"PEM file not found">>}};
{error, invalid_pem_file} ->
{validation_failure, <<"Unable to read a key from the PEM file">>};
{error, Err} ->
{validation_failure, Err}
end.
merge_defaults(Args, #{pem_file := FileName} = Options) ->
AbsFileName = filename:absname(FileName),
{Args, Options#{pem_file := AbsFileName}};
merge_defaults(Args, Options) -> {Args, Options}.
banner([Name], #{json := Json}) ->
<<"Adding OAuth signing key \"",
Name/binary,
"\" in JSON format: \"",
Json/binary, "\"">>;
banner([Name], #{pem := Pem}) ->
<<"Adding OAuth signing key \"",
Name/binary,
"\" public key: \"",
Pem/binary, "\"">>;
banner([Name], #{pem_file := PemFile}) ->
<<"Adding OAuth signing key \"",
Name/binary,
"\" filename: \"",
PemFile/binary, "\"">>.
run([Name], #{node := Node} = Options) ->
{Type, Value} = case Options of
#{json := Json} -> {json, Json};
#{pem := Pem} -> {pem, Pem};
#{pem_file := PemFile} -> {pem_file, PemFile}
end,
case rabbit_misc:rpc_call(Node,
uaa_jwt, add_signing_key,
[Name, Type, Value]) of
{ok, _Keys} -> ok;
{error, Err} -> {error, Err}
end.
output(E, _Opts) ->
'Elixir.RabbitMQ.CLI.DefaultOutput':output(E).
formatter() -> 'Elixir.RabbitMQ.CLI.Formatters.Erlang'.

View File

@ -8,6 +8,8 @@
-behaviour('Elixir.RabbitMQ.CLI.CommandBehaviour').
-define(COMMAND, 'Elixir.RabbitMQ.CLI.Ctl.Commands.AddSigningKeyCommand').
-export([
usage/0,
validate/2,
@ -17,6 +19,7 @@
switches/0,
aliases/0,
output/2,
description/0,
formatter/0
]).
@ -24,118 +27,20 @@
usage() ->
<<"add_uaa_key <name> [--json=<json_key>] [--pem=<public_key>] [--pem-file=<pem_file>]">>.
switches() ->
[{json, string},
{pem, string},
{pem_file, string}].
description() -> <<"DEPRECATED. Use instead add_signing_key">>.
switches() -> ?COMMAND:switches().
aliases() -> [].
validate([], _Options) -> {validation_failure, not_enough_args};
validate([_,_|_], _Options) -> {validation_failure, too_many_args};
validate([_], Options) ->
Json = maps:get(json, Options, undefined),
Pem = maps:get(pem, Options, undefined),
PemFile = maps:get(pem_file, Options, undefined),
case {is_binary(Json), is_binary(Pem), is_binary(PemFile)} of
{false, false, false} ->
{validation_failure,
{bad_argument, <<"No key specified">>}};
{true, false, false} ->
validate_json(Json);
{false, true, false} ->
validate_pem(Pem);
{false, false, true} ->
validate_pem_file(PemFile);
{_, _, _} ->
{validation_failure,
{bad_argument, <<"There can be only one key type">>}}
end.
validate_json(Json) ->
case rabbit_json:try_decode(Json) of
{ok, _} ->
case uaa_jwt:verify_signing_key(json, Json) of
ok -> ok;
{error, {fields_missing_for_kty, Kty}} ->
{validation_failure,
{bad_argument,
<<"Key fields are missing fot kty \"", Kty/binary, "\"">>}};
{error, unknown_kty} ->
{validation_failure,
{bad_argument, <<"\"kty\" field is invalid">>}};
{error, no_kty} ->
{validation_failure,
{bad_argument, <<"Json key should contain \"kty\" field">>}};
{error, Err} ->
{validation_failure, {bad_argument, Err}}
end;
{error, _} ->
{validation_failure, {bad_argument, <<"Invalid JSON">>}}
end.
validate_pem(Pem) ->
case uaa_jwt:verify_signing_key(pem, Pem) of
ok -> ok;
{error, invalid_pem_string} ->
{validation_failure, <<"Unable to read a key from the PEM string">>};
{error, Err} ->
{validation_failure, Err}
end.
validate_pem_file(PemFile) ->
case uaa_jwt:verify_signing_key(pem_file, PemFile) of
ok -> ok;
{error, enoent} ->
{validation_failure, {bad_argument, <<"PEM file not found">>}};
{error, invalid_pem_file} ->
{validation_failure, <<"Unable to read a key from the PEM file">>};
{error, Err} ->
{validation_failure, Err}
end.
merge_defaults(Args, #{pem_file := FileName} = Options) ->
AbsFileName = filename:absname(FileName),
{Args, Options#{pem_file := AbsFileName}};
merge_defaults(Args, Options) -> {Args, Options}.
banner([Name], #{json := Json}) ->
<<"Adding UAA signing key \"",
Name/binary,
"\" in JSON format: \"",
Json/binary, "\"">>;
banner([Name], #{pem := Pem}) ->
<<"Adding UAA signing key \"",
Name/binary,
"\" public key: \"",
Pem/binary, "\"">>;
banner([Name], #{pem_file := PemFile}) ->
<<"Adding UAA signing key \"",
Name/binary,
"\" filename: \"",
PemFile/binary, "\"">>.
run([Name], #{node := Node} = Options) ->
{Type, Value} = case Options of
#{json := Json} -> {json, Json};
#{pem := Pem} -> {pem, Pem};
#{pem_file := PemFile} -> {pem_file, PemFile}
end,
case rabbit_misc:rpc_call(Node,
uaa_jwt, add_signing_key,
[Name, Type, Value]) of
{ok, _Keys} -> ok;
{error, Err} -> {error, Err}
end.
output(E, _Opts) ->
'Elixir.RabbitMQ.CLI.DefaultOutput':output(E).
formatter() -> 'Elixir.RabbitMQ.CLI.Formatters.Erlang'.
validate(Args, Options) -> ?COMMAND:validate(Args, Options).
merge_defaults(Args, Options) -> ?COMMAND:merge_defaults(Args, Options).
banner(Names, Args) -> ?COMMAND:banner(Names, Args).
run(Names, Options) -> ?COMMAND:run(Names, Options).
output(E, Opts) -> ?COMMAND:output(E, Opts).
formatter() -> ?COMMAND:formatter().

View File

@ -18,7 +18,7 @@
expiry_timestamp/1]).
% for testing
-export([post_process_payload/1, get_expanded_scopes/2]).
-export([post_process_payload/2, get_expanded_scopes/2]).
-import(rabbit_data_coercion, [to_map/1]).
@ -30,22 +30,13 @@
%% App environment
%%
-type app_env() :: [{atom(), any()}].
-define(APP, rabbitmq_auth_backend_oauth2).
-define(RESOURCE_SERVER_ID, resource_server_id).
-define(SCOPE_PREFIX, scope_prefix).
%% a term defined for Rich Authorization Request tokens to identify a RabbitMQ permission
-define(RESOURCE_SERVER_TYPE, resource_server_type).
%% verify server_server_id aud field is on the aud field
-define(VERIFY_AUD, verify_aud).
%% a term used by the IdentityServer community
-define(COMPLEX_CLAIM_APP_ENV_KEY, extra_scopes_source).
%% scope aliases map "role names" to a set of scopes
-define(SCOPE_MAPPINGS_APP_ENV_KEY, scope_aliases).
%% list of JWT claims (such as <<"sub">>) used to determine the username
-define(PREFERRED_USERNAME_CLAIMS, preferred_username_claims).
-define(DEFAULT_PREFERRED_USERNAME_CLAIMS, [<<"sub">>, <<"client_id">>]).
%%
%% Key JWT fields
@ -82,7 +73,8 @@ check_vhost_access(#auth_user{impl = DecodedTokenFun},
VHost, _AuthzData) ->
with_decoded_token(DecodedTokenFun(),
fun(_Token) ->
Scopes = get_scopes(DecodedTokenFun()),
DecodedToken = DecodedTokenFun(),
Scopes = get_scopes(DecodedToken),
ScopeString = rabbit_oauth2_scope:concat_scopes(Scopes, ","),
rabbit_log:debug("Matching virtual host '~ts' against the following scopes: ~ts", [VHost, ScopeString]),
rabbit_oauth2_scope:vhost_access(VHost, Scopes)
@ -132,6 +124,7 @@ expiry_timestamp(#auth_user{impl = DecodedTokenFun}) ->
authenticate(_, AuthProps0) ->
AuthProps = to_map(AuthProps0),
Token = token_from_context(AuthProps),
case check_token(Token) of
%% avoid logging the token
{error, _} = E -> E;
@ -141,9 +134,7 @@ authenticate(_, AuthProps0) ->
{refused, "Authentication using an OAuth 2/JWT token failed: ~tp", [Err]};
{ok, DecodedToken} ->
Func = fun(Token0) ->
Username = username_from(
application:get_env(?APP, ?PREFERRED_USERNAME_CLAIMS, []),
Token0),
Username = username_from(rabbit_oauth2_config:get_preferred_username_claims(), Token0),
Tags = tags_from(Token0),
{ok, #auth_user{username = Username,
@ -186,18 +177,16 @@ check_token(DecodedToken) when is_map(DecodedToken) ->
{ok, DecodedToken};
check_token(Token) ->
Settings = application:get_all_env(?APP),
case uaa_jwt:decode_and_verify(Token) of
{error, Reason} -> {refused, {error, Reason}};
{true, Payload} ->
validate_payload(post_process_payload(Payload, Settings));
{false, _} -> {refused, signature_invalid}
{error, Reason} ->
{refused, {error, Reason}};
{true, TargetResourceServerId, Payload} ->
Payload0 = post_process_payload(TargetResourceServerId, Payload),
validate_payload(TargetResourceServerId, Payload0)
% _ -> {refused, signature_invalid}
end.
post_process_payload(Payload) when is_map(Payload) ->
post_process_payload(Payload, []).
post_process_payload(Payload, AppEnv) when is_map(Payload) ->
post_process_payload(ResourceServerId, Payload) when is_map(Payload) ->
Payload0 = maps:map(fun(K, V) ->
case K of
?AUD_JWT_FIELD when is_binary(V) -> binary:split(V, <<" ">>, [global, trim_all]);
@ -207,8 +196,9 @@ post_process_payload(Payload, AppEnv) when is_map(Payload) ->
end,
Payload
),
Payload1 = case does_include_complex_claim_field(Payload0) of
true -> post_process_payload_with_complex_claim(Payload0);
Payload1 = case does_include_complex_claim_field(ResourceServerId, Payload0) of
true -> post_process_payload_with_complex_claim(ResourceServerId, Payload0);
false -> Payload0
end,
@ -217,56 +207,49 @@ post_process_payload(Payload, AppEnv) when is_map(Payload) ->
false -> Payload1
end,
Payload3 = case has_configured_scope_aliases(AppEnv) of
true -> post_process_payload_with_scope_aliases(Payload2, AppEnv);
Payload3 = case rabbit_oauth2_config:has_scope_aliases(ResourceServerId) of
true -> post_process_payload_with_scope_aliases(ResourceServerId, Payload2);
false -> Payload2
end,
Payload4 = case maps:is_key(<<"authorization_details">>, Payload3) of
true -> post_process_payload_in_rich_auth_request_format(Payload3);
true -> post_process_payload_in_rich_auth_request_format(ResourceServerId, Payload3);
false -> Payload3
end,
Payload4.
-spec has_configured_scope_aliases(AppEnv :: app_env()) -> boolean().
has_configured_scope_aliases(AppEnv) ->
Map = maps:from_list(AppEnv),
maps:is_key(?SCOPE_MAPPINGS_APP_ENV_KEY, Map).
-spec post_process_payload_with_scope_aliases(Payload :: map(), AppEnv :: app_env()) -> map().
-spec post_process_payload_with_scope_aliases(ResourceServerId :: binary(), Payload :: map()) -> map().
%% This is for those hopeless environments where the token structure is so out of
%% messaging team's control that even the extra scopes field is no longer an option.
%%
%% This assumes that scopes can be random values that do not follow the RabbitMQ
%% convention, or any other convention, in any way. They are just random client role IDs.
%% See rabbitmq/rabbitmq-server#4588 for details.
post_process_payload_with_scope_aliases(Payload, AppEnv) ->
post_process_payload_with_scope_aliases(ResourceServerId, Payload) ->
%% try JWT scope field value for alias
Payload1 = post_process_payload_with_scope_alias_in_scope_field(Payload, AppEnv),
Payload1 = post_process_payload_with_scope_alias_in_scope_field(ResourceServerId, Payload),
%% try the configurable 'extra_scopes_source' field value for alias
Payload2 = post_process_payload_with_scope_alias_in_extra_scopes_source(Payload1, AppEnv),
Payload2.
post_process_payload_with_scope_alias_in_extra_scopes_source(ResourceServerId, Payload1).
-spec post_process_payload_with_scope_alias_in_scope_field(Payload :: map(),
AppEnv :: app_env()) -> map().
-spec post_process_payload_with_scope_alias_in_scope_field(ResourceServerId :: binary(), Payload :: map()) -> map().
%% First attempt: use the value in the 'scope' field for alias
post_process_payload_with_scope_alias_in_scope_field(Payload, AppEnv) ->
ScopeMappings = proplists:get_value(?SCOPE_MAPPINGS_APP_ENV_KEY, AppEnv, #{}),
post_process_payload_with_scope_alias_in_scope_field(ResourceServerId, Payload) ->
ScopeMappings = rabbit_oauth2_config:get_scope_aliases(ResourceServerId),
post_process_payload_with_scope_alias_field_named(Payload, ?SCOPE_JWT_FIELD, ScopeMappings).
-spec post_process_payload_with_scope_alias_in_extra_scopes_source(Payload :: map(),
AppEnv :: app_env()) -> map().
-spec post_process_payload_with_scope_alias_in_extra_scopes_source(ResourceServerId :: binary(), Payload :: map()) -> map().
%% Second attempt: use the value in the configurable 'extra scopes source' field for alias
post_process_payload_with_scope_alias_in_extra_scopes_source(Payload, AppEnv) ->
ExtraScopesField = proplists:get_value(?COMPLEX_CLAIM_APP_ENV_KEY, AppEnv, undefined),
post_process_payload_with_scope_alias_in_extra_scopes_source(ResourceServerId, Payload) ->
ExtraScopesField = rabbit_oauth2_config:get_additional_scopes_key(ResourceServerId),
case ExtraScopesField of
%% nothing to inject
undefined -> Payload;
_ ->
ScopeMappings = proplists:get_value(?SCOPE_MAPPINGS_APP_ENV_KEY, AppEnv, #{}),
ScopeMappings = rabbit_oauth2_config:get_scope_aliases(ResourceServerId),
post_process_payload_with_scope_alias_field_named(Payload, ExtraScopesField, ScopeMappings)
end.
@ -274,8 +257,6 @@ post_process_payload_with_scope_alias_in_extra_scopes_source(Payload, AppEnv) ->
-spec post_process_payload_with_scope_alias_field_named(Payload :: map(),
Field :: binary(),
ScopeAliasMapping :: map()) -> map().
post_process_payload_with_scope_alias_field_named(Payload, undefined, _ScopeAliasMapping) ->
Payload;
post_process_payload_with_scope_alias_field_named(Payload, FieldName, ScopeAliasMapping) ->
Scopes0 = maps:get(FieldName, Payload, []),
Scopes = rabbit_data_coercion:to_list_of_binaries(Scopes0),
@ -298,15 +279,16 @@ post_process_payload_with_scope_alias_field_named(Payload, FieldName, ScopeAlias
maps:put(?SCOPE_JWT_FIELD, ExpandedScopes, Payload).
-spec does_include_complex_claim_field(Payload :: map()) -> boolean().
does_include_complex_claim_field(Payload) when is_map(Payload) ->
maps:is_key(application:get_env(?APP, ?COMPLEX_CLAIM_APP_ENV_KEY, undefined), Payload).
-spec post_process_payload_with_complex_claim(Payload :: map()) -> map().
post_process_payload_with_complex_claim(Payload) ->
ComplexClaim = maps:get(application:get_env(?APP, ?COMPLEX_CLAIM_APP_ENV_KEY, undefined), Payload),
ResourceServerId = rabbit_data_coercion:to_binary(application:get_env(?APP, ?RESOURCE_SERVER_ID, <<>>)),
-spec does_include_complex_claim_field(ResourceServerId :: binary(), Payload :: map()) -> boolean().
does_include_complex_claim_field(ResourceServerId, Payload) when is_map(Payload) ->
case rabbit_oauth2_config:has_additional_scopes_key(ResourceServerId) of
true -> maps:is_key(rabbit_oauth2_config:get_additional_scopes_key(ResourceServerId), Payload);
false -> false
end.
-spec post_process_payload_with_complex_claim(ResourceServerId :: binary(), Payload :: map()) -> map().
post_process_payload_with_complex_claim(ResourceServerId, Payload) ->
ComplexClaim = maps:get(rabbit_oauth2_config:get_additional_scopes_key(ResourceServerId), Payload),
AdditionalScopes =
case ComplexClaim of
L when is_list(L) -> L;
@ -486,13 +468,10 @@ is_recognized_permission(#{?ACTIONS_FIELD := _, ?LOCATIONS_FIELD:= _ , ?TYPE_FIE
is_recognized_permission(_, _) -> false.
-spec post_process_payload_in_rich_auth_request_format(Payload :: map()) -> map().
-spec post_process_payload_in_rich_auth_request_format(ResourceServerId :: binary(), Payload :: map()) -> map().
%% https://oauth.net/2/rich-authorization-requests/
post_process_payload_in_rich_auth_request_format(#{<<"authorization_details">> := Permissions} = Payload) ->
ResourceServerId = rabbit_data_coercion:to_binary(
application:get_env(?APP, ?RESOURCE_SERVER_ID, <<>>)),
ResourceServerType = rabbit_data_coercion:to_binary(
application:get_env(?APP, ?RESOURCE_SERVER_TYPE, <<>>)),
post_process_payload_in_rich_auth_request_format(ResourceServerId, #{<<"authorization_details">> := Permissions} = Payload) ->
ResourceServerType = rabbit_oauth2_config:get_resource_server_type(ResourceServerId),
FilteredPermissionsByType = lists:filter(fun(P) ->
is_recognized_permission(P, ResourceServerType) end, Permissions),
@ -503,24 +482,22 @@ post_process_payload_in_rich_auth_request_format(#{<<"authorization_details">> :
validate_payload(DecodedToken) ->
ResourceServerEnv = application:get_env(?APP, ?RESOURCE_SERVER_ID, <<>>),
ResourceServerId = rabbit_data_coercion:to_binary(ResourceServerEnv),
ScopePrefix = application:get_env(?APP, ?SCOPE_PREFIX, <<ResourceServerId/binary, ".">>),
validate_payload(DecodedToken, ResourceServerId, ScopePrefix).
validate_payload(ResourceServerId, DecodedToken) ->
ScopePrefix = rabbit_oauth2_config:get_scope_prefix(ResourceServerId),
validate_payload(ResourceServerId, DecodedToken, ScopePrefix).
validate_payload(#{?SCOPE_JWT_FIELD := Scope, ?AUD_JWT_FIELD := Aud} = DecodedToken, ResourceServerId, ScopePrefix) ->
validate_payload(ResourceServerId, #{?SCOPE_JWT_FIELD := Scope, ?AUD_JWT_FIELD := Aud} = DecodedToken, ScopePrefix) ->
case check_aud(Aud, ResourceServerId) of
ok -> {ok, DecodedToken#{?SCOPE_JWT_FIELD => filter_scopes(Scope, ScopePrefix)}};
{error, Err} -> {refused, {invalid_aud, Err}}
end;
validate_payload(#{?AUD_JWT_FIELD := Aud} = DecodedToken, ResourceServerId, _ScopePrefix) ->
validate_payload(ResourceServerId, #{?AUD_JWT_FIELD := Aud} = DecodedToken, _ScopePrefix) ->
case check_aud(Aud, ResourceServerId) of
ok -> {ok, DecodedToken};
{error, Err} -> {refused, {invalid_aud, Err}}
end;
validate_payload(#{?SCOPE_JWT_FIELD := Scope} = DecodedToken, _ResourceServerId, ScopePrefix) ->
case application:get_env(?APP, ?VERIFY_AUD, true) of
validate_payload(ResourceServerId, #{?SCOPE_JWT_FIELD := Scope} = DecodedToken, ScopePrefix) ->
case rabbit_oauth2_config:is_verify_aud(ResourceServerId) of
true -> {error, {badarg, {aud_field_is_missing}}};
false -> {ok, DecodedToken#{?SCOPE_JWT_FIELD => filter_scopes(Scope, ScopePrefix)}}
end.
@ -531,7 +508,7 @@ filter_scopes(Scopes, ScopePrefix) ->
check_aud(_, <<>>) -> ok;
check_aud(Aud, ResourceServerId) ->
case application:get_env(?APP, ?VERIFY_AUD, true) of
case rabbit_oauth2_config:is_verify_aud(ResourceServerId) of
true ->
case Aud of
List when is_list(List) ->
@ -627,8 +604,7 @@ token_from_context(AuthProps) ->
-spec username_from(list(), map()) -> binary() | undefined.
username_from(PreferredUsernameClaims, DecodedToken) ->
UsernameClaims = append_or_return_default(PreferredUsernameClaims, ?DEFAULT_PREFERRED_USERNAME_CLAIMS),
ResolvedUsernameClaims = lists:filtermap(fun(Claim) -> find_claim_in_token(Claim, DecodedToken) end, UsernameClaims),
ResolvedUsernameClaims = lists:filtermap(fun(Claim) -> find_claim_in_token(Claim, DecodedToken) end, PreferredUsernameClaims),
Username = case ResolvedUsernameClaims of
[ ] -> <<"unknown">>;
[ _One ] -> _One;
@ -638,13 +614,6 @@ username_from(PreferredUsernameClaims, DecodedToken) ->
[lists:flatten(io_lib:format("~p",[ResolvedUsernameClaims])), Username]),
Username.
append_or_return_default(ListOrBinary, Default) ->
case ListOrBinary of
VarList when is_list(VarList) -> VarList ++ Default;
VarBinary when is_binary(VarBinary) -> [VarBinary] ++ Default;
_ -> Default
end.
find_claim_in_token(Claim, Token) ->
case maps:get(Claim, Token, undefined) of
undefined -> false;

View File

@ -23,4 +23,3 @@ stop(_State) ->
init([]) ->
{ok, {{one_for_one,3,10},[]}}.

View File

@ -0,0 +1,311 @@
%% 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-2023 VMware, Inc. or its affiliates. All rights reserved.
%%
-module(rabbit_oauth2_config).
-include_lib("oauth2_client/include/oauth2_client.hrl").
-define(APP, rabbitmq_auth_backend_oauth2).
-define(DEFAULT_PREFERRED_USERNAME_CLAIMS, [<<"sub">>, <<"client_id">>]).
-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_jwks_url/1, get_default_resource_server_id/0,
get_oauth_provider_for_resource_server_id/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, has_additional_scopes_key/1, get_additional_scopes_key/1,
get_default_preferred_username_claims/0, get_preferred_username_claims/0, get_preferred_username_claims/1,
get_scope_prefix/0, get_scope_prefix/1,
get_resource_server_type/0, get_resource_server_type/1,
has_scope_aliases/1, get_scope_aliases/1
]).
-spec get_default_preferred_username_claims() -> list().
get_default_preferred_username_claims() ->
?DEFAULT_PREFERRED_USERNAME_CLAIMS.
-spec get_preferred_username_claims() -> list().
get_preferred_username_claims() ->
case application:get_env(?APP, preferred_username_claims) of
{ok, Value} -> append_or_return_default(Value, ?DEFAULT_PREFERRED_USERNAME_CLAIMS);
_ -> ?DEFAULT_PREFERRED_USERNAME_CLAIMS
end.
-spec get_preferred_username_claims(binary()) -> 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 ->
case proplists:get_value(preferred_username_claims, maps:get(ResourceServerId, application:get_env(?APP, resource_servers, #{}))) of
undefined -> get_preferred_username_claims();
Value -> append_or_return_default(Value, ?DEFAULT_PREFERRED_USERNAME_CLAIMS)
end.
-type key_type() :: json | pem | map.
-spec add_signing_key(binary(), {key_type(), binary()} ) -> {ok, map()} | {error, term()}.
add_signing_key(KeyId, Key) ->
LockId = lock(),
try do_add_signing_key(KeyId, Key) 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)
end.
do_add_signing_key(KeyId, Key) ->
do_replace_signing_keys(maps:put(KeyId, Key, get_signing_keys())).
do_add_signing_key(ResourceServerId, KeyId, Key) ->
do_replace_signing_keys(ResourceServerId, maps:put(KeyId, Key, get_signing_keys(ResourceServerId))).
replace_signing_keys(SigningKeys) ->
LockId = lock(),
try do_replace_signing_keys(SigningKeys) of
V -> V
after
unlock(LockId)
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) ->
KeyConfig = application:get_env(?APP, key_config, []),
KeyConfig1 = proplists:delete(signing_keys, KeyConfig),
KeyConfig2 = [{signing_keys, SigningKeys} | KeyConfig1],
application:set_env(?APP, key_config, KeyConfig2),
rabbit_log:debug("Replacing signing keys ~p", [ KeyConfig2]),
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(), #{}).
-spec get_signing_keys(binary()) -> map().
get_signing_keys(ResourceServerId) -> get_signing_keys(get_default_resource_server_id(), ResourceServerId).
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_oauth_provider_for_resource_server_id(binary(), list()) -> {ok, oauth_provider()} | {error, any()}.
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 ->
oauth2_client:get_oauth_provider(RequiredAttributeList);
get_oauth_provider_for_resource_server_id(TopResourceServerId, ResourceServerId, RequiredAttributeList) when ResourceServerId =/= TopResourceServerId ->
case proplists:get_value(oauth_provider_id, get_resource_server_props(ResourceServerId)) of
undefined -> rabbit_log:error("Missing oauth_provider_id attribute for ResourceServer ~p", [ResourceServerId]);
OauthProviderId -> oauth2_client:get_oauth_provider(OauthProviderId, RequiredAttributeList)
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()).
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_jwks_url(ResourceServerId) ->
rabbit_log:debug("get_jwks_url for resource-server_id ~p : ~p", [ResourceServerId,get_key_config(ResourceServerId)]),
proplists:get_value(jwks_url, get_key_config(ResourceServerId)).
append_or_return_default(ListOrBinary, Default) ->
case ListOrBinary of
VarList when is_list(VarList) -> VarList ++ Default;
VarBinary when is_binary(VarBinary) -> [VarBinary] ++ Default;
_ -> Default
end.
-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 };
{ok, ResourceServerId} -> ResourceServerId
end.
-spec get_allowed_resource_server_ids() -> list().
get_allowed_resource_server_ids() ->
ResourceServers = application:get_env(?APP, resource_servers, #{}),
rabbit_log:debug("ResourceServers: ~p", [ResourceServers]),
ResourceServerIds = maps:fold(fun(K, V, List) -> List ++ [proplists:get_value(id, V, K)] end, [], ResourceServers),
rabbit_log:debug("ResourceServersIds: ~p", [ResourceServerIds]),
ResourceServerIds ++ case get_default_resource_server_id() of
{error, _} -> [];
ResourceServerId -> [ ResourceServerId ]
end.
-spec find_audience_in_resource_server_ids(binary() | list()) -> {ok, binary()} | {error, term()}.
find_audience_in_resource_server_ids(Audience) when is_binary(Audience) ->
find_audience_in_resource_server_ids(binary:split(Audience, <<" ">>, [global, trim_all]));
find_audience_in_resource_server_ids(AudList) when is_list(AudList) ->
AllowedAudList = get_allowed_resource_server_ids(),
%rabbit_log:debug("find_audience_in_resource_server_ids AudList:~p, AllowedAudList:~p",[AudList,AllowedAudList]),
case intersection(AudList, AllowedAudList) of
[One] -> {ok, One};
[_One|_Tail] -> {error, only_one_resource_server_as_audience_found_many};
[] -> {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).
is_verify_aud(TopResourceServerId, ResourceServerId) when ResourceServerId =:= TopResourceServerId -> is_verify_aud();
is_verify_aud(TopResourceServerId, ResourceServerId) when ResourceServerId =/= TopResourceServerId ->
proplists:get_value(verify_aud, maps:get(ResourceServerId, application:get_env(?APP, resource_servers, #{}), []), is_verify_aud()).
-spec has_additional_scopes_key(binary()) -> boolean().
has_additional_scopes_key(ResourceServerId) -> has_additional_scopes_key(get_default_resource_server_id(), ResourceServerId).
has_additional_scopes_key(TopResourceServerId, ResourceServerId) when ResourceServerId =:= TopResourceServerId ->
case application:get_env(?APP, extra_scopes_source, undefined) of
undefined -> false;
<<>> -> false;
_ -> true
end;
has_additional_scopes_key(TopResourceServerId, ResourceServerId) when ResourceServerId =/= TopResourceServerId ->
case proplists:get_value(extra_scopes_source, maps:get(ResourceServerId, application:get_env(?APP, resource_servers, #{}), [])) of
undefined -> has_additional_scopes_key(TopResourceServerId);
<<>> -> has_additional_scopes_key(TopResourceServerId);
_ -> true
end.
-spec get_additional_scopes_key() -> binary() | undefined.
get_additional_scopes_key() -> application:get_env(?APP, extra_scopes_source, undefined).
-spec get_additional_scopes_key(binary()) -> binary() | undefined .
get_additional_scopes_key(ResourceServerId) -> get_additional_scopes_key(get_default_resource_server_id(), ResourceServerId).
get_additional_scopes_key(TopResourceServerId, ResourceServerId) when ResourceServerId =:= TopResourceServerId -> get_additional_scopes_key();
get_additional_scopes_key(TopResourceServerId, ResourceServerId) when ResourceServerId =/= TopResourceServerId ->
proplists:get_value(extra_scopes_source, maps:get(ResourceServerId, application:get_env(?APP, resource_servers, #{}), []),
get_additional_scopes_key()).
-spec get_scope_prefix() -> binary().
get_scope_prefix() ->
DefaultScopePrefix = erlang:iolist_to_binary([get_default_resource_server_id(), <<".">>]),
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).
get_scope_prefix(TopResourceServerId, ResourceServerId) when ResourceServerId =:= TopResourceServerId -> get_scope_prefix();
get_scope_prefix(TopResourceServerId, ResourceServerId) when ResourceServerId =/= TopResourceServerId ->
case proplists:get_value(scope_prefix, maps:get(ResourceServerId, application:get_env(?APP, resource_servers, #{}), [])) of
undefined -> case application:get_env(?APP, scope_prefix) of
undefined -> <<ResourceServerId/binary, ".">>;
{ok, Prefix} -> Prefix
end;
Prefix -> Prefix
end.
-spec get_resource_server_type() -> binary().
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).
get_resource_server_type(TopResourceServerId, ResourceServerId) when ResourceServerId =:= TopResourceServerId -> get_resource_server_type();
get_resource_server_type(TopResourceServerId, ResourceServerId) when ResourceServerId =/= TopResourceServerId ->
proplists:get_value(resource_server_type, maps:get(ResourceServerId, application:get_env(?APP, resource_servers, #{}), []),
get_resource_server_type()).
-spec has_scope_aliases(binary()) -> boolean().
has_scope_aliases(ResourceServerId) -> has_scope_aliases(get_default_resource_server_id(), ResourceServerId).
has_scope_aliases(TopResourceServerId, ResourceServerId) when ResourceServerId =:= TopResourceServerId ->
case application:get_env(?APP, scope_aliases) of
undefined -> false;
_ -> true
end;
has_scope_aliases(TopResourceServerId, ResourceServerId) when ResourceServerId =/= TopResourceServerId ->
ResourceServerProps = maps:get(ResourceServerId, application:get_env(?APP, resource_servers, #{}),[]),
case proplists:is_defined(scope_aliases, ResourceServerProps) of
true -> true;
false -> has_scope_aliases(TopResourceServerId)
end.
-spec get_scope_aliases(binary()) -> map().
get_scope_aliases(ResourceServerId) -> get_scope_aliases(get_default_resource_server_id(), ResourceServerId).
get_scope_aliases(TopResourceServerId, ResourceServerId) when ResourceServerId =:= TopResourceServerId ->
application:get_env(?APP, scope_aliases, #{});
get_scope_aliases(TopResourceServerId, ResourceServerId) when ResourceServerId =/= TopResourceServerId ->
ResourceServerProps = maps:get(ResourceServerId, application:get_env(?APP, resource_servers, #{}),[]),
proplists:get_value(scope_aliases, ResourceServerProps, get_scope_aliases(TopResourceServerId)).
intersection(List1, List2) ->
[I || I <- List1, lists:member(I, List2)].
lock() ->
Nodes = rabbit_nodes:list_running(),
Retries = rabbit_nodes:lock_retries(),
LockId = case global:set_lock({oauth2_config_lock, rabbitmq_auth_backend_oauth2}, Nodes, Retries) of
true -> rabbitmq_auth_backend_oauth2;
false -> undefined
end,
LockId.
unlock(LockId) ->
Nodes = rabbit_nodes:list_running(),
case LockId of
undefined -> ok;
Value ->
global:del_lock({oauth2_config_lock, Value}, Nodes)
end,
ok.

View File

@ -1,32 +1,31 @@
-module(uaa_jwks).
-export([get/1, ssl_options/0]).
-export([get/2, ssl_options/1]).
-spec get(string() | binary()) -> {ok, term()} | {error, term()}.
get(JwksUrl) ->
httpc:request(get, {JwksUrl, []}, [{ssl, ssl_options()}, {timeout, 60000}], []).
-spec get(string() | binary(), term()) -> {ok, term()} | {error, term()}.
get(JwksUrl, KeyConfig) ->
httpc:request(get, {JwksUrl, []}, [{ssl, ssl_options(KeyConfig)}, {timeout, 60000}], []).
-spec ssl_options() -> list().
ssl_options() ->
UaaEnv = application:get_env(rabbitmq_auth_backend_oauth2, key_config, []),
PeerVerification = proplists:get_value(peer_verification, UaaEnv, verify_none),
Depth = proplists:get_value(depth, UaaEnv, 10),
FailIfNoPeerCert = proplists:get_value(fail_if_no_peer_cert, UaaEnv, false),
CrlCheck = proplists:get_value(crl_check, UaaEnv, false),
-spec ssl_options(term()) -> list().
ssl_options(KeyConfig) ->
PeerVerification = proplists:get_value(peer_verification, KeyConfig, verify_none),
Depth = proplists:get_value(depth, KeyConfig, 10),
FailIfNoPeerCert = proplists:get_value(fail_if_no_peer_cert, KeyConfig, false),
CrlCheck = proplists:get_value(crl_check, KeyConfig, false),
SslOpts0 = [{verify, PeerVerification},
{depth, Depth},
{fail_if_no_peer_cert, FailIfNoPeerCert},
{crl_check, CrlCheck},
{crl_cache, {ssl_crl_cache, {internal, [{http, 10000}]}}} | cacertfile(UaaEnv)],
{crl_cache, {ssl_crl_cache, {internal, [{http, 10000}]}}} | cacertfile(KeyConfig)],
case proplists:get_value(hostname_verification, UaaEnv, none) of
case proplists:get_value(hostname_verification, KeyConfig, none) of
wildcard ->
[{customize_hostname_check, [{match_fun, public_key:pkix_verify_hostname_match_fun(https)}]} | SslOpts0];
none ->
SslOpts0
end.
cacertfile(UaaEnv) ->
case proplists:get_value(cacertfile, UaaEnv) of
cacertfile(KeyConfig) ->
case proplists:get_value(cacertfile, KeyConfig) of
undefined -> [];
CaCertFile -> [{cacertfile, CaCertFile}]
end.

View File

@ -7,104 +7,99 @@
-module(uaa_jwt).
-export([add_signing_key/3,
remove_signing_key/1,
decode_and_verify/1,
get_jwk/1,
verify_signing_key/2,
signing_keys/0]).
get_jwk/2,
verify_signing_key/2]).
-export([client_id/1, sub/1, client_id/2, sub/2]).
-include_lib("jose/include/jose_jwk.hrl").
-include_lib("oauth2_client/include/oauth2_client.hrl").
-define(APP, rabbitmq_auth_backend_oauth2).
-type key_type() :: json | pem | map.
-spec add_signing_key(binary(), key_type(), binary() | map()) -> {ok, map()} | {error, term()}.
add_signing_key(KeyId, Type, Value) ->
case verify_signing_key(Type, Value) of
ok ->
SigningKeys0 = signing_keys(),
SigningKeys1 = maps:put(KeyId, {Type, Value}, SigningKeys0),
ok = update_uaa_jwt_signing_keys(SigningKeys1),
{ok, SigningKeys1};
{ok, rabbit_oauth2_config:add_signing_key(KeyId, {Type, Value})};
{error, _} = Err ->
Err
end.
remove_signing_key(KeyId) ->
UaaEnv = application:get_env(?APP, key_config, []),
Keys0 = proplists:get_value(signing_keys, UaaEnv),
Keys1 = maps:remove(KeyId, Keys0),
update_uaa_jwt_signing_keys(UaaEnv, Keys1).
-spec update_uaa_jwt_signing_keys(map()) -> ok.
update_uaa_jwt_signing_keys(SigningKeys) ->
UaaEnv0 = application:get_env(?APP, key_config, []),
update_uaa_jwt_signing_keys(UaaEnv0, SigningKeys).
-spec update_uaa_jwt_signing_keys([term()], map()) -> ok.
update_uaa_jwt_signing_keys(UaaEnv0, SigningKeys) ->
UaaEnv1 = proplists:delete(signing_keys, UaaEnv0),
UaaEnv2 = [{signing_keys, SigningKeys} | UaaEnv1],
application:set_env(?APP, key_config, UaaEnv2).
-spec update_jwks_signing_keys() -> ok | {error, term()}.
update_jwks_signing_keys() ->
UaaEnv = application:get_env(?APP, key_config, []),
case proplists:get_value(jwks_url, UaaEnv) of
undefined ->
{error, no_jwks_url};
JwksUrl ->
rabbit_log:debug("Retrieving signing keys from ~ts", [JwksUrl]),
case uaa_jwks:get(JwksUrl) of
-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 JWKS URL for resource-server-id ~p ", [ResourceServerId]),
Error;
{ok, #oauth_provider{jwks_uri = JwksUrl, ssl_options = SslOptions}} ->
rabbit_log:debug("Downloading keys from ~p (ssl_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)),
update_uaa_jwt_signing_keys(UaaEnv, Keys);
rabbit_log:debug("Downloaded keys ~p", [Keys]),
case rabbit_oauth2_config:replace_signing_keys(ResourceServerId, Keys) of
{error, _} = Err -> Err;
_ -> ok
end;
{error, _} = Err ->
rabbit_log:error("Error Downloadings keys ~p", [Err]),
Err
end
end.
-spec decode_and_verify(binary()) -> {boolean(), map()} | {error, term()}.
-spec decode_and_verify(binary()) -> {boolean(), binary(), map()} | {error, term()}.
decode_and_verify(Token) ->
case uaa_jwt_jwt:get_key_id(Token) of
case uaa_jwt_jwt:resolve_resource_server_id(Token) of
{error, _} = Err ->
Err;
ResourceServerId ->
rabbit_log:debug("Resolved resource_server_id : ~p", [ResourceServerId]),
case uaa_jwt_jwt:get_key_id(ResourceServerId, Token) of
{ok, KeyId} ->
case get_jwk(KeyId) of
rabbit_log:debug("Resolved signing_key_id : ~p", [KeyId]),
case get_jwk(KeyId, ResourceServerId) of
{ok, JWK} ->
uaa_jwt_jwt:decode_and_verify(JWK, Token);
case uaa_jwt_jwt:decode_and_verify(ResourceServerId, JWK, Token) of
{true, Payload} -> {true, ResourceServerId, Payload};
Other -> Other
end;
{error, _} = Err ->
Err
end;
{error, _} = Err ->
Err
end
end.
-spec get_jwk(binary()) -> {ok, map()} | {error, term()}.
get_jwk(KeyId) ->
get_jwk(KeyId, true).
-spec get_jwk(binary(), binary()) -> {ok, map()} | {error, term()}.
get_jwk(KeyId, ResourceServerId) ->
get_jwk(KeyId, ResourceServerId, true).
get_jwk(KeyId, AllowUpdateJwks) ->
Keys = signing_keys(),
case maps:get(KeyId, Keys, undefined) of
get_jwk(KeyId, ResourceServerId, AllowUpdateJwks) ->
case rabbit_oauth2_config:get_signing_key(KeyId, ResourceServerId) of
undefined ->
if
AllowUpdateJwks ->
case update_jwks_signing_keys() of
rabbit_log:debug("Signing key ~p not found. Downloading it .. ", [KeyId]),
case update_jwks_signing_keys(ResourceServerId) of
ok ->
get_jwk(KeyId, false);
get_jwk(KeyId, ResourceServerId, false);
{error, no_jwks_url} ->
{error, key_not_found};
{error, _} = Err ->
Err
end;
true ->
rabbit_log:debug("Signing key ~p not found. Download not allowed ", [KeyId]),
{error, key_not_found}
end;
{Type, Value} ->
rabbit_log:debug("Signing key found : ~p, ~p ", [Type, Value]),
case Type of
json -> uaa_jwt_jwk:make_jwk(Value);
pem -> uaa_jwt_jwk:from_pem(Value);
@ -131,9 +126,6 @@ verify_signing_key(Type, Value) ->
Err -> Err
end.
signing_keys() ->
UaaEnv = application:get_env(?APP, key_config, []),
proplists:get_value(signing_keys, UaaEnv, #{}).
-spec client_id(map()) -> binary() | undefined.
client_id(DecodedToken) ->

View File

@ -6,7 +6,7 @@
%%
-module(uaa_jwt_jwt).
-export([decode/1, decode_and_verify/2, get_key_id/1]).
-export([decode/1, decode_and_verify/3, get_key_id/2, get_aud/1, resolve_resource_server_id/1]).
-include_lib("jose/include/jose_jwt.hrl").
-include_lib("jose/include/jose_jws.hrl").
@ -19,10 +19,10 @@ decode(Token) ->
{error, {invalid_token, Type, Err, Stacktrace}}
end.
decode_and_verify(Jwk, Token) ->
UaaEnv = application:get_env(rabbitmq_auth_backend_oauth2, key_config, []),
decode_and_verify(ResourceServerId, Jwk, Token) ->
KeyConfig = rabbit_oauth2_config:get_key_config(ResourceServerId),
Verify =
case proplists:get_value(algorithms, UaaEnv) of
case proplists:get_value(algorithms, KeyConfig) of
undefined ->
jose_jwt:verify(Jwk, Token);
Algs ->
@ -33,20 +33,51 @@ decode_and_verify(Jwk, Token) ->
{false, #jose_jwt{fields = Fields}, _} -> {false, Fields}
end.
get_key_id(Token) ->
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) ->
try
case jose_jwt:peek_protected(Token) of
#jose_jws{fields = #{<<"kid">> := Kid}} -> {ok, Kid};
#jose_jws{} -> get_default_key()
#jose_jws{} -> get_default_key(ResourceServerId)
end
catch Type:Err:Stacktrace ->
{error, {invalid_token, Type, Err, Stacktrace}}
end.
get_aud(Token) ->
try
case jose_jwt:peek_payload(Token) of
#jose_jwt{fields = #{<<"aud">> := Aud}} -> {ok, Aud};
#jose_jwt{} -> undefined
end
catch Type:Err:Stacktrace ->
{error, {invalid_token, Type, Err, Stacktrace}}
end.
get_default_key() ->
UaaEnv = application:get_env(rabbitmq_auth_backend_oauth2, key_config, []),
case proplists:get_value(default_key, UaaEnv, undefined) of
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.

View File

@ -0,0 +1,74 @@
%% 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-2023 VMware, Inc. or its affiliates. All rights reserved.
%%
-module(add_signing_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.AddSigningKeyCommand').
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}).

View File

@ -72,4 +72,3 @@ validate_pem_file_key(Config) ->
CertsDir = ?config(rmq_certsdir, Config),
Keyfile = filename:join([CertsDir, <<"client">>, <<"key.pem">>]),
ok = ?COMMAND:validate([<<"foo">>], #{pem_file => Keyfile}).

View File

@ -12,6 +12,7 @@
auth_oauth2.signing_keys.id1 = test/config_schema_SUITE_data/certs/key.pem
auth_oauth2.signing_keys.id2 = test/config_schema_SUITE_data/certs/cert.pem
auth_oauth2.jwks_url = https://my-jwt-issuer/jwks.json
auth_oauth2.issuer = https://my-jwt-issuer
auth_oauth2.https.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem
auth_oauth2.https.peer_verification = verify_none
auth_oauth2.https.depth = 5
@ -28,6 +29,7 @@
{extra_scopes_source, <<"my_custom_scope_key">>},
{preferred_username_claims, [<<"user_name">>, <<"username">>, <<"email">>]},
{verify_aud, true},
{issuer, "https://my-jwt-issuer"},
{key_config, [
{default_key, <<"id1">>},
{signing_keys,
@ -48,5 +50,113 @@
}
]}
],[]
},
{oauth2_pem_config3,
"auth_oauth2.resource_server_id = new_resource_server_id
auth_oauth2.scope_prefix = new_resource_server_id.
auth_oauth2.resource_server_type = new_resource_server_type
auth_oauth2.additional_scopes_key = my_custom_scope_key
auth_oauth2.preferred_username_claims.1 = user_name
auth_oauth2.preferred_username_claims.2 = username
auth_oauth2.preferred_username_claims.3 = email
auth_oauth2.verify_aud = true
auth_oauth2.default_key = id1
auth_oauth2.signing_keys.id1 = test/config_schema_SUITE_data/certs/key.pem
auth_oauth2.signing_keys.id2 = test/config_schema_SUITE_data/certs/cert.pem
auth_oauth2.jwks_url = https://my-jwt-issuer/jwks.json
auth_oauth2.https.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem
auth_oauth2.https.peer_verification = verify_none
auth_oauth2.https.depth = 5
auth_oauth2.https.fail_if_no_peer_cert = false
auth_oauth2.https.hostname_verification = wildcard
auth_oauth2.https.crl_check = true
auth_oauth2.algorithms.1 = HS256
auth_oauth2.algorithms.2 = RS256
auth_oauth2.resource_servers.1.id = rabbitmq-operations
auth_oauth2.resource_servers.1.scope_prefix = api://
auth_oauth2.resource_servers.customers.id = rabbitmq-customers
auth_oauth2.resource_servers.customers.additional_scopes_key = roles",
[
{rabbitmq_auth_backend_oauth2, [
{resource_server_id,<<"new_resource_server_id">>},
{scope_prefix,<<"new_resource_server_id.">>},
{resource_server_type,<<"new_resource_server_type">>},
{extra_scopes_source, <<"my_custom_scope_key">>},
{preferred_username_claims, [<<"user_name">>, <<"username">>, <<"email">>]},
{verify_aud, true},
{resource_servers,
#{
<<"rabbitmq-operations">> => [
{id, <<"rabbitmq-operations">>},
{scope_prefix, <<"api://">>}
],
<<"rabbitmq-customers">> => [
{id, <<"rabbitmq-customers">>},
{additional_scopes_key, <<"roles">>}
]
}
},
{key_config, [
{default_key, <<"id1">>},
{signing_keys,
#{
<<"id1">> => {pem, <<"I'm not a certificate">>},
<<"id2">> => {pem, <<"I'm not a certificate">>}
}
},
{jwks_url, "https://my-jwt-issuer/jwks.json"},
{cacertfile, "test/config_schema_SUITE_data/certs/cacert.pem"},
{peer_verification, verify_none},
{depth, 5},
{fail_if_no_peer_cert, false},
{hostname_verification, wildcard},
{crl_check, true},
{algorithms, [<<"HS256">>, <<"RS256">>]}
]
}
]}
],[]
},
{oauth2_pem_config4,
"auth_oauth2.resource_server_id = new_resource_server_id
auth_oauth2.scope_prefix = new_resource_server_id.
auth_oauth2.resource_server_type = new_resource_server_type
auth_oauth2.additional_scopes_key = my_custom_scope_key
auth_oauth2.preferred_username_claims.1 = user_name
auth_oauth2.preferred_username_claims.2 = username
auth_oauth2.preferred_username_claims.3 = email
auth_oauth2.verify_aud = true
auth_oauth2.oauth_providers.uaa.issuer = https://uaa
auth_oauth2.oauth_providers.keycloak.token_endpoint = https://keycloak/token
auth_oauth2.oauth_providers.keycloak.jwks_uri = https://keycloak/keys
auth_oauth2.oauth_providers.keycloak.https.cacertfile = /mnt/certs/ca_certificate.pem
auth_oauth2.oauth_providers.keycloak.https.verify = verify_none",
[
{rabbitmq_auth_backend_oauth2, [
{resource_server_id,<<"new_resource_server_id">>},
{scope_prefix,<<"new_resource_server_id.">>},
{resource_server_type,<<"new_resource_server_type">>},
{extra_scopes_source, <<"my_custom_scope_key">>},
{preferred_username_claims, [<<"user_name">>, <<"username">>, <<"email">>]},
{verify_aud, true},
{oauth_providers,
#{
<<"uaa">> => [
{issuer, <<"https://uaa">>}
],
<<"keycloak">> => [
{https, [
{verify, verify_none},
{cacertfile, "/mnt/certs/ca_certificate.pem"}
]},
{token_endpoint, <<"https://keycloak/token">>},
{jwks_uri, <<"https://keycloak/keys">>}
]
}
}
]}
],[]
}
].

View File

@ -22,8 +22,8 @@ all() ->
[
{group, happy_path},
{group, unhappy_path},
{group, unvalidated_jwks_server},
{group, no_peer_verification}
{group, no_peer_verification},
{group, multi_resource}
].
groups() ->
@ -48,8 +48,14 @@ groups() ->
test_failed_token_refresh_case1,
test_failed_token_refresh_case2
]},
{unvalidated_jwks_server, [], [test_failed_connection_with_unvalidated_jwks_server]},
{no_peer_verification, [], [{group, happy_path}, {group, unhappy_path}]}
{no_peer_verification, [], [
{group, happy_path},
{group, unhappy_path}
]},
{multi_resource, [], [
test_m_successful_connection,
test_m_failed_connection_due_to_missing_key
]}
].
%%
@ -82,8 +88,37 @@ init_per_group(no_peer_verification, Config) ->
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">>}
],
<<"rabbitmq2">> => [
{id, <<"rabbitmq2">>},
{oauth_provider_id, <<"two">>}
]
},
OAuthProviders =
#{
<<"one">> => [
{issuer, strict_jwks_url(Config, "/")},
{jwks_uri, strict_jwks_url(Config, "/jwks1")}
],
<<"two">> => [
{issuer, strict_jwks_url(Config, "/")},
{jwks_uri, strict_jwks_url(Config, "/jwks2")}
]
},
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]),
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]),
Config.
end_per_group(no_peer_verification, Config) ->
@ -138,12 +173,6 @@ init_per_testcase(Testcase, Config) when Testcase =:= test_failed_connection_wit
rabbit_ct_helpers:testcase_started(Config, Testcase),
Config;
init_per_testcase(Testcase, Config) when Testcase =:= test_failed_connection_with_unvalidated_jwks_server ->
KeyConfig = rabbit_ct_helpers:set_config(?config(key_config, Config), {jwks_url, ?config(non_strict_jwks_url, Config)}),
ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, [rabbitmq_auth_backend_oauth2, key_config, KeyConfig]),
rabbit_ct_helpers:testcase_started(Config, Testcase),
Config;
init_per_testcase(Testcase, Config) ->
rabbit_ct_helpers:testcase_started(Config, Testcase),
Config.
@ -158,14 +187,13 @@ end_per_testcase(Testcase, Config) when Testcase =:= test_successful_connection_
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]),
ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, unset_env,
[rabbitmq_auth_backend_oauth2, extra_scopes_source]),
rabbit_ct_helpers:testcase_started(Config, Testcase),
Config;
end_per_testcase(Testcase, Config) when Testcase =:= test_successful_connection_with_algorithm_restriction orelse
Testcase =:= test_failed_connection_with_algorithm_restriction orelse
Testcase =:= test_failed_connection_with_unvalidated_jwks_server ->
Testcase =:= test_failed_connection_with_algorithm_restriction ->
rabbit_ct_broker_helpers:delete_vhost(Config, <<"vhost1">>),
ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, [rabbitmq_auth_backend_oauth2, key_config, ?config(key_config, Config)]),
rabbit_ct_helpers:testcase_finished(Config, Testcase),
@ -183,32 +211,52 @@ preconfigure_node(Config) ->
[rabbitmq_auth_backend_oauth2, resource_server_id, ?RESOURCE_SERVER_ID]),
Config.
start_jwks_server(Config) ->
start_jwks_server(Config0) ->
Jwk = ?UTIL_MOD:fixture_jwk(),
Jwk1 = ?UTIL_MOD:fixture_jwk(<<"token-key-1">>),
Jwk2 = ?UTIL_MOD:fixture_jwk(<<"token-key-2">>),
Jwk3 = ?UTIL_MOD:fixture_jwk(<<"token-key-3">>),
%% Assume we don't have more than 100 ports allocated for tests
PortBase = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_ports_base),
PortBase = rabbit_ct_broker_helpers:get_node_config(Config0, 0, tcp_ports_base),
JwksServerPort = PortBase + 100,
Config = rabbit_ct_helpers:set_config(Config0, [{jwksServerPort, JwksServerPort}]),
%% Both URLs direct to the same JWKS server
%% The NonStrictJwksUrl identity cannot be validated while StrictJwksUrl identity can be validated
NonStrictJwksUrl = "https://127.0.0.1:" ++ integer_to_list(JwksServerPort) ++ "/jwks",
StrictJwksUrl = "https://localhost:" ++ integer_to_list(JwksServerPort) ++ "/jwks",
NonStrictJwksUrl = non_strict_jwks_url(Config),
StrictJwksUrl = strict_jwks_url(Config),
ok = application:set_env(jwks_http, keys, [Jwk]),
{ok, _} = application:ensure_all_started(ssl),
{ok, _} = application:ensure_all_started(cowboy),
CertsDir = ?config(rmq_certsdir, Config),
ok = jwks_http_app:start(JwksServerPort, CertsDir),
ok = jwks_http_app:start(JwksServerPort, CertsDir,
[ {"/jwks", [Jwk]},
{"/jwks1", [Jwk1, Jwk3]},
{"/jwks2", [Jwk2]}
]),
KeyConfig = [{jwks_url, StrictJwksUrl},
{peer_verification, verify_peer},
{cacertfile, filename:join([CertsDir, "testca", "cacert.pem"])}],
ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env,
[rabbitmq_auth_backend_oauth2, key_config, KeyConfig]),
rabbit_ct_helpers:set_config(Config,
[{non_strict_jwks_url, NonStrictJwksUrl},
[
{non_strict_jwks_url, NonStrictJwksUrl},
{strict_jwks_url, StrictJwksUrl},
{key_config, KeyConfig},
{fixture_jwk, Jwk}]).
{fixture_jwk, Jwk},
{fixture_jwks_1, [Jwk1, Jwk3]},
{fixture_jwks_2, [Jwk2]}
]).
strict_jwks_url(Config) ->
strict_jwks_url(Config, "/jwks").
strict_jwks_url(Config, Path) ->
"https://localhost:" ++ integer_to_list(?config(jwksServerPort, Config)) ++ Path.
non_strict_jwks_url(Config) ->
non_strict_jwks_url(Config, "/jwks").
non_strict_jwks_url(Config, Path) ->
"https://127.0.0.1:" ++ integer_to_list(?config(jwksServerPort, Config)) ++ Path.
stop_jwks_server(Config) ->
ok = jwks_http_app:stop(),
@ -225,6 +273,9 @@ generate_valid_token(Config, Scopes, Audience) ->
undefined -> ?UTIL_MOD:fixture_jwk();
Value -> Value
end,
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))
@ -264,18 +315,59 @@ 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),
verify_queue_declare_with_token(Config, Token).
verify_queue_declare_with_token(Config, Token) ->
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_m_successful_connection(Config) ->
{_Alg, Token1} = generate_valid_token(
Config,
lists:nth(1, ?config(fixture_jwks_1, Config)),
<<"rabbitmq1.configure:*/* rabbitmq1.write:*/* rabbitmq1.read:*/*">>,
[<<"rabbitmq1">>]
),
verify_queue_declare_with_token(Config, Token1),
{_Alg2, Token2} = generate_valid_token(
Config,
lists:nth(2, ?config(fixture_jwks_1, 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)),
<<"rabbitmq2.configure:*/* rabbitmq2.write:*/* rabbitmq2.read:*/*">>,
[<<"rabbitmq2">>]
),
verify_queue_declare_with_token(Config, Token3).
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
<<"rabbitmq1.configure:*/* rabbitmq1.write:*/* rabbitmq1.read:*/*">>,
[<<"rabbitmq1">>]
),
?assertMatch({error, {auth_failure, _}},
open_unmanaged_connection(Config, 0, <<"username">>, Token)).
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/*">>,
@ -290,7 +382,7 @@ 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">>
[<<"hare">>, <<"rabbitmq">>]
),
Conn = open_unmanaged_connection(Config, 0, <<"username">>, Token),
{ok, Ch} = amqp_connection:open_channel(Conn),
@ -323,7 +415,7 @@ test_successful_connection_with_complex_claim_as_a_list(Config) ->
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:*/*">>}
#{<<"additional_rabbitmq_scopes">> => <<"rabbitmq.configure:*/* rabbitmq.read:*/* rabbitmq.write:*/*">>}
),
Conn = open_unmanaged_connection(Config, 0, <<"username">>, Token),
{ok, Ch} = amqp_connection:open_channel(Conn),
@ -459,8 +551,3 @@ test_failed_connection_with_algorithm_restriction(Config) ->
{_Algo, Token} = rabbit_ct_helpers:get_config(Config, fixture_jwt),
?assertMatch({error, {auth_failure, _}},
open_unmanaged_connection(Config, 0, <<"username">>, Token)).
test_failed_connection_with_unvalidated_jwks_server(Config) ->
{_Algo, Token} = rabbit_ct_helpers:get_config(Config, fixture_jwt),
?assertMatch({error, {auth_failure, _}},
open_unmanaged_connection(Config, 0, <<"username">>, Token)).

View File

@ -1,16 +1,11 @@
-module(jwks_http_app).
-export([start/2, stop/0]).
-export([start/3, stop/0]).
start(Port, CertsDir) ->
Dispatch =
cowboy_router:compile(
[
{'_', [
{"/jwks", jwks_http_handler, []}
]}
]
),
start(Port, CertsDir, Mounts) ->
Endpoints = [ {Mount, jwks_http_handler, [{keys, Keys}]} || {Mount,Keys} <- Mounts ] ++
[{"/.well-known/openid-configuration", openid_http_handler, []}],
Dispatch = cowboy_router:compile([{'_', Endpoints}]),
{ok, _} = cowboy:start_tls(jwks_http_listener,
[{port, Port},
{certfile, filename:join([CertsDir, "server", "cert.pem"])},

View File

@ -4,7 +4,7 @@
-export([init/2, terminate/3]).
init(Req, State) ->
{ok, Keys} = application:get_env(jwks_http, keys),
Keys = proplists:get_value(keys, State, []),
Body = rabbit_json:encode(#{keys => Keys}),
Headers = #{<<"content-type">> => <<"application/json">>},
Req2 = cowboy_req:reply(200, Headers, Body, Req),

View File

@ -0,0 +1,14 @@
-module(openid_http_handler).
-behavior(cowboy_handler).
-export([init/2, terminate/3]).
init(Req, State) ->
OpenIdConfig = application:get_env(jwks_http, openid_config, #{}),
Body = rabbit_json:encode(OpenIdConfig),
Headers = #{<<"content-type">> => <<"application/json">>},
Req2 = cowboy_req:reply(200, Headers, Body, Req),
{ok, Req2, State}.
terminate(_Reason, _Req, _State) ->
ok.

View File

@ -40,12 +40,15 @@ sign_token(Token, Jwk, Jws) ->
jose_jws:compact(Signed).
fixture_jwk() ->
fixture_jwk(<<"token-key">>).
fixture_jwk(TokenKey) ->
#{<<"alg">> => <<"HS256">>,
<<"k">> => <<"dG9rZW5rZXk">>,
<<"kid">> => <<"token-key">>,
<<"kid">> => TokenKey,
<<"kty">> => <<"oct">>,
<<"use">> => <<"sig">>,
<<"value">> => <<"tokenkey">>}.
<<"value">> => TokenKey}.
full_permission_scopes() ->
[<<"rabbitmq.configure:*/*">>,
@ -78,7 +81,6 @@ fixture_token_with_scopes(Scopes) ->
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">>],
@ -87,7 +89,6 @@ token_with_scopes_and_expiration(Scopes, Expiration) ->
token_without_scopes() ->
%% expiration is a timestamp with precision in seconds
#{
<<"kid">> => <<"token-key">>,
<<"iss">> => <<"unit_test">>,
<<"foo">> => <<"bar">>,
<<"aud">> => [<<"rabbitmq">>]
@ -115,14 +116,12 @@ fixture_token_with_full_permissions() ->
plain_token_without_scopes_and_aud() ->
%% expiration is a timestamp with precision in seconds
#{<<"exp">> => default_expiration_moment(),
<<"kid">> => <<"token-key">>,
<<"iss">> => <<"unit_test">>,
<<"foo">> => <<"bar">>}.
token_with_scope_alias_in_scope_field(Value) ->
%% expiration is a timestamp with precision in seconds
#{<<"exp">> => default_expiration_moment(),
<<"kid">> => <<"token-key">>,
<<"iss">> => <<"unit_test">>,
<<"foo">> => <<"bar">>,
<<"aud">> => [<<"rabbitmq">>],
@ -131,7 +130,6 @@ token_with_scope_alias_in_scope_field(Value) ->
token_with_scope_alias_in_claim_field(Claims, Scopes) ->
%% expiration is a timestamp with precision in seconds
#{<<"exp">> => default_expiration_moment(),
<<"kid">> => <<"token-key">>,
<<"iss">> => <<"unit_test">>,
<<"foo">> => <<"bar">>,
<<"aud">> => [<<"rabbitmq">>],

View File

@ -0,0 +1,397 @@
%% 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-2023 VMware, Inc. or its affiliates. All rights reserved.
%%
-module(rabbit_oauth2_config_SUITE).
-compile(export_all).
-include_lib("eunit/include/eunit.hrl").
-define(RABBITMQ,<<"rabbitmq">>).
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_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_jwks_url_for_resource_server_id
]
},
{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,
get_jwks_url_for_resource_servers_id,
index_resource_servers_by_id_else_by_key
]
},
{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, [], [
resolve_settings_via_inheritance,
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
]
}
].
init_per_suite(Config) ->
rabbit_ct_helpers:log_environment(),
rabbit_ct_helpers:run_setup_steps(Config).
end_per_suite(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());
init_per_group(with_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">> }]),
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,
#{<<"rabbitmq1">> => [ { key_config, [
{jwks_url,<<"https://oauth-for-rabbitmq1">> }
]
}
],
<<"rabbitmq2">> => [ { key_config, [
{jwks_url,<<"https://oauth-for-rabbitmq2">> }
]
}
]
}),
Config;
init_per_group(with_resource_servers, Config) ->
application:set_env(rabbitmq_auth_backend_oauth2, resource_servers,
#{<<"rabbitmq1">> => [ { key_config, [
{jwks_url,<<"https://oauth-for-rabbitmq1">> }
]
}
],
<<"rabbitmq2">> => [ { 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,
#{<<"rabbitmq1">> => [ { 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, #{} }
],
<<"rabbitmq2">> => [ {id, <<"rabbitmq-2">> } ]
}
),
Config;
init_per_group(_any, Config) ->
Config.
end_per_group(with_rabbitmq_node, Config) ->
rabbit_ct_helpers:run_steps(Config, rabbit_ct_broker_helpers:teardown_steps());
end_per_group(with_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;
end_per_group(with_resource_servers, Config) ->
application:unset_env(rabbitmq_auth_backend_oauth2, resource_servers),
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, key_config),
application:unset_env(rabbitmq_auth_backend_oauth2, resource_servers),
Config;
end_per_group(_any, Config) ->
Config.
init_per_testcase(get_preferred_username_claims, 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;
init_per_testcase(is_verify_aud_when_is_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;
init_per_testcase(get_resource_server_type_when_not_defined, 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;
init_per_testcase(_TestCase, Config) ->
Config.
end_per_testcase(get_preferred_username_claims, Config) ->
application:unset_env(rabbitmq_auth_backend_oauth2, preferred_username_claims),
Config;
end_per_testcase(_Testcase, Config) ->
Config.
%% -----
call_add_signing_key(Config, 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).
call_get_signing_key(Config, 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).
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">>]),
#{<<"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, []).
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_default_resource_server_id_returns_error(_Config) ->
{error, _} = rabbit_oauth2_config:get_default_resource_server_id().
get_default_resource_server_id(_Config) ->
?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().
get_allowed_resource_server_ids_returns_resource_server_id(_Config) ->
[?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().
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()).
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">>).
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).
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">>]).
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">>]).
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">>]).
find_audience_in_resource_server_ids_using_binary_audience(_Config) ->
{ok, ?RABBITMQ} = rabbit_oauth2_config:find_audience_in_resource_server_ids(<<"rabbitmq other">>).
get_jwks_url_for_resource_server_id(_Config) ->
?assertEqual(<<"https://oauth-for-rabbitmq">>, rabbit_oauth2_config:get_jwks_url(?RABBITMQ)).
get_jwks_url_for_resource_servers_id(_Config) ->
?assertEqual(<<"https://oauth-for-rabbitmq1">>, rabbit_oauth2_config:get_jwks_url(<<"rabbitmq1">>)).
resolve_settings_via_inheritance(_Config) ->
?assertEqual(<<"https://oauth-for-rabbitmq">>, rabbit_oauth2_config:get_jwks_url(<<"rabbitmq-2">>)).
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)).
get_additional_scopes_key(_Config) ->
?assertEqual(<<"roles">>, rabbit_oauth2_config:get_additional_scopes_key()),
?assertEqual(<<"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(<<"roles">>, rabbit_oauth2_config:get_additional_scopes_key(?RABBITMQ)).
get_additional_scopes_key_when_not_defined(_Config) ->
?assertEqual(undefined, 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">>)).
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">>)).
get_default_preferred_username_claims(_Config) ->
?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">>)).
get_scope_prefix_when_not_defined(_Config) ->
?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">>)).
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">>)).
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">>)).
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">>)).
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">>)).
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">>)).

View File

@ -42,7 +42,8 @@ groups() ->
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_a_token_with_insufficient_resource_permission,
more_than_one_resource_server_id_not_allowed_in_one_token
]},
{token_refresh, [], [
@ -213,11 +214,23 @@ init_per_testcase(Testcase, Config) when Testcase =:= test_successful_connection
rabbit_ct_helpers:testcase_started(Config, Testcase),
Config;
init_per_testcase(multiple_resource_server_ids, Config) ->
ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env,
[rabbitmq_auth_backend_oauth2, scope_prefix, <<"rmq.">> ]),
ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env,
[rabbitmq_auth_backend_oauth2, resource_servers, #{
<<"prod">> => [ ],
<<"dev">> => [ ]
}]),
rabbit_ct_helpers:testcase_started(Config, multiple_resource_server_ids),
Config;
init_per_testcase(Testcase, Config) ->
rabbit_ct_helpers:testcase_started(Config, Testcase),
Config.
%%
%% Per-case Teardown
%%
@ -270,6 +283,14 @@ end_per_testcase(Testcase, Config) when Testcase =:= test_successful_connection_
rabbit_ct_helpers:testcase_finished(Config, Testcase),
Config;
end_per_testcase(multiple_resource_server_ids, Config) ->
rabbit_ct_broker_helpers:rpc(Config, 0, application, unset_env,
[rabbitmq_auth_backend_oauth2, scope_prefix ]),
rabbit_ct_broker_helpers:rpc(Config, 0, application, unset_env,
[rabbitmq_auth_backend_oauth2, resource_servers ]),
rabbit_ct_helpers:testcase_started(Config, multiple_resource_server_ids),
Config;
end_per_testcase(Testcase, Config) ->
rabbit_ct_broker_helpers:delete_vhost(Config, <<"vhost1">>),
rabbit_ct_helpers:testcase_finished(Config, Testcase),
@ -363,7 +384,7 @@ 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">>
[<<"hare">>, <<"rabbitmq">>]
),
Conn = open_unmanaged_connection(Config, 0, <<"username">>, Token),
{ok, Ch} = amqp_connection:open_channel(Conn),
@ -626,3 +647,8 @@ test_failed_connection_with_non_existent_scope_alias_in_scope_field(Config) ->
{_Algo, Token} = generate_valid_token(Config, <<"non-existent alias a8798s7doaisd79">>),
?assertMatch({error, not_allowed},
open_unmanaged_connection(Config, 0, <<"vhost2">>, <<"username">>, Token)).
more_than_one_resource_server_id_not_allowed_in_one_token(Config) ->
{_Algo, Token} = generate_valid_token(Config, <<"rmq.configure:*/*">>, [<<"prod">>, <<"dev">>]),
{error, _} = open_unmanaged_connection(Config, 0, <<"username">>, Token).

View File

@ -16,14 +16,11 @@
all() ->
[
test_own_scope,
test_validate_payload_resource_server_id_mismatch,
test_validate_payload_with_scope_prefix,
test_validate_payload,
test_validate_payload_without_scope,
test_validate_payload_when_verify_aud_false,
test_successful_access_with_a_token,
test_successful_authentication_without_scopes,
test_successful_authorization_without_scopes,
test_unsuccessful_access_without_scopes,
test_successful_access_with_a_token_with_variables_in_scopes,
test_successful_access_with_a_parsed_token,
@ -31,26 +28,39 @@ all() ->
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_username_from,
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_payload_rich_auth_request,
test_post_process_payload_rich_auth_request_using_regular_expression_with_cluster,
test_post_process_token_payload_complex_claims,
test_successful_access_with_a_token_that_uses_single_scope_alias_in_scope_field,
test_successful_access_with_a_token_that_uses_multiple_scope_aliases_in_scope_field,
test_successful_access_with_a_token_that_uses_single_scope_alias_in_scope_field_and_custom_scope_prefix,
test_unsuccessful_access_with_a_token_that_uses_missing_scope_alias_in_scope_field,
test_successful_access_with_a_token_that_uses_single_scope_alias_in_extra_scope_source_field,
test_successful_access_with_a_token_that_uses_multiple_scope_aliases_in_extra_scope_source_field,
test_unsuccessful_access_with_a_token_that_uses_missing_scope_alias_in_extra_scope_source_field,
test_default_ssl_options,
test_default_ssl_options_with_cacertfile
test_default_ssl_options_with_cacertfile,
test_username_from,
{group, with_rabbitmq_node}
].
groups() ->
[
{with_rabbitmq_node, [], [
test_command_json,
test_command_pem,
test_command_pem_no_kid
]
},
{with_resource_server_id, [], [
test_successful_access_with_a_token,
test_validate_payload_resource_server_id_mismatch,
test_successful_access_with_a_token_that_uses_single_scope_alias_in_scope_field,
test_successful_access_with_a_token_that_uses_multiple_scope_aliases_in_scope_field,
test_successful_authorization_without_scopes,
test_successful_authentication_without_scopes,
test_successful_access_with_a_token_that_uses_single_scope_alias_in_extra_scope_source_field,
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
]}
].
init_per_suite(Config) ->
@ -68,21 +78,45 @@ end_per_suite(Config) ->
Env),
rabbit_ct_helpers:run_teardown_steps(Config).
init_per_group(with_rabbitmq_node, Config) ->
Config1 = rabbit_ct_helpers:set_config(Config, [
{rmq_nodename_suffix, signing_key_group},
{rmq_nodes_count, 1}
]),
Config2 = rabbit_ct_helpers:merge_app_env(
Config1, {rabbitmq_auth_backend_oauth2, [
{resource_server_id, <<"rabbitmq">>},
{key_config, [{default_key, <<"token-key">>}]}
]}),
rabbit_ct_helpers:run_steps(Config2, rabbit_ct_broker_helpers:setup_steps());
init_per_group(with_resource_server_id, Config) ->
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
Config;
init_per_group(_, Config) ->
Config.
end_per_group(with_rabbitmq_node, Config) ->
rabbit_ct_helpers:run_steps(Config, rabbit_ct_broker_helpers:teardown_steps());
end_per_group(_, Config) ->
application:unset_env(rabbitmq_auth_backend_oauth2, resource_server_id),
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">>),
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq-resource">>),
Config;
init_per_testcase(test_validate_payload_when_verify_aud_false, Config) ->
application:set_env(rabbitmq_auth_backend_oauth2, verify_aud, false),
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
Config;
init_per_testcase(test_post_process_payload_rich_auth_request, Config) ->
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_type, <<"rabbitmq-type">>),
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
Config;
init_per_testcase(test_post_process_payload_rich_auth_request_using_regular_expression_with_cluster, Config) ->
@ -139,8 +173,12 @@ 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).
case rabbit_oauth2_config:find_audience_in_resource_server_ids(Audience) of
{ok, TargetResourceServerId} ->
{true, Payload} = uaa_jwt_jwt:decode_and_verify(TargetResourceServerId, Jwk, EncodedToken),
rabbit_auth_backend_oauth2:post_process_payload(TargetResourceServerId, Payload);
{error, _} = Error -> Error
end.
test_post_process_token_payload_keycloak(_) ->
Pairs = [
@ -202,8 +240,8 @@ 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).
{true, Payload} = uaa_jwt_jwt:decode_and_verify(<<"rabbitmq">>, Jwk, EncodedToken),
rabbit_auth_backend_oauth2:post_process_payload(<<"rabbitmq">>, Payload).
test_post_process_payload_rich_auth_request_using_regular_expression_with_cluster(_) ->
@ -244,7 +282,7 @@ test_post_process_payload_rich_auth_request_using_regular_expression_with_cluste
lists:foreach(
fun({Case, Permissions, ExpectedScope}) ->
Payload = post_process_payload_with_rich_auth_request(Permissions),
Payload = post_process_payload_with_rich_auth_request(<<"rabbitmq-test">>, Permissions),
?assertEqual(lists:sort(ExpectedScope), lists:sort(maps:get(<<"scope">>, Payload)), Case)
end, Pairs).
@ -542,16 +580,16 @@ test_post_process_payload_rich_auth_request(_) ->
lists:foreach(
fun({Case, Permissions, ExpectedScope}) ->
Payload = post_process_payload_with_rich_auth_request(Permissions),
Payload = post_process_payload_with_rich_auth_request(<<"rabbitmq">>, Permissions),
?assertEqual(lists:sort(ExpectedScope), lists:sort(maps:get(<<"scope">>, Payload)), Case)
end, Pairs).
post_process_payload_with_rich_auth_request(Permissions) ->
post_process_payload_with_rich_auth_request(ResourceServerId, Permissions) ->
Jwk = ?UTIL_MOD:fixture_jwk(),
Token = maps:put(<<"authorization_details">>, Permissions, ?UTIL_MOD:plain_token_without_scopes_and_aud()),
{_, 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).
{true, Payload} = uaa_jwt_jwt:decode_and_verify(<<"rabbitmq">>, Jwk, EncodedToken),
rabbit_auth_backend_oauth2:post_process_payload(ResourceServerId, Payload).
test_post_process_token_payload_complex_claims(_) ->
Pairs = [
@ -612,22 +650,21 @@ test_post_process_token_payload_complex_claims(_) ->
],
lists:foreach(
fun({Authorization, ExpectedScope}) ->
Payload = post_process_payload_with_complex_claim_authorization(Authorization),
Payload = post_process_payload_with_complex_claim_authorization(<<"rabbitmq-resource">>, Authorization),
?assertEqual(ExpectedScope, maps:get(<<"scope">>, Payload))
end, Pairs).
post_process_payload_with_complex_claim_authorization(Authorization) ->
post_process_payload_with_complex_claim_authorization(ResourceServerId, 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).
rabbit_auth_backend_oauth2:post_process_payload(ResourceServerId, Payload).
test_successful_authentication_without_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:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk),
@ -639,7 +676,6 @@ test_successful_authorization_without_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:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk),
@ -654,7 +690,6 @@ test_successful_access_with_a_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">>),
VHost = <<"vhost">>,
Username = <<"username">>,
@ -662,8 +697,8 @@ test_successful_access_with_a_token(_) ->
{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}),
% {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)),
assert_resource_access_granted(User, VHost, <<"foo">>, configure),
@ -680,7 +715,6 @@ test_successful_access_with_a_token_with_variables_in_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">>),
VHost = <<"my-vhost">>,
Username = <<"username">>,
@ -696,7 +730,6 @@ test_successful_access_with_a_parsed_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:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk),
@ -711,7 +744,6 @@ 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:token_with_sub(?UTIL_MOD:fixture_token(
[<<"rabbitmq.tag:management">>, <<"rabbitmq.tag:policymaker">>]), Username), Jwk),
@ -723,7 +755,6 @@ test_successful_access_with_a_token_that_uses_single_scope_alias_in_scope_field(
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">>),
Alias = <<"client-alias-1">>,
application:set_env(rabbitmq_auth_backend_oauth2, scope_aliases, #{
Alias => [
@ -742,7 +773,7 @@ test_successful_access_with_a_token_that_uses_single_scope_alias_in_scope_field(
Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub(
?UTIL_MOD:token_with_scope_alias_in_scope_field(Alias), Username), Jwk),
{ok, #auth_user{username = Username, tags = [custom, management]} = AuthUser} =
{ok, #auth_user{username = Username} = AuthUser} =
rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]),
assert_vhost_access_granted(AuthUser, VHost),
assert_vhost_access_denied(AuthUser, <<"some-other-vhost">>),
@ -764,7 +795,6 @@ test_successful_access_with_a_token_that_uses_single_scope_alias_in_scope_field_
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">>),
application:set_env(rabbitmq_auth_backend_oauth2, scope_prefix, <<>>),
Alias = <<"client-alias-1">>,
application:set_env(rabbitmq_auth_backend_oauth2, scope_aliases, #{
@ -784,7 +814,7 @@ test_successful_access_with_a_token_that_uses_single_scope_alias_in_scope_field_
Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub(
?UTIL_MOD:token_with_scope_alias_in_scope_field(Alias), Username), Jwk),
{ok, #auth_user{username = Username, tags = [custom, management]} = AuthUser} =
{ok, #auth_user{username = Username} = AuthUser} =
rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]),
assert_vhost_access_granted(AuthUser, VHost),
assert_vhost_access_denied(AuthUser, <<"some-other-vhost">>),
@ -806,7 +836,6 @@ test_successful_access_with_a_token_that_uses_multiple_scope_aliases_in_scope_fi
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">>),
Role1 = <<"client-aliases-1">>,
Role2 = <<"client-aliases-2">>,
Role3 = <<"client-aliases-3">>,
@ -831,7 +860,7 @@ test_successful_access_with_a_token_that_uses_multiple_scope_aliases_in_scope_fi
Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub(
?UTIL_MOD:token_with_scope_alias_in_scope_field([Role1, Role2, Role3]), Username), Jwk),
{ok, #auth_user{username = Username, tags = [custom, management]} = AuthUser} =
{ok, #auth_user{username = Username} = AuthUser} =
rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]),
assert_vhost_access_granted(AuthUser, VHost),
assert_vhost_access_denied(AuthUser, <<"some-other-vhost">>),
@ -890,7 +919,6 @@ test_successful_access_with_a_token_that_uses_single_scope_alias_in_extra_scope_
UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}],
application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv),
application:set_env(rabbitmq_auth_backend_oauth2, extra_scopes_source, <<"claims">>),
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
Alias = <<"client-alias-1">>,
application:set_env(rabbitmq_auth_backend_oauth2, scope_aliases, #{
Alias => [
@ -928,7 +956,6 @@ test_successful_access_with_a_token_that_uses_multiple_scope_aliases_in_extra_sc
UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}],
application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv),
application:set_env(rabbitmq_auth_backend_oauth2, extra_scopes_source, <<"claims">>),
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
Role1 = <<"client-aliases-1">>,
Role2 = <<"client-aliases-2">>,
Role3 = <<"client-aliases-3">>,
@ -1027,10 +1054,9 @@ test_unsuccessful_access_without_scopes(_) ->
UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}],
application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv),
{ok, #auth_user{username = Username, tags = [], impl = CredentialsFun } = AuthUser} =
{ok, #auth_user{username = Username, tags = [], impl = _CredentialsFun } = AuthUser} =
rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]),
ct:log("authuser ~p ~p ", [AuthUser, CredentialsFun()]),
assert_vhost_access_denied(AuthUser, <<"vhost">>).
test_restricted_vhost_access_with_a_valid_token(_) ->
@ -1100,48 +1126,29 @@ test_incorrect_kid(_) ->
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:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk1),
?assertMatch({refused, "Authentication using an OAuth 2/JWT token failed: ~tp", [{error,key_not_found}]},
Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk, AltKid),
?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})).
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:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk),
login_and_check_vhost_access(Username, Token, Vhost) ->
{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_vhost_access(User, <<"vhost">>, Vhost)).
test_command_json(Config) ->
Username = <<"username">>,
Jwk = ?UTIL_MOD:fixture_jwk(),
Json = rabbit_json:encode(Jwk),
'Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand':run(
[<<"token-key">>],
#{node => rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename), json => Json}),
Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk),
rabbit_ct_broker_helpers:rpc(Config, 0, unit_SUITE, login_and_check_vhost_access, [Username, Token, none]).
test_username_from(_) ->
Pairs = [
{ <<"resolved username from DEFAULT_PREFERRED_USERNAME_CLAIMS 'sub' ">>, % Comment
[ ], % Given this configure preferred_username_claims
#{ % When we test this Token
<<"sub">> => <<"rabbit_user">>
},
<<"rabbit_user">> % We expect username to be this one
},
{ <<"resolved username from DEFAULT_PREFERRED_USERNAME_CLAIMS when there are no preferred_username_claims">>, % Comment
<<>>, % Given this configure preferred_username_claims
#{ % When we test this Token
<<"sub">> => <<"rabbit_user">>
},
<<"rabbit_user">> % We expect username to be this one
},
{ <<"resolved username from DEFAULT_PREFERRED_USERNAME_CLAIMS 'client_id' ">>, % Comment
[ ], % Given this configure preferred_username_claims
#{ % When we test this Token
<<"client_id">> => <<"rabbit_user">>
},
<<"rabbit_user">> % We expect username to be this one
},
{ <<"resolve username from 1st claim in the array of configured claims ">>,
[<<"user_name">>, <<"email">>],
#{
@ -1158,7 +1165,7 @@ test_username_from(_) ->
<<"rabbit_user">>
},
{ <<"resolve username from configured string claim ">>,
<<"email">>,
[<<"email">>],
#{
<<"email">> => <<"rabbit_user">>
},
@ -1182,7 +1189,6 @@ test_username_from(_) ->
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),
@ -1193,45 +1199,14 @@ test_command_pem_file(Config) ->
'Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand':run(
[<<"token-key">>],
#{node => node(), pem_file => PublicKeyFile}),
#{node => rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename), 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}),
rabbit_ct_broker_helpers:rpc(Config, 0, unit_SUITE, login_and_check_vhost_access, [Username, Token, none]).
?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),
@ -1240,18 +1215,13 @@ test_command_pem(Config) ->
'Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand':run(
[<<"token-key">>],
#{node => node(), pem => Pem}),
#{node => rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename), pem => Pem}),
Token = ?UTIL_MOD:sign_token_rsa(?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), 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)).
rabbit_ct_broker_helpers:rpc(Config, 0, unit_SUITE, login_and_check_vhost_access, [Username, Token, 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),
@ -1260,19 +1230,10 @@ test_command_pem_no_kid(Config) ->
'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),
#{node => rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename), pem => Pem}),
Token = ?UTIL_MOD:sign_token_no_kid(?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), 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)).
rabbit_ct_broker_helpers:rpc(Config, 0, unit_SUITE, login_and_check_vhost_access, [Username, Token, none]).
test_own_scope(_) ->
@ -1300,10 +1261,10 @@ test_validate_payload_resource_server_id_mismatch(_) ->
?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, ?DEFAULT_SCOPE_PREFIX)),
rabbit_auth_backend_oauth2:validate_payload(?RESOURCE_SERVER_ID, NoKnownResourceServerId, ?DEFAULT_SCOPE_PREFIX)),
?assertEqual({refused, {invalid_aud, {resource_id_not_found_in_aud, ?RESOURCE_SERVER_ID, []}}},
rabbit_auth_backend_oauth2:validate_payload(EmptyAud, ?RESOURCE_SERVER_ID, ?DEFAULT_SCOPE_PREFIX)).
rabbit_auth_backend_oauth2:validate_payload(?RESOURCE_SERVER_ID, EmptyAud, ?DEFAULT_SCOPE_PREFIX)).
test_validate_payload_with_scope_prefix(_) ->
Scenarios = [ { <<>>,
@ -1321,7 +1282,7 @@ test_validate_payload_with_scope_prefix(_) ->
lists:map(fun({ ScopePrefix, Token, ExpectedScopes}) ->
?assertEqual({ok, #{<<"aud">> => [?RESOURCE_SERVER_ID], <<"scope">> => ExpectedScopes } },
rabbit_auth_backend_oauth2:validate_payload(Token, ?RESOURCE_SERVER_ID, ScopePrefix))
rabbit_auth_backend_oauth2:validate_payload(?RESOURCE_SERVER_ID, Token, ScopePrefix))
end
, Scenarios).
@ -1332,13 +1293,13 @@ test_validate_payload(_) ->
<<"foobar">>, <<"rabbitmq.other.third">>]},
?assertEqual({ok, #{<<"aud">> => [?RESOURCE_SERVER_ID],
<<"scope">> => [<<"bar">>, <<"other.third">>]}},
rabbit_auth_backend_oauth2:validate_payload(KnownResourceServerId, ?RESOURCE_SERVER_ID, ?DEFAULT_SCOPE_PREFIX)).
rabbit_auth_backend_oauth2:validate_payload(?RESOURCE_SERVER_ID, KnownResourceServerId, ?DEFAULT_SCOPE_PREFIX)).
test_validate_payload_without_scope(_) ->
KnownResourceServerId = #{<<"aud">> => [?RESOURCE_SERVER_ID]
},
?assertEqual({ok, #{<<"aud">> => [?RESOURCE_SERVER_ID] }},
rabbit_auth_backend_oauth2:validate_payload(KnownResourceServerId, ?RESOURCE_SERVER_ID, ?DEFAULT_SCOPE_PREFIX)).
rabbit_auth_backend_oauth2:validate_payload(?RESOURCE_SERVER_ID, KnownResourceServerId, ?DEFAULT_SCOPE_PREFIX)).
test_validate_payload_when_verify_aud_false(_) ->
WithoutAud = #{
@ -1347,7 +1308,7 @@ test_validate_payload_when_verify_aud_false(_) ->
<<"foobar">>, <<"rabbitmq.other.third">>]},
?assertEqual({ok, #{
<<"scope">> => [<<"bar">>, <<"other.third">>]}},
rabbit_auth_backend_oauth2:validate_payload(WithoutAud, ?RESOURCE_SERVER_ID, ?DEFAULT_SCOPE_PREFIX)),
rabbit_auth_backend_oauth2:validate_payload(?RESOURCE_SERVER_ID, WithoutAud, ?DEFAULT_SCOPE_PREFIX)),
WithAudWithUnknownResourceId = #{
<<"aud">> => [<<"unknown">>],
@ -1356,7 +1317,7 @@ test_validate_payload_when_verify_aud_false(_) ->
<<"foobar">>, <<"rabbitmq.other.third">>]},
?assertEqual({ok, #{<<"aud">> => [<<"unknown">>],
<<"scope">> => [<<"bar">>, <<"other.third">>]}},
rabbit_auth_backend_oauth2:validate_payload(WithAudWithUnknownResourceId, ?RESOURCE_SERVER_ID, ?DEFAULT_SCOPE_PREFIX)).
rabbit_auth_backend_oauth2:validate_payload(?RESOURCE_SERVER_ID, WithAudWithUnknownResourceId, ?DEFAULT_SCOPE_PREFIX)).
test_default_ssl_options(_) ->
?assertEqual([
@ -1365,7 +1326,7 @@ test_default_ssl_options(_) ->
{fail_if_no_peer_cert, false},
{crl_check, false},
{crl_cache, {ssl_crl_cache, {internal, [{http, 10000}]}}}
], uaa_jwks:ssl_options()).
], uaa_jwks:ssl_options(rabbit_oauth2_config:get_key_config())).
test_default_ssl_options_with_cacertfile(_) ->
?assertEqual([
@ -1375,7 +1336,7 @@ test_default_ssl_options_with_cacertfile(_) ->
{crl_check, false},
{crl_cache, {ssl_crl_cache, {internal, [{http, 10000}]}}},
{cacertfile, filename:join(["testca", "cacert.pem"])}
], uaa_jwks:ssl_options()).
], uaa_jwks:ssl_options(rabbit_oauth2_config:get_key_config())).
%%
%% Helpers

View File

@ -1,7 +1,7 @@
../../../.gitignore
.sw?
.*.sw?
*.beam
*.pem
erl_crash.dump
MnesiaCore.*
/.erlang.mk/

View File

@ -89,6 +89,7 @@ rabbitmq_app(
"@cowboy//:erlang_app",
"@cowlib//:erlang_app",
"@ranch//:erlang_app",
"//deps/oauth2_client:erlang_app",
],
)
@ -207,6 +208,11 @@ rabbitmq_suite(
size = "small",
)
rabbitmq_suite(
name = "rabbit_mgmt_wm_auth_SUITE",
size = "small",
)
rabbitmq_suite(
name = "stats_SUITE",
size = "small",

View File

@ -21,7 +21,7 @@ define PROJECT_APP_EXTRA_KEYS
{broker_version_requirements, []}
endef
DEPS = rabbit_common rabbit amqp_client cowboy cowlib rabbitmq_web_dispatch rabbitmq_management_agent
DEPS = rabbit_common rabbit amqp_client cowboy cowlib rabbitmq_web_dispatch rabbitmq_management_agent oauth2_client
TEST_DEPS = rabbitmq_ct_helpers rabbitmq_ct_client_helpers proper
LOCAL_DEPS += ranch ssl crypto public_key

View File

@ -127,6 +127,7 @@ def all_beam_files(name = "all_beam_files"):
"//deps/rabbit:erlang_app",
"//deps/rabbit_common:erlang_app",
"//deps/rabbitmq_management_agent:erlang_app",
"//deps/oauth2_client:erlang_app",
],
)
@ -259,6 +260,7 @@ def all_test_beam_files(name = "all_test_beam_files"):
"//deps/rabbit:erlang_app",
"//deps/rabbit_common:erlang_app",
"//deps/rabbitmq_management_agent:erlang_app",
"//deps/oauth2_client:erlang_app",
],
)
@ -602,6 +604,14 @@ def test_suite_beam_files(name = "test_suite_beam_files"):
app_name = "rabbitmq_management",
erlc_opts = "//:test_erlc_opts",
)
erlang_bytecode(
name = "rabbit_mgmt_wm_auth_SUITE_beam_files",
testonly = True,
srcs = ["test/rabbit_mgmt_wm_auth_SUITE.erl"],
outs = ["test/rabbit_mgmt_wm_auth_SUITE.beam"],
app_name = "rabbitmq_management",
erlc_opts = "//:test_erlc_opts",
)
erlang_bytecode(
name = "stats_SUITE_beam_files",
testonly = True,

View File

@ -432,7 +432,6 @@ fun(Conf) ->
end}.
%% OAuth 2/SSO access only
{mapping, "management.disable_basic_auth", "rabbitmq_management.disable_basic_auth",
[{datatype, {enum, [true, false]}}]}.
@ -448,15 +447,21 @@ end}.
%% ===========================================================================
%% Authorization
%% OAuth 2/SSO access only
%% Enable OAuth2 in the management ui
{mapping, "management.oauth_enabled", "rabbitmq_management.oauth_enabled",
[{datatype, {enum, [true, false]}}]}.
%% Enable Basic Auth in the management ui along with OAuth2 (it requires an additional auth_backend)
{mapping, "management.oauth_disable_basic_auth", "rabbitmq_management.oauth_disable_basic_auth",
[{datatype, {enum, [true, false]}}]}.
%% The URL of the OIDC/OAuth2 provider
{mapping, "management.oauth_provider_url", "rabbitmq_management.oauth_provider_url",
[{datatype, string}]}.
%% Your client application's identifier as registered with the OIDC/OAuth2
{mapping, "management.oauth_client_id", "rabbitmq_management.oauth_client_id",
[{datatype, string}]}.
@ -483,6 +488,95 @@ end}.
{mapping, "management.oauth_initiated_logon_type", "rabbitmq_management.oauth_initiated_logon_type",
[{datatype, {enum, [sp_initiated, idp_initiated]}}]}.
{mapping,
"management.oauth_resource_servers.$name.id",
"rabbitmq_management.oauth_resource_servers",
[{datatype, string}]
}.
{mapping,
"management.oauth_resource_servers.$name.label",
"rabbitmq_management.oauth_resource_servers",
[{datatype, string}]
}.
{mapping,
"management.oauth_resource_servers.$name.oauth_provider_url",
"rabbitmq_management.oauth_resource_servers",
[{datatype, string}]
}.
{mapping,
"management.oauth_resource_servers.$name.client_id",
"rabbitmq_management.oauth_resource_servers",
[{datatype, string}]
}.
{mapping,
"management.oauth_resource_servers.$name.client_secret",
"rabbitmq_management.oauth_resource_servers",
[{datatype, string}]
}.
{mapping,
"management.oauth_resource_servers.$name.oauth_response_type",
"rabbitmq_management.oauth_resource_servers",
[{datatype, string}]
}.
{mapping,
"management.oauth_resource_servers.$name.scopes",
"rabbitmq_management.oauth_resource_servers",
[{datatype, string}]
}.
{mapping,
"management.oauth_resource_servers.$name.metadata_url",
"rabbitmq_management.oauth_resource_servers",
[{datatype, string}]
}.
{mapping,
"management.oauth_resource_servers.$name.initiated_logon_type",
"rabbitmq_management.oauth_resource_servers",
[{datatype, {enum, [sp_initiated, idp_initiated]}}]}.
{translation, "rabbitmq_management.oauth_resource_servers",
fun(Conf) ->
Settings = cuttlefish_variable:filter_by_prefix("management.oauth_resource_servers", Conf),
ResourceServers = [{Name, {list_to_atom(Key), V}} || {["management","oauth_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, ResourceServers),
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 when is_binary(ID) -> maps:put(ID, V, NewMap);
ID -> maps:put(list_to_binary(ID), V, NewMap)
end
end,
maps:fold(IndexByIdOrElseNameFun,#{}, NewGroupTwo)
end}.
%% ===========================================================================

View File

@ -37,7 +37,6 @@
oauth_initiateLogout();
} else {
if (!status.loggedIn) {
replace_content('outer', format('login_oauth', {}));
clear_auth();
} else {
oauth.logged_in = true;

View File

@ -22,27 +22,63 @@ function startWithOAuthLogin () {
store_pref("oauth-return-to", window.location.hash);
if (!oauth.logged_in) {
if (oauth.sp_initiated) {
get(oauth.readiness_url, 'application/json', function (req) {
if (oauth.resource_servers.length == 1 && oauth.resource_servers[0].sp_initiated) {
get(readiness_url(oauth.resource_servers[0]), 'application/json', function (req) {
if (req.status !== 200) {
renderWarningMessageInLoginStatus(oauth.authority + ' does not appear to be a running OAuth2.0 instance or may not have a trusted SSL certificate')
let message = 'Unable to retrieve OpenID configuration from ' + oauth.resource_servers[0].provider_url + '. '
switch(req.status) {
case 0:
message += 'Reason: ' + oauth.resource_servers[0].provider_url + " not reachable"
break
case 404:
message += 'Reason: ' + readiness_url(oauth.resource_servers[0]) + " return 404"
break
default:
message += 'Reason: ' + req.statusText
}
console.log(message)
renderWarningMessageInLoginStatus(message)
} else {
replace_content('outer', format('login_oauth', {}))
try {
validate_openid_configuration(JSON.parse(req.responseText))
render_login_oauth()
start_app_login()
}catch(e) {
let message = 'Unable to retrieve OpenID configuration from ' + readiness_url(oauth.resource_servers[0]) +
'. Reason: ' + e.message
console.log(message)
renderWarningMessageInLoginStatus(message)
}
}
})
} else {
replace_content('outer', format('login_oauth', {}))
console.log("Rendering login with " + JSON.stringify(oauth.resource_servers))
render_login_oauth()
start_app_login()
}
} else {
start_app_login()
}
}
function render_login_oauth(message) {
replace_content('outer', format('login_oauth', {
'message' : message,
'resource_servers' : oauth.resource_servers,
'errorCode' : message != null ? (message == "Not authorized" ? 401 : 400) : undefined,
'oauth_disable_basic_auth' : oauth.oauth_disable_basic_auth
}))
setup_visibility()
$('#login').off('click', 'div.section h2, div.section-hidden h2');
$('#login').on('click', 'div.section h2, div.section-hidden h2', function() {
toggle_visibility($(this));
});
}
function renderWarningMessageInLoginStatus(message) {
replace_content('outer', format('login_oauth', {}))
replace_content('login-status', '<p class="warning">' + message + '</p> <button id="loginWindow" onclick="oauth_initiateLogin()">Click here to log in</button>')
render_login_oauth(message)
}
@ -72,7 +108,7 @@ function start_app_login () {
app = new Sammy.Application(function () {
this.get('/', function () {})
this.get('#/', function () {})
if (!oauth.enabled) {
if (!oauth.enabled || !oauth.oauth_disable_basic_auth) {
this.put('#/login', function() {
set_basic_auth(this.params['username'], this.params['password'])
check_login()

View File

@ -2,24 +2,6 @@
var mgr;
var _management_logger;
/*
function oauth_initialize_if_required_deprecated() {
rabbit_port = window.location.port ? ":" + window.location.port : ""
rabbit_path_prefix = window.location.pathname.replace(/(\/js\/oidc-oauth\/.*$|\/+$)/, "")
rabbit_base_uri = window.location.protocol + "//" + window.location.hostname
+ rabbit_port + rabbit_path_prefix
var request = new XMLHttpRequest();
request.open("GET", rabbit_base_uri + "/api/auth", false);
request.send(null);
if (request.status === 200) {
return oauth_initialize(JSON.parse(request.responseText));
} else {
return { "enabled" : false };
}
}
*/
function rabbit_base_uri() {
return window.location.protocol + "//" + window.location.hostname + rabbit_port() + rabbit_path_prefix()
@ -30,59 +12,113 @@ function rabbit_path_prefix() {
function rabbit_port() {
return window.location.port ? ":" + window.location.port : "";
}
function auth_settings_apply_defaults(authSettings) {
function readiness_url(resource_server) {
if (!resource_server.metadata_url) {
return resource_server.provider_url + "/.well-known/openid-configuration"
}else {
return resource_server.metadata_url
}
}
function auth_settings_apply_defaults(authSettings) {
if (authSettings.oauth_provider_url) {
if (!authSettings.oauth_response_type) {
authSettings.oauth_response_type = "code"; // although the default value in oidc client
}
if (!authSettings.oauth_scopes) {
authSettings.oauth_scopes = "openid profile";
}
if (!authSettings.oauth_initiated_logon_type) {
authSettings.oauth_initiated_logon_type = "sp_initiated"
}
}
authSettings.resource_servers = []
if (authSettings.oauth_resource_servers && Object.keys(authSettings.oauth_resource_servers).length > 0) {
for (const [resource_server_id, resource_server] of Object.entries(authSettings.oauth_resource_servers)) {
if (!resource_server.provider_url) {
resource_server.provider_url = authSettings.oauth_provider_url
}
if (!resource_server.provider_url) {
break
}
if (!resource_server.response_type) {
resource_server.response_type = authSettings.oauth_response_type
if (!resource_server.response_type) {
resource_server.response_type = "code"
}
}
if (!resource_server.scopes) {
resource_server.scopes = authSettings.oauth_scopes
if (!resource_server.scopes) {
resource_server.scopes = "openid profile"
}
}
if (!resource_server.client_id) {
resource_server.client_id = authSettings.oauth_client_id
}
if (!resource_server.client_secret) {
resource_server.client_secret = authSettings.oauth_client_secret
}
if (resource_server.initiated_logon_type == "idp_initiated") {
resource_server.sp_initiated = false
} else {
resource_server.sp_initiated = true
}
if (!resource_server.metadata_url) {
resource_server.metadata_url = authSettings.metadata_url
}
resource_server.id = resource_server_id
authSettings.resource_servers.push(resource_server)
}
}else if (authSettings.oauth_provider_url) {
let resource = {
"provider_url" : authSettings.oauth_provider_url,
"scopes" : authSettings.oauth_scopes,
"response_type" : authSettings.oauth_response_type,
"sp_initiated" : authSettings.oauth_initiated_logon_type == "sp_initiated",
"id" : authSettings.oauth_resource_id
}
if (authSettings.oauth_client_secret) {
resource.client_secret = authSettings.oauth_client_secret
}
if (authSettings.oauth_client_id) {
resource.client_id = authSettings.oauth_client_id
}
if (authSettings.metadata_url) {
resource.metadata_url = authSettings.metadata_url
}
authSettings.resource_servers.push(resource)
}
return authSettings;
}
function oauth_initialize(authSettings) {
oauth = {
"logged_in": false,
"enabled" : authSettings.oauth_enabled,
"authority" : authSettings.oauth_provider_url
};
if (!oauth.enabled) return oauth;
oauth.sp_initiated = true;
if (authSettings.oauth_initiated_logon_type == "idp_initiated") {
oauth.sp_initiated = false;
return oauth;
}
authSettings = auth_settings_apply_defaults(authSettings);
function oauth_initialize_user_manager(resource_server) {
oidcSettings = {
//userStore: new WebStorageStateStore({ store: window.localStorage }),
authority: authSettings.oauth_provider_url,
client_id: authSettings.oauth_client_id,
response_type: authSettings.oauth_response_type,
scope: authSettings.oauth_scopes,
resource: authSettings.oauth_resource_id,
authority: resource_server.provider_url,
client_id: resource_server.client_id,
response_type: resource_server.response_type,
scope: resource_server.scopes,
resource: resource_server.id,
redirect_uri: rabbit_base_uri() + "/js/oidc-oauth/login-callback.html",
post_logout_redirect_uri: rabbit_base_uri() + "/",
automaticSilentRenew: true,
revokeAccessTokenOnSignout: true,
extraQueryParams: {
audience: authSettings.oauth_resource_id, // required by oauth0
audience: resource_server.id, // required by oauth0
},
};
if (authSettings.oauth_client_secret != "") {
oidcSettings.client_secret = authSettings.oauth_client_secret;
if (resource_server.client_secret != "") {
oidcSettings.client_secret = resource_server.client_secret;
}
if (authSettings.oauth_metadata_url != "") {
oidcSettings.metadataUrl = authSettings.oauth_metadata_url;
if (resource_server.metadata_url != "") {
oidcSettings.metadataUrl = resource_server.metadata_url;
}
oidc.Log.setLevel(oidc.Log.DEBUG);
@ -108,6 +144,32 @@ function oauth_initialize(authSettings) {
set_token_auth(oauth.access_token)
});
}
function oauth_initialize(authSettings) {
authSettings = auth_settings_apply_defaults(authSettings);
oauth = {
"logged_in": false,
"enabled" : authSettings.oauth_enabled,
"resource_servers" : authSettings.resource_servers,
"oauth_disable_basic_auth" : authSettings.oauth_disable_basic_auth
}
if (!oauth.enabled) return oauth;
resource_server = null
if (oauth.resource_servers.length == 1) {
resource_server = oauth.resource_servers[0]
} else if (has_auth_resource()) {
resource_server = lookup_resource_server(get_auth_resource())
}
if (resource_server) {
oauth.sp_initiated = resource_server.sp_initiated
oauth.authority = resource_server.provider_url
if (!resource_server.sp_initiated) return oauth;
else oauth_initialize_user_manager(resource_server)
}
return oauth;
}
@ -133,20 +195,38 @@ function oauth_is_logged_in() {
return { "user": user, "loggedIn": !user.expired };
});
}
function oauth_initiateLogin() {
if (oauth.sp_initiated) {
function lookup_resource_server(resource_server_id) {
let i = 0;
while (i < oauth.resource_servers.length && oauth.resource_servers[i].id != resource_server_id) {
i++;
}
if (i < oauth.resource_servers.length) return oauth.resource_servers[i]
else return null
}
function oauth_initiateLogin(resource_server_id) {
resource_server = lookup_resource_server(resource_server_id)
if (!resource_server) return;
set_auth_resource(resource_server_id)
oauth.sp_initiated = resource_server.sp_initiated
oauth.authority = resource_server.provider_url
if (resource_server.sp_initiated) {
if (!mgr) oauth_initialize_user_manager(resource_server)
mgr.signinRedirect({ state: { } }).then(function() {
_management_logger.debug("signinRedirect done");
_management_logger.debug("signinRedirect done")
}).catch(function(err) {
_management_logger.error(err);
_management_logger.error(err)
})
} else {
location.href = oauth.authority;
location.href = resource_server.provider_url
}
}
function oauth_redirectToHome(oauth) {
console.log("oauth_redirectToHome set_token_auth")
set_token_auth(oauth.access_token)
path = get_pref("oauth-return-to");
@ -183,3 +263,21 @@ function oauth_completeLogout() {
clear_auth()
mgr.signoutRedirectCallback().then(_ => oauth_redirectToLogin())
}
function validate_openid_configuration(payload) {
if (payload == null) {
throw new Error("Payload does not contain openid configuration")
}
if (typeof payload.authorization_endpoint != 'string') {
throw new Error("Missing authorization_endpoint")
}
if (typeof payload.token_endpoint != 'string') {
throw new Error("Missing token_endpoint")
}
if (typeof payload.jwks_uri != 'string') {
throw new Error("Missing jwks_uri")
}
if (typeof payload.end_session_endpoint != 'string') {
throw new Error("Missing end_session_endpoint")
}
}

View File

@ -12,6 +12,17 @@ const CREDENTIALS = 'credentials'
const AUTH_SCHEME = "auth-scheme"
const LOGGED_IN = 'loggedIn'
const LOGIN_SESSION_TIMEOUT = "login_session_timeout"
const AUTH_RESOURCE = 'auth_resource'
function set_auth_resource(resource) {
store_local_pref(AUTH_RESOURCE, resource)
}
function has_auth_resource() {
return get_local_pref(AUTH_RESOURCE) != undefined
}
function get_auth_resource() {
return get_local_pref(AUTH_RESOURCE)
}
function has_auth_credentials() {
return get_local_pref(CREDENTIALS) != undefined && get_local_pref(AUTH_SCHEME) != undefined &&
@ -28,6 +39,7 @@ function clear_auth() {
clear_local_pref(AUTH_SCHEME)
clear_cookie_value(LOGIN_SESSION_TIMEOUT)
clear_cookie_value(LOGGED_IN)
clear_local_pref(AUTH_RESOURCE)
}
function set_basic_auth(username, password) {
set_auth("Basic", b64_encode_utf8(username + ":" + password), default_hard_session_timeout())

View File

@ -1,5 +1,76 @@
<div id="login">
<p><img src="img/rabbitmqlogo.svg" alt="RabbitMQ logo" width="204" height="37"/></p>
<div id="login-status"><button id="loginWindow" onclick="oauth_initiateLogin()">Click here to log in</button></div>
<!-- begin login status -->
<div id="login-status">
<% if (typeof message == 'string') { %>
<p class="warning"><%=fmt_string(message)%> </p>
<% } %>
<% if (typeof errorCode == 'number' && errorCode == 401) { %>
<button id="logout" onclick="oauth_initiateLogout()">Click here to logout</button>
<%} %>
</div>
<% if (!(typeof errorCode == 'number' && errorCode == 401)) { %>
<!-- end login status -->
<% if ((typeof resource_servers == 'object' && resource_servers.length == 1) && oauth_disable_basic_auth) { %>
<button id="login" onclick="oauth_initiateLogin('<%=resource_servers[0].id%>')">Click here to log in</button>
<% } else if (typeof resource_servers == 'object') { %>
<b>Login with :</b>
<p></p>
<!-- begin login with oauth2 -->
<div class="section" id="login-with-oauth2">
<h2>OAuth 2.0</h2>
<div class="hider"><div class="updatable">
<% if (resource_servers.length == 1) { %>
<button id="login" onclick="oauth_initiateLogin('<%=resource_servers[0].id%>')">Click here to log in</button>
<% } else { %>
<form onsubmit="oauth_initiateLogin(document.getElementById('oauth2-resource').value)">
<label for="oauth2-resource">Resource:</label>
<select id="oauth2-resource">
<% for (var i = 0; i < resource_servers.length; i++) { %>
<option value="<%= fmt_string(resource_servers[i].id) %>"><%= resource_servers[i].label %></option>
<% } %>
</select>
<br></br>
<button id="login" type="submit">Click here to log in</button>
</form>
<% } %>
</div></div>
</div>
<% } %>
<!-- end login with oauth2 -->
<!-- begin login with basic auth -->
<%if (!oauth_disable_basic_auth) { %>
<div class="section-hidden" id="login-with-basic-auth">
<h2>Basic Authentication</h2>
<div class="hider"><div class="updatable">
<form action="#/login" id="basic-auth-form" method="put">
<table class="form">
<tr>
<th><label>Username:</label></th>
<td><input type="text" id="username" name="username" autocomplete="username"/><span class="mand">*</span></td>
</tr>
<tr>
<th><label>Password:</label></th>
<td><input type="password" id="password" name="password" autocomplete="current-password"/><span class="mand">*</span></td>
</tr>
<tr>
<th>&nbsp;</th>
<td><input type="submit" value="Login"/></td>
</tr>
</table>
</form>
</div></div>
</div> <!-- section -->
<% } %>
<!-- end login with basic auth -->
<% } %>
</div> <!-- login -->

View File

@ -36,14 +36,21 @@ not see any browser interaction, everything happens in the background, i.e. rabb
To run just one suite, you proceed as follows:
```
suites/oauth-with-uaa.sh
suites/authnz-mgt/oauth-with-uaa.sh
```
And to is run all suites, like the CI does, you run:
And to a group of suites, like the CI does, you run the command below which runs all
the management ui suites. If you do not pass `full-suite-management-ui`, `run-suites.sh`
defaults to `full-suite-management-ui`.
```
./run-suites.sh
./run-suites.sh full-suite-management-ui
```
Other suites files available are:
- `short-suite-management-ui` which only runs a short set of suites
- `full-suite-authnz` which runs all the suites related to testing auth backends vs protocols
If you want to test your local changes, you can still build an image with these 2 commands from the
root folder of the `rabbitmq-server` repo:
```
@ -57,6 +64,12 @@ The last command prints something like this:
=> => naming to docker.io/pivotalrabbitmq/rabbitmq:3.11.0-rc.2.51.g4f3e539.dirty 0.0s
```
Or if you prefer to use bazel run instead:
```
bazelisk run packaging/docker-image:rabbitmq
```
To run a suite with a particular docker image you do it like this:
```
cd deps/rabbitmq_management/selenium
@ -78,22 +91,22 @@ For instance, say you want to run the test cases for the suite `suites/oauth-wit
First, open a terminal and launch RabbitMQ in the foreground:
```
suites/oauth-with-uaa.sh start-rabbitmq
suites/authnz-mgt/oauth-with-uaa.sh start-rabbitmq
```
Then, launch all the components, the suite depends on, in the background:
```
suites/oauth-with-uaa.sh start-others
suites/authnz-mgt/oauth-with-uaa.sh start-others
```
And finally, run all the test cases for the suite:
```
suites/oauth-with-uaa.sh test
suites/authnz-mgt/oauth-with-uaa.sh test
```
Or just one test case:
```
suites/oauth-with-uaa.sh test happy-login.js
suites/authnz-mgt/oauth-with-uaa.sh test happy-login.js
```
**NOTE**: Nowadays, it is not possible to run all test in interactive mode. It is doable but it has not
@ -131,7 +144,7 @@ automatically activated when running in interactive mode
The rest of the components the test cases depends on will typically run in docker such as uaa, keycloak, and the rest.
Besides these two profiles, mutually exclusive, you can have as many profiles as needed. It is just a matter of naming the appropriate file (.env, or rabbitmq.conf, etc) with the profile and activating the profile in the test suite script. For instance `suites/oauth-with-uaa.sh` activates two profiles by declaring them in `PROFILES` environment variable as shown below:
Besides these two profiles, mutually exclusive, you can have as many profiles as needed. It is just a matter of naming the appropriate file (.env, or rabbitmq.conf, etc) with the profile and activating the profile in the test suite script. For instance `suites/authnz-mgt/oauth-with-uaa.sh` activates two profiles by declaring them in `PROFILES` environment variable as shown below:
```
PROFILES="uaa uaa-oauth-provider"
```

View File

@ -0,0 +1,10 @@
These shell scripts are not meant to be executed directly. Instead they are
imported by bin/suite_template script.
Each component required to run a test, for instance, uaa or keycloak, has
its own script with its corresponding function:
start_<ComponentName>()
Although there is a convention to have two functions, the entrypoint `start_<ComponentName>()`,
and `init_<ComponentName>()`. The latter is called by the former to initialize
environment variables.

View File

@ -0,0 +1,44 @@
init_fakeportal() {
FAKEPORTAL_URL=${FAKEPORTAL_URL:-http://fakeportal:3000}
FAKEPORTAL_DIR=${SCRIPT}/../fakeportal
CLIENT_ID="${CLIENT_ID:-rabbit_idp_user}"
CLIENT_SECRET="${CLIENT_SECRET:-rabbit_idp_user}"
RABBITMQ_HOST=${RABBITMQ_HOST:-proxy:9090}
RABBITMQ_HOST_FOR_FAKEPORTAL=${RABBITMQ_HOST_FOR_FAKEPORTAL:-rabbitmq:15672}
RABBITMQ_URL=$(calculate_rabbitmq_url $RABBITMQ_HOST)
RABBITMQ_URL_FOR_FAKEPORTAL=$(calculate_rabbitmq_url $RABBITMQ_HOST_FOR_FAKEPORTAL)
print "> FAKEPORTAL_URL: ${FAKEPORTAL_URL}"
print "> UAA_URL_FOR_FAKEPORTAL: ${UAA_URL_FOR_FAKEPORTAL}"
print "> RABBITMQ_HOST_FOR_FAKEPORTAL: ${RABBITMQ_HOST_FOR_FAKEPORTAL}"
print "> RABBITMQ_HOST: ${RABBITMQ_HOST}"
print "> CLIENT_ID: ${CLIENT_ID}"
print "> CLIENT_SECRET: ${CLIENT_SECRET}"
print "> RABBITMQ_URL: ${RABBITMQ_URL}"
}
start_fakeportal() {
begin "Starting fakeportal ..."
init_fakeportal
kill_container_if_exist fakeportal
mocha_test_tag=($(md5sum $SELENIUM_ROOT_FOLDER/package.json))
docker run \
--detach \
--name fakeportal \
--net ${DOCKER_NETWORK} \
--publish 3000:3000 \
--env PORT=3000 \
--env RABBITMQ_URL="${RABBITMQ_URL_FOR_FAKEPORTAL}" \
--env PROXIED_RABBITMQ_URL="${RABBITMQ_URL}" \
--env UAA_URL="${UAA_URL_FOR_FAKEPORTAL}" \
--env CLIENT_ID="${CLIENT_ID}" \
--env CLIENT_SECRET="${CLIENT_SECRET}" \
-v ${FAKEPORTAL_DIR}:/code/fakeportal \
mocha-test:${mocha_test_tag} run fakeportal
wait_for_url $FAKEPORTAL_URL
end "Fakeportal is ready"
}

View File

@ -0,0 +1,44 @@
init_fakeproxy() {
FAKEPROXY_URL=${FAKEPROXY_URL:-http://fakeproxy:9090}
FAKEPROXY_DIR=${SCRIPT}/../fakeportal
CLIENT_ID="${CLIENT_ID:-rabbit_idp_user}"
CLIENT_SECRET="${CLIENT_SECRET:-rabbit_idp_user}"
RABBITMQ_HOST_FOR_FAKEPROXY=${RABBITMQ_HOST_FOR_FAKEPROXY:-rabbitmq:15672}
UAA_URL_FOR_FAKEPROXY=${UAA_URL_FOR_FAKEPROXY:-http://uaa:8080}
RABBITMQ_URL_FOR_FAKEPROXY=$(calculate_rabbitmq_url $RABBITMQ_HOST_FOR_FAKEPROXY)
print "> FAKEPROXY_URL: ${FAKEPROXY_URL}"
print "> UAA_URL: ${UAA_URL_FOR_FAKEPROXY}"
print "> RABBITMQ_HOST_FOR_FAKEPROXY: ${RABBITMQ_HOST_FOR_FAKEPROXY}"
print "> CLIENT_ID: ${CLIENT_ID}"
print "> CLIENT_SECRET: ${CLIENT_SECRET}"
print "> RABBITMQ_URL_FOR_FAKEPROXY: ${RABBITMQ_URL_FOR_FAKEPROXY}"
}
start_fakeproxy() {
begin "Starting fakeproxy ..."
init_fakeproxy
kill_container_if_exist fakeproxy
mocha_test_tag=($(md5sum $SELENIUM_ROOT_FOLDER/package.json))
docker run \
--detach \
--name fakeproxy \
--net ${DOCKER_NETWORK} \
--publish 9090:9090 \
--env PORT=9090 \
--env RABBITMQ_URL="${RABBITMQ_URL_FOR_FAKEPROXY}" \
--env UAA_URL="${UAA_URL_FOR_FAKEPROXY}" \
--env CLIENT_ID="${CLIENT_ID}" \
--env CLIENT_SECRET="${CLIENT_SECRET}" \
-v ${FAKEPROXY_DIR}:/code/fakeportal \
mocha-test:${mocha_test_tag} run fakeproxy
wait_for_url $FAKEPROXY_URL
end "fakeproxy is ready"
}

View File

@ -0,0 +1,42 @@
#!/usr/bin/env bash
KEYCLOAK_DOCKER_IMAGE=quay.io/keycloak/keycloak:20.0
init_keycloak() {
KEYCLOAK_CONFIG_PATH=${KEYCLOAK_CONFIG_PATH:-oauth/keycloak}
KEYCLOAK_CONFIG_DIR=$(realpath ${TEST_DIR}/${KEYCLOAK_CONFIG_PATH})
KEYCLOAK_URL=${OAUTH_PROVIDER_URL}
print "> KEYCLOAK_CONFIG_DIR: ${KEYCLOAK_CONFIG_DIR}"
print "> KEYCLOAK_URL: ${KEYCLOAK_URL}"
print "> KEYCLOAK_DOCKER_IMAGE: ${KEYCLOAK_DOCKER_IMAGE}"
}
start_keycloak() {
begin "Starting keycloak ..."
init_keycloak
kill_container_if_exist keycloak
MOUNT_KEYCLOAK_CONF_DIR=$CONF_DIR/keycloak
mkdir -p $MOUNT_KEYCLOAK_CONF_DIR
${BIN_DIR}/gen-keycloak-json ${KEYCLOAK_CONFIG_DIR} $ENV_FILE $MOUNT_KEYCLOAK_CONF_DIR/test-realm.json
print "> EFFECTIVE KEYCLOAK_CONFIG_FILE: $MOUNT_KEYCLOAK_CONF_DIR/test-realm.json"
cp ${KEYCLOAK_CONFIG_DIR}/*.pem $MOUNT_KEYCLOAK_CONF_DIR
docker run \
--detach \
--name keycloak \
--net ${DOCKER_NETWORK} \
--publish 8081:8080 \
--publish 8443:8443 \
--env KEYCLOAK_ADMIN=admin \
--env KEYCLOAK_ADMIN_PASSWORD=admin \
--mount type=bind,source=${MOUNT_KEYCLOAK_CONF_DIR},target=/opt/keycloak/data/import/ \
${KEYCLOAK_DOCKER_IMAGE} start-dev --import-realm \
--https-certificate-file=/opt/keycloak/data/import/server_keycloak_certificate.pem \
--https-certificate-key-file=/opt/keycloak/data/import/server_keycloak_key.pem
wait_for_oidc_endpoint keycloak $KEYCLOAK_URL $MOUNT_KEYCLOAK_CONF_DIR/ca_certificate.pem
end "Keycloak is ready"
}

View File

@ -0,0 +1,28 @@
init_mock-auth-backend-http() {
AUTH_BACKEND_HTTP_BASEURL=${AUTH_BACKEND_HTTP_BASEURL:-http://localhost:8888}
AUTH_BACKEND_HTTP_DIR=${TEST_CASES_DIR}/mock-auth-backend-http
print "> AUTH_BACKEND_HTTP_BASEURL: ${AUTH_BACKEND_HTTP_BASEURL}"
print "> AUTH_BACKEND_HTTP_DIR: ${AUTH_BACKEND_HTTP_DIR}"
}
start_mock-auth-backend-http() {
begin "Starting mock-auth-backend-http ..."
init_mock-auth-backend-http
kill_container_if_exist mock-auth-backend-http
docker run \
--detach \
--name mock-auth-backend-http \
--net ${DOCKER_NETWORK} \
--publish 8888:1080 \
--env MOCKSERVER_INITIALIZATION_JSON_PATH="/config/expectationInitialiser.json" \
-v ${AUTH_BACKEND_HTTP_DIR}:/config \
mockserver/mockserver
wait_for_url $AUTH_BACKEND_HTTP_BASEURL/ready
end "mock-auth-backend-http is ready"
}

View File

@ -0,0 +1,34 @@
init_mock-auth-backend-ldap() {
AUTH_BACKEND_LDAP_DIR=${TEST_CONFIG_DIR}/mock-auth-backend-ldap
print "> AUTH_BACKEND_LDAP_DIR: ${AUTH_BACKEND_LDAP_DIR}"
}
start_mock-auth-backend-ldap() {
begin "Starting mock-auth-backend-ldap ..."
init_mock-auth-backend-ldap
kill_container_if_exist mock-auth-backend-ldap
docker run \
--detach \
--name mock-auth-backend-ldap \
--net ${DOCKER_NETWORK} \
--env LDAP_ORGANISATION="Authentication and Tags" \
--env LDAP_DOMAIN="example.com" \
--env LDAP_ADMIN_PASSWORD="admin" \
--publish 389:389 \
--publish 636:636 \
-v ${AUTH_BACKEND_LDAP_DIR}:/config \
osixia/openldap:1.2.1
wait_for_message mock-auth-backend-ldap "starting"
docker exec mock-auth-backend-ldap ldapadd \
-x -w "admin" \
-H ldap:// \
-D "cn=admin,dc=example,dc=com" \
-f /config/import.ldif
end "mock-auth-backend-ldap is ready"
}

View File

@ -0,0 +1,37 @@
HTTPD_DOCKER_IMAGE=httpd:latest
init_proxy() {
HTTPD_CONFIG_DIR=${TEST_CONFIG_DIR}/httpd-proxy
PUBLIC_RABBITMQ_HOST=${PUBLIC_RABBITMQ_HOST:-proxy:9090}
PROXIED_RABBITMQ_URL=$(calculate_rabbitmq_url $PUBLIC_RABBITMQ_HOST)
print "> HTTPD_CONFIG: ${HTTPD_CONFIG_DIR}"
print "> PUBLIC_RABBITMQ_HOST: ${PUBLIC_RABBITMQ_HOST}"
print "> PROXIED_RABBITMQ_URL: ${PROXIED_RABBITMQ_URL}"
print "> RABBITMQ_HOST_FOR_PROXY: ${RABBITMQ_HOST_FOR_PROXY}"
print "> HTTPD_DOCKER_IMAGE: ${HTTPD_DOCKER_IMAGE}"
}
start_proxy() {
begin "Starting proxy ..."
init_proxy
kill_container_if_exist proxy
MOUNT_HTTPD_CONFIG_DIR=$CONF_DIR/httpd
mkdir -p $MOUNT_HTTPD_CONFIG_DIR
${BIN_DIR}/gen-httpd-conf ${HTTPD_CONFIG_DIR} $ENV_FILE $MOUNT_HTTPD_CONFIG_DIR/httpd.conf
print "> EFFECTIVE HTTPD_CONFIG_FILE: $MOUNT_HTTPD_CONFIG_DIR/httpd.conf"
docker run \
--detach \
--name proxy \
--net ${DOCKER_NETWORK} \
--publish 9090:9090 \
--mount "type=bind,source=${MOUNT_HTTPD_CONFIG_DIR},target=/usr/local/apache2/conf" \
${HTTPD_DOCKER_IMAGE}
wait_for_url $PROXIED_RABBITMQ_URL
end "Proxy is ready"
}

View File

@ -0,0 +1,86 @@
#!/usr/bin/env bash
init_rabbitmq() {
RABBITMQ_CONFIG_DIR=${TEST_CONFIG_DIR}
RABBITMQ_DOCKER_IMAGE=${RABBITMQ_DOCKER_IMAGE:-rabbitmq}
print "> RABBITMQ_CONFIG_DIR: ${RABBITMQ_CONFIG_DIR}"
print "> RABBITMQ_DOCKER_IMAGE: ${RABBITMQ_DOCKER_IMAGE}"
[[ -z "${OAUTH_SERVER_CONFIG_BASEDIR}" ]] || print "> OAUTH_SERVER_CONFIG_BASEDIR: ${OAUTH_SERVER_CONFIG_BASEDIR}"
[[ -z "${OAUTH_SERVER_CONFIG_DIR}" ]] || print "> OAUTH_SERVER_CONFIG_DIR: ${OAUTH_SERVER_CONFIG_DIR}"
}
start_rabbitmq() {
if [[ "$PROFILES" == *"docker"* ]]; then
start_docker_rabbitmq
else
start_local_rabbitmq
fi
}
start_local_rabbitmq() {
begin "Starting rabbitmq ..."
init_rabbitmq
RABBITMQ_SERVER_ROOT=$(realpath $TEST_DIR/../../../../)
MOUNT_RABBITMQ_CONF="/etc/rabbitmq/rabbitmq.conf"
MOUNT_ADVANCED_CONFIG="/etc/rabbitmq/advanced.config"
RABBITMQ_TEST_DIR="${RABBITMQ_CONFIG_DIR}"
${BIN_DIR}/gen-rabbitmq-conf ${RABBITMQ_CONFIG_DIR} $ENV_FILE /tmp$MOUNT_RABBITMQ_CONF
print "> EFFECTIVE RABBITMQ_CONFIG_FILE: /tmp$MOUNT_RABBITMQ_CONF"
${BIN_DIR}/gen-advanced-config ${RABBITMQ_CONFIG_DIR} $ENV_FILE /tmp$MOUNT_ADVANCED_CONFIG
RESULT=$?
if [ $RESULT -eq 0 ]; then
print "> EFFECTIVE RABBITMQ_CONFIG_FILE: /tmp$MOUNT_ADVANCED_CONFIG"
gmake --directory=${RABBITMQ_SERVER_ROOT} run-broker \
RABBITMQ_ENABLED_PLUGINS_FILE=${RABBITMQ_CONFIG_DIR}/enabled_plugins \
RABBITMQ_CONFIG_FILE=/tmp$MOUNT_RABBITMQ_CONF \
RABBITMQ_ADVANCED_CONFIG_FILE=/tmp$MOUNT_ADVANCED_CONFIG
else
gmake --directory=${RABBITMQ_SERVER_ROOT} run-broker \
RABBITMQ_ENABLED_PLUGINS_FILE=${RABBITMQ_CONFIG_DIR}/enabled_plugins \
RABBITMQ_CONFIG_FILE=/tmp$MOUNT_RABBITMQ_CONF
fi
print "> RABBITMQ_TEST_DIR: ${RABBITMQ_CONFIG_DIR}"
}
start_docker_rabbitmq() {
begin "Starting rabbitmq in docker ..."
init_rabbitmq
kill_container_if_exist rabbitmq
mkdir -p $CONF_DIR/rabbitmq
MOUNT_RABBITMQ_CONF="/etc/rabbitmq/rabbitmq.conf"
MOUNT_ADVANCED_CONFIG="/etc/rabbitmq/advanced.config"
RABBITMQ_TEST_DIR="/var/rabbitmq" ${BIN_DIR}/gen-rabbitmq-conf ${RABBITMQ_CONFIG_DIR} $ENV_FILE $CONF_DIR/rabbitmq/rabbitmq.conf
print "> EFFECTIVE RABBITMQ_CONFIG_FILE: $CONF_DIR/rabbitmq/rabbitmq.conf"
${BIN_DIR}/gen-advanced-config ${RABBITMQ_CONFIG_DIR} $ENV_FILE /$CONF_DIR/rabbitmq/advanced.config
RESULT=$?
if [ $RESULT -eq 0 ]; then
print "> EFFECTIVE ADVANCED_CONFIG_FILE: $CONF_DIR/rabbitmq/advanced.config"
EXTRA_MOUNTS="-v $CONF_DIR/rabbitmq/advanced.config:${MOUNT_ADVANCED_CONFIG}:ro"
fi
print "> RABBITMQ_TEST_DIR: /var/rabbitmq"
docker run \
--detach \
--name rabbitmq \
--net ${DOCKER_NETWORK} \
-p 15672:15672 -p 5672:5672 \
-v ${RABBITMQ_CONFIG_DIR}/logging.conf:/etc/rabbitmq/conf.d/logging.conf:ro \
-v $CONF_DIR/rabbitmq/rabbitmq.conf:${MOUNT_RABBITMQ_CONF}:ro \
-v ${RABBITMQ_CONFIG_DIR}/enabled_plugins:/etc/rabbitmq/enabled_plugins \
-v ${TEST_DIR}:/config \
-v ${RABBITMQ_CONFIG_DIR}/imports:/var/rabbitmq/imports \
${EXTRA_MOUNTS} \
${RABBITMQ_DOCKER_IMAGE}
wait_for_message rabbitmq "Server startup complete"
end "RabbitMQ ready"
}

View File

@ -0,0 +1,21 @@
#!/usr/bin/env bash
SELENIUM_DOCKER_IMAGE=selenium/standalone-chrome:103.0
start_selenium() {
begin "Starting selenium ..."
print "> SELENIUM_DOCKER_IMAGE: ${SELENIUM_DOCKER_IMAGE}"
kill_container_if_exist selenium
docker run \
--detach \
--name selenium \
--net ${DOCKER_NETWORK} \
-p 4444:4444 \
--shm-size=2g \
${SELENIUM_DOCKER_IMAGE}
wait_for_message selenium "Started Selenium Standalone"
end "Selenium ready"
}

View File

@ -0,0 +1,38 @@
#!/usr/bin/env bash
UAA_DOCKER_IMAGE=cloudfoundry/uaa:75.21.0
init_uaa() {
UAA_CONFIG_PATH=${UAA_CONFIG_PATH:-oauth/uaa}
UAA_CONFIG_DIR=$(realpath ${TEST_DIR}/${UAA_CONFIG_PATH})
print "> UAA_CONFIG_DIR: ${UAA_CONFIG_DIR}"
print "> UAA_URL: ${UAA_URL}"
print "> UAA_DOCKER_IMAGE: ${UAA_DOCKER_IMAGE}"
}
start_uaa() {
begin "Starting UAA ..."
init_uaa
kill_container_if_exist uaa
MOUNT_UAA_CONF_DIR=$CONF_DIR/uaa
mkdir -p $MOUNT_UAA_CONF_DIR
cp ${UAA_CONFIG_DIR}/* $MOUNT_UAA_CONF_DIR
${BIN_DIR}/gen-uaa-yml ${UAA_CONFIG_DIR} $ENV_FILE $MOUNT_UAA_CONF_DIR/uaa.yml
print "> EFFECTIVE UAA_CONFIG_FILE: $MOUNT_UAA_CONF_DIR/uaa.yml"
docker run \
--detach \
--name uaa \
--net ${DOCKER_NETWORK} \
--publish 8080:8080 \
--mount "type=bind,source=$MOUNT_UAA_CONF_DIR,target=/uaa" \
--env UAA_CONFIG_PATH="/uaa" \
--env JAVA_OPTS="-Djava.security.egd=file:/dev/./urandom" \
${UAA_DOCKER_IMAGE}
wait_for_oidc_endpoint uaa $UAA_URL
end "UAA is ready"
}

View File

@ -1,6 +1,8 @@
#!/usr/bin/env bash
SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
#set -x
ENV_FILE="/tmp/rabbitmq/.env"
FIND_PATH=$1
ENV_FILE=$2
@ -9,12 +11,40 @@ FIND_PARENT_PATH="$(dirname "$FIND_PATH")"
generate_env_file() {
parentdir="$(dirname "$ENV_FILE")"
mkdir -p $parentdir
echo "" > $ENV_FILE
echo "#!/usr/bin/env bash" > $ENV_FILE
echo "set -u" >> $ENV_FILE
for f in $($SCRIPT/find-template-files $FIND_PATH ".env")
declare -a FILE_ARRAY
for f in $($SCRIPT/find-template-files $FIND_PATH "env")
do
cat $f >> $ENV_FILE
FILE_ARRAY+=($f)
done
TMP_ENV_FILE="/tmp/env-tmp"
FILE_ARRAY_LENGTH=${#FILE_ARRAY[@]}
## Append each .env file one by one while all variables can be resolved
## if one variable cannot be resolve the temporary .env file fails
## and we add the last env file to end of the list and carry one with the next one
while [ $FILE_ARRAY_LENGTH -gt 0 ]
do
f="${FILE_ARRAY[0]}"
cp $ENV_FILE $TMP_ENV_FILE
cat $f >> $TMP_ENV_FILE
chmod u+x $TMP_ENV_FILE
$TMP_ENV_FILE 2> /dev/null
if [ $? -eq 0 ]
then
cat $f >> $ENV_FILE
else
FILE_ARRAY+=($f) # insert it to the end
fi
FILE_ARRAY=("${FILE_ARRAY[@]:1}") # remove the first element
FILE_ARRAY_LENGTH=${#FILE_ARRAY[@]}
done
rm -r $TMP_ENV_FILE
tail +3 $ENV_FILE > "$ENV_FILE.tmp" && mv "$ENV_FILE.tmp" $ENV_FILE
}
generate_env_file

View File

@ -1,14 +1,14 @@
#!/usr/bin/env bash
#set -x
SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
SUITE=$(caller)
SUITE=$(basename "${SUITE}" .sh )
SELENIUM_DOCKER_IMAGE=selenium/standalone-chrome:103.0
UAA_DOCKER_IMAGE=cloudfoundry/uaa:75.21.0
KEYCLOAK_DOCKER_IMAGE=quay.io/keycloak/keycloak:20.0
HTTPD_DOCKER_IMAGE=httpd:latest
PADDING=""
tabs 1
declare -i PADDING_LEVEL=0
declare -i STEP=1
declare -a REQUIRED_COMPONENTS
find_selenium_dir() {
@ -32,6 +32,13 @@ SCREENS=${SELENIUM_ROOT_FOLDER}/screens/${SUITE}
CONF_DIR=/tmp/selenium/${SUITE}
ENV_FILE=$CONF_DIR/.env
for f in $SCRIPT/components/*; do
if [[ ! "$f" == *README.md ]]
then
source $f;
fi
done
parse_arguments() {
if [[ "$#" -gt 0 ]]
then
@ -55,18 +62,26 @@ parse_arguments() {
COMMAND=$(parse_arguments $@)
tabs 4
print() {
echo -e "${PADDING}$1"
tabbing=""
if [[ $PADDING_LEVEL -gt 0 ]]; then
for i in $(seq $PADDING_LEVEL); do
tabbing="$tabbing\t"
done
fi
echo -e "$tabbing$1"
}
begin() {
print "\n$@"
PADDING="${PADDING}\t"
print "\n[$STEP] $@"
PADDING_LEVEL=$(($PADDING_LEVEL + 1))
STEP=$(($STEP + 1))
}
end() {
PADDING=`echo $PADDING | rev | cut -c 4- | rev`
PADDING_LEVEL=$(($PADDING_LEVEL - 1))
print "$@"
}
ensure_docker_network() {
@ -92,39 +107,27 @@ init_suite() {
print "> PROFILES: ${PROFILES} "
print "> ENV_FILE: ${ENV_FILE} "
print "> COMMAND: ${COMMAND}"
end "Initialized suite ..."
end "Initialized suite"
mkdir -p ${LOGS}/${SUITE}
mkdir -p ${SCREENS}/${SUITE}
}
build_mocha_image() {
begin "Ensuring mocha-test image ..."
tag=($(md5sum $SELENIUM_ROOT_FOLDER/package.json))
if [[ "$(docker images -q mocha-test:$tag 2> /dev/null)" == "" ]]; then
print "> tag : $tag"
if [[ $(docker images -q mocha-test:$tag 2> /dev/null) == "" ]]; then
docker build -t mocha-test:$tag --target test $SCRIPT/..
print "> Built docker image mocha-test:$tag"
fi
end "mocha-test image exists"
}
start_selenium() {
begin "Starting selenium ..."
print "> SELENIUM_DOCKER_IMAGE: ${SELENIUM_DOCKER_IMAGE}"
kill_container_if_exist selenium
docker run \
--detach \
--name selenium \
--net ${DOCKER_NETWORK} \
-p 4444:4444 \
--shm-size=2g \
${SELENIUM_DOCKER_IMAGE}
wait_for_message selenium "Started Selenium Standalone"
end "Selenium ready"
}
kill_container_if_exist() {
docker stop $1 &> /dev/null || true && docker rm $1 &> /dev/null || true
if docker stop $1 &> /dev/null; then
docker rm $1 &> /dev/null
fi
}
wait_for_message() {
attemps_left=10
@ -135,185 +138,36 @@ wait_for_message() {
((attemps_left--))
if [[ "$attemps_left" -lt 1 ]]; then
print "Timed out waiting"
save_container_log $1
exit 1
fi
done
}
init_rabbitmq() {
RABBITMQ_CONFIG_DIR=${TEST_CONFIG_DIR}
RABBITMQ_DOCKER_IMAGE=${RABBITMQ_DOCKER_IMAGE:-rabbitmq}
print "> RABBITMQ_CONFIG_DIR: ${RABBITMQ_CONFIG_DIR}"
print "> RABBITMQ_DOCKER_IMAGE: ${RABBITMQ_DOCKER_IMAGE}"
[[ -z "${OAUTH_SIGNING_KEY_PATH}" ]] || print "> OAUTH_SIGNING_KEY_PATH: ${OAUTH_SIGNING_KEY_PATH}"
}
start_rabbitmq() {
if [[ "$PROFILES" == *"docker"* ]]; then
start_docker_rabbitmq
else
start_local_rabbitmq
fi
}
start_local_rabbitmq() {
begin "Starting rabbitmq ..."
init_rabbitmq
RABBITMQ_SERVER_ROOT=$(realpath $TEST_DIR/../../../../)
MOUNT_RABBITMQ_CONF="/etc/rabbitmq/rabbitmq.conf"
MOUNT_ADVANCED_CONFIG="/etc/rabbitmq/advanced.config"
RABBITMQ_TEST_DIR="${RABBITMQ_CONFIG_DIR}"
${BIN_DIR}/gen-rabbitmq-conf ${RABBITMQ_CONFIG_DIR} $ENV_FILE /tmp$MOUNT_RABBITMQ_CONF
print "> EFFECTIVE RABBITMQ_CONFIG_FILE: /tmp$MOUNT_RABBITMQ_CONF"
${BIN_DIR}/gen-advanced-config ${RABBITMQ_CONFIG_DIR} $ENV_FILE /tmp$MOUNT_ADVANCED_CONFIG
RESULT=$?
if [ $RESULT -eq 0 ]; then
print "> EFFECTIVE RABBITMQ_CONFIG_FILE: /tmp$MOUNT_ADVANCED_CONFIG"
gmake --directory=${RABBITMQ_SERVER_ROOT} run-broker \
RABBITMQ_ENABLED_PLUGINS_FILE=${RABBITMQ_CONFIG_DIR}/enabled_plugins \
RABBITMQ_CONFIG_FILE=/tmp$MOUNT_RABBITMQ_CONF \
RABBITMQ_ADVANCED_CONFIG_FILE=/tmp$MOUNT_ADVANCED_CONFIG
else
gmake --directory=${RABBITMQ_SERVER_ROOT} run-broker \
RABBITMQ_ENABLED_PLUGINS_FILE=${RABBITMQ_CONFIG_DIR}/enabled_plugins \
RABBITMQ_CONFIG_FILE=/tmp$MOUNT_RABBITMQ_CONF
fi
print "> RABBITMQ_TEST_DIR: ${RABBITMQ_CONFIG_DIR}"
}
start_docker_rabbitmq() {
begin "Starting rabbitmq in docker ..."
init_rabbitmq
kill_container_if_exist rabbitmq
mkdir -p $CONF_DIR/rabbitmq
MOUNT_RABBITMQ_CONF="/etc/rabbitmq/rabbitmq.conf"
MOUNT_ADVANCED_CONFIG="/etc/rabbitmq/advanced.config"
RABBITMQ_TEST_DIR="/var/rabbitmq" ${BIN_DIR}/gen-rabbitmq-conf ${RABBITMQ_CONFIG_DIR} $ENV_FILE $CONF_DIR/rabbitmq/rabbitmq.conf
print "> EFFECTIVE RABBITMQ_CONFIG_FILE: $CONF_DIR/rabbitmq/rabbitmq.conf"
${BIN_DIR}/gen-advanced-config ${RABBITMQ_CONFIG_DIR} $ENV_FILE /$CONF_DIR/rabbitmq/advanced.config
RESULT=$?
if [ $RESULT -eq 0 ]; then
print "> EFFECTIVE ADVANCED_CONFIG_FILE: $CONF_DIR/rabbitmq/advanced.config"
EXTRA_MOUNTS="-v $CONF_DIR/rabbitmq/advanced.config:${MOUNT_ADVANCED_CONFIG}:ro"
fi
print "> RABBITMQ_TEST_DIR: /var/rabbitmq"
docker run \
--detach \
--name rabbitmq \
--net ${DOCKER_NETWORK} \
-p 15672:15672 -p 5672:5672 \
-v ${RABBITMQ_CONFIG_DIR}/logging.conf:/etc/rabbitmq/conf.d/logging.conf:ro \
-v $CONF_DIR/rabbitmq/rabbitmq.conf:${MOUNT_RABBITMQ_CONF}:ro \
-v ${RABBITMQ_CONFIG_DIR}/enabled_plugins:/etc/rabbitmq/enabled_plugins \
-v ${TEST_DIR}/${OAUTH_SIGNING_KEY_PATH}:/config \
-v ${RABBITMQ_CONFIG_DIR}/imports:/var/rabbitmq/imports \
${EXTRA_MOUNTS} \
${RABBITMQ_DOCKER_IMAGE}
wait_for_message rabbitmq "Server startup complete"
end "RabbitMQ ready"
}
init_uaa() {
UAA_CONFIG_PATH=${UAA_CONFIG_PATH:-oauth/uaa}
UAA_CONFIG_DIR=$(realpath ${TEST_DIR}/${UAA_CONFIG_PATH})
print "> UAA_CONFIG_DIR: ${UAA_CONFIG_DIR}"
print "> UAA_URL: ${UAA_URL}"
print "> UAA_DOCKER_IMAGE: ${UAA_DOCKER_IMAGE}"
}
start_uaa() {
begin "Starting UAA ..."
init_uaa
kill_container_if_exist uaa
MOUNT_UAA_CONF_DIR=$CONF_DIR/uaa
mkdir -p $MOUNT_UAA_CONF_DIR
cp ${UAA_CONFIG_DIR}/* $MOUNT_UAA_CONF_DIR
${BIN_DIR}/gen-uaa-yml ${UAA_CONFIG_DIR} $ENV_FILE $MOUNT_UAA_CONF_DIR/uaa.yml
print "> EFFECTIVE UAA_CONFIG_FILE: $MOUNT_UAA_CONF_DIR/uaa.yml"
docker run \
--detach \
--name uaa \
--net ${DOCKER_NETWORK} \
--publish 8080:8080 \
--mount "type=bind,source=$MOUNT_UAA_CONF_DIR,target=/uaa" \
--env UAA_CONFIG_PATH="/uaa" \
--env JAVA_OPTS="-Djava.security.egd=file:/dev/./urandom" \
${UAA_DOCKER_IMAGE}
wait_for_oidc_endpoint uaa $UAA_URL
end "UAA is ready"
}
init_keycloak() {
KEYCLOAK_CONFIG_PATH=${KEYCLOAK_CONFIG_PATH:-oauth/keycloak}
KEYCLOAK_CONFIG_DIR=$(realpath ${TEST_DIR}/${KEYCLOAK_CONFIG_PATH})
KEYCLOAK_URL=${OAUTH_PROVIDER_URL}
print "> KEYCLOAK_CONFIG_DIR: ${KEYCLOAK_CONFIG_DIR}"
print "> KEYCLOAK_URL: ${KEYCLOAK_URL}"
print "> KEYCLOAK_DOCKER_IMAGE: ${KEYCLOAK_DOCKER_IMAGE}"
}
start_keycloak() {
begin "Starting keycloak ..."
init_keycloak
kill_container_if_exist keycloak
MOUNT_KEYCLOAK_CONF_DIR=$CONF_DIR/keycloak
mkdir -p $MOUNT_KEYCLOAK_CONF_DIR
${BIN_DIR}/gen-keycloak-json ${KEYCLOAK_CONFIG_DIR} $ENV_FILE $MOUNT_KEYCLOAK_CONF_DIR/test-realm.json
print "> EFFECTIVE KEYCLOAK_CONFIG_FILE: $MOUNT_KEYCLOAK_CONF_DIR/test-realm.json"
cp ${KEYCLOAK_CONFIG_DIR}/*.pem $MOUNT_KEYCLOAK_CONF_DIR
docker run \
--detach \
--name keycloak \
--net ${DOCKER_NETWORK} \
--publish 8080:8080 \
--publish 8443:8443 \
--env KEYCLOAK_ADMIN=admin \
--env KEYCLOAK_ADMIN_PASSWORD=admin \
--mount type=bind,source=${MOUNT_KEYCLOAK_CONF_DIR},target=/opt/keycloak/data/import/ \
${KEYCLOAK_DOCKER_IMAGE} start-dev --import-realm \
--https-certificate-file=/opt/keycloak/data/import/server_localhost_certificate.pem \
--https-certificate-key-file=/opt/keycloak/data/import/server_localhost_key.pem
wait_for_oidc_endpoint keycloak $KEYCLOAK_URL
end "Keycloak is ready"
}
wait_for_oidc_endpoint() {
NAME=$1
BASE_URL=$2
if [[ $BASE_URL == *"localhost"** ]]; then
wait_for_oidc_endpoint_local $NAME $BASE_URL
if [[ $BASE_URL == *"localhost"** || $BASE_URL == *"0.0.0.0"** ]]; then
wait_for_oidc_endpoint_local $@
else
wait_for_oidc_endpoint_docker $NAME $BASE_URL
wait_for_oidc_endpoint_docker $@
fi
}
wait_for_oidc_endpoint_local() {
NAME=$1
BASE_URL=$2
CURL_ARGS="-L --fail "
DELAY_BETWEEN_ATTEMPTS=5
if [[ $# -eq 3 ]]; then
CURL_ARGS="$CURL_ARGS --cacert $3"
DELAY_BETWEEN_ATTEMPTS=10
fi
max_retry=10
counter=0
print "Waiting for OIDC discovery endpoint $NAME ... (BASE_URL: $BASE_URL)"
until (curl -L --fail ${BASE_URL}/.well-known/openid-configuration >/dev/null 2>&1)
until (curl $CURL_ARGS ${BASE_URL}/.well-known/openid-configuration >/dev/null 2>&1)
do
sleep 5
sleep $DELAY_BETWEEN_ATTEMPTS
[[ counter -eq $max_retry ]] && print "Failed!" && exit 1
print "Trying again. Try #$counter"
((counter++))
@ -323,13 +177,20 @@ wait_for_oidc_endpoint_local() {
wait_for_oidc_endpoint_docker() {
NAME=$1
BASE_URL=$2
CURL_ARGS="-L --fail "
DOCKER_ARGS="--rm --net ${DOCKER_NETWORK} "
DELAY_BETWEEN_ATTEMPTS=5
if [[ $# -gt 2 ]]; then
DOCKER_ARGS="$DOCKER_ARGS -v $3:/tmp/ca_certificate.pem"
CURL_ARGS="$CURL_ARGS --cacert /tmp/ca_certificate.pem"
DELAY_BETWEEN_ATTEMPTS=10
fi
max_retry=10
counter=0
print "Waiting for OIDC discovery endpoint $NAME ... (BASE_URL: $BASE_URL)"
until (docker run --net ${DOCKER_NETWORK} --rm curlimages/curl:7.85.0 -L --fail ${BASE_URL}/.well-known/openid-configuration >/dev/null 2>&1)
until (docker run $DOCKER_ARGS curlimages/curl:7.85.0 $CURL_ARGS ${BASE_URL}/.well-known/openid-configuration >/dev/null 2>&1)
do
sleep 5
sleep $DELAY_BETWEEN_ATTEMPTS
[[ counter -eq $max_retry ]] && print "Failed!" && exit 1
print "Trying again. Try #$counter"
((counter++))
@ -339,156 +200,6 @@ wait_for_oidc_endpoint_docker() {
calculate_rabbitmq_url() {
echo "${RABBITMQ_SCHEME:-http}://$1${PUBLIC_RABBITMQ_PATH:-$RABBITMQ_PATH}"
}
init_fakeportal() {
FAKEPORTAL_URL=${FAKEPORTAL_URL:-http://fakeportal:3000}
FAKEPORTAL_DIR=${SCRIPT}/../fakeportal
CLIENT_ID="${CLIENT_ID:-rabbit_idp_user}"
CLIENT_SECRET="${CLIENT_SECRET:-rabbit_idp_user}"
RABBITMQ_HOST=${RABBITMQ_HOST:-proxy:9090}
RABBITMQ_HOST_FOR_FAKEPORTAL=${RABBITMQ_HOST_FOR_FAKEPORTAL:-rabbitmq:15672}
RABBITMQ_URL=$(calculate_rabbitmq_url $RABBITMQ_HOST)
RABBITMQ_URL_FOR_FAKEPORTAL=$(calculate_rabbitmq_url $RABBITMQ_HOST_FOR_FAKEPORTAL)
print "> FAKEPORTAL_URL: ${FAKEPORTAL_URL}"
print "> UAA_URL_FOR_FAKEPORTAL: ${UAA_URL_FOR_FAKEPORTAL}"
print "> RABBITMQ_HOST_FOR_FAKEPORTAL: ${RABBITMQ_HOST_FOR_FAKEPORTAL}"
print "> RABBITMQ_HOST: ${RABBITMQ_HOST}"
print "> CLIENT_ID: ${CLIENT_ID}"
print "> CLIENT_SECRET: ${CLIENT_SECRET}"
print "> RABBITMQ_URL: ${RABBITMQ_URL}"
}
start_fakeportal() {
begin "Starting fakeportal ..."
init_fakeportal
kill_container_if_exist fakeportal
mocha_test_tag=($(md5sum $SELENIUM_ROOT_FOLDER/package.json))
docker run \
--detach \
--name fakeportal \
--net ${DOCKER_NETWORK} \
--publish 3000:3000 \
--env PORT=3000 \
--env RABBITMQ_URL="${RABBITMQ_URL_FOR_FAKEPORTAL}" \
--env PROXIED_RABBITMQ_URL="${RABBITMQ_URL}" \
--env UAA_URL="${UAA_URL_FOR_FAKEPORTAL}" \
--env CLIENT_ID="${CLIENT_ID}" \
--env CLIENT_SECRET="${CLIENT_SECRET}" \
-v ${FAKEPORTAL_DIR}:/code/fakeportal \
mocha-test:${mocha_test_tag} run fakeportal
wait_for_url $FAKEPORTAL_URL
end "Fakeportal is ready"
}
init_fakeproxy() {
FAKEPROXY_URL=${FAKEPROXY_URL:-http://fakeproxy:9090}
FAKEPROXY_DIR=${SCRIPT}/../fakeportal
CLIENT_ID="${CLIENT_ID:-rabbit_idp_user}"
CLIENT_SECRET="${CLIENT_SECRET:-rabbit_idp_user}"
RABBITMQ_HOST_FOR_FAKEPROXY=${RABBITMQ_HOST_FOR_FAKEPROXY:-rabbitmq:15672}
UAA_URL_FOR_FAKEPROXY=${UAA_URL_FOR_FAKEPROXY:-http://uaa:8080}
RABBITMQ_URL_FOR_FAKEPROXY=$(calculate_rabbitmq_url $RABBITMQ_HOST_FOR_FAKEPROXY)
print "> FAKEPROXY_URL: ${FAKEPROXY_URL}"
print "> UAA_URL: ${UAA_URL_FOR_FAKEPROXY}"
print "> RABBITMQ_HOST_FOR_FAKEPROXY: ${RABBITMQ_HOST_FOR_FAKEPROXY}"
print "> CLIENT_ID: ${CLIENT_ID}"
print "> CLIENT_SECRET: ${CLIENT_SECRET}"
print "> RABBITMQ_URL_FOR_FAKEPROXY: ${RABBITMQ_URL_FOR_FAKEPROXY}"
}
start_fakeproxy() {
begin "Starting fakeproxy ..."
init_fakeproxy
kill_container_if_exist fakeproxy
mocha_test_tag=($(md5sum $SELENIUM_ROOT_FOLDER/package.json))
docker run \
--detach \
--name fakeproxy \
--net ${DOCKER_NETWORK} \
--publish 9090:9090 \
--env PORT=9090 \
--env RABBITMQ_URL="${RABBITMQ_URL_FOR_FAKEPROXY}" \
--env UAA_URL="${UAA_URL_FOR_FAKEPROXY}" \
--env CLIENT_ID="${CLIENT_ID}" \
--env CLIENT_SECRET="${CLIENT_SECRET}" \
-v ${FAKEPROXY_DIR}:/code/fakeportal \
mocha-test:${mocha_test_tag} run fakeproxy
wait_for_url $FAKEPROXY_URL
end "fakeproxy is ready"
}
init_mock-auth-backend-http() {
AUTH_BACKEND_HTTP_BASEURL=${AUTH_BACKEND_HTTP_BASEURL:-http://localhost:8888}
AUTH_BACKEND_HTTP_DIR=${TEST_CASES_DIR}/mock-auth-backend-http
AUTH_BACKEND_HTTP_EXPECTATIONS=defaultExpectations.json
print "> AUTH_BACKEND_HTTP_BASEURL: ${AUTH_BACKEND_HTTP_BASEURL}"
print "> AUTH_BACKEND_HTTP_DIR: ${AUTH_BACKEND_HTTP_DIR}"
print "> AUTH_BACKEND_HTTP_EXPECTATIONS: ${AUTH_BACKEND_HTTP_EXPECTATIONS}"
}
start_mock-auth-backend-http() {
begin "Starting mock-auth-backend-http ..."
init_mock-auth-backend-http
kill_container_if_exist mock-auth-backend-http
# --env MOCKSERVER_INITIALIZATION_JSON_PATH="/config/$AUTH_BACKEND_HTTP_EXPECTATIONS" \
docker run \
--detach \
--name mock-auth-backend-http \
--net ${DOCKER_NETWORK} \
--publish 8888:1080 \
-v ${AUTH_BACKEND_HTTP_DIR}:/config \
mockserver/mockserver
#wait_for_url $AUTH_BACKEND_HTTP_BASEURL/ready
wait_for_message mock-auth-backend-http "started on port"
end "mock-auth-backend-http is ready"
}
init_mock-auth-backend-ldap() {
AUTH_BACKEND_LDAP_DIR=${TEST_CONFIG_DIR}/mock-auth-backend-ldap
print "> AUTH_BACKEND_LDAP_DIR: ${AUTH_BACKEND_LDAP_DIR}"
}
start_mock-auth-backend-ldap() {
begin "Starting mock-auth-backend-ldap ..."
init_mock-auth-backend-ldap
kill_container_if_exist mock-auth-backend-ldap
docker run \
--detach \
--name mock-auth-backend-ldap \
--net ${DOCKER_NETWORK} \
--env LDAP_ORGANISATION="Authentication and Tags" \
--env LDAP_DOMAIN="example.com" \
--env LDAP_ADMIN_PASSWORD="admin" \
--publish 389:389 \
--publish 636:636 \
-v ${AUTH_BACKEND_LDAP_DIR}:/config \
osixia/openldap:1.2.1
wait_for_message mock-auth-backend-ldap "starting"
docker exec mock-auth-backend-ldap ldapadd \
-x -w "admin" \
-H ldap:// \
-D "cn=admin,dc=example,dc=com" \
-f /config/import.ldif
end "mock-auth-backend-ldap is ready"
}
wait_for_url() {
BASE_URL=$1
@ -525,42 +236,6 @@ wait_for_url_docker() {
done
}
init_proxy() {
HTTPD_CONFIG_DIR=${TEST_CONFIG_DIR}/httpd-proxy
PUBLIC_RABBITMQ_HOST=${PUBLIC_RABBITMQ_HOST:-proxy:9090}
PROXIED_RABBITMQ_URL=$(calculate_rabbitmq_url $PUBLIC_RABBITMQ_HOST)
print "> HTTPD_CONFIG: ${HTTPD_CONFIG_DIR}"
print "> PUBLIC_RABBITMQ_HOST: ${PUBLIC_RABBITMQ_HOST}"
print "> PROXIED_RABBITMQ_URL: ${PROXIED_RABBITMQ_URL}"
print "> RABBITMQ_HOST_FOR_PROXY: ${RABBITMQ_HOST_FOR_PROXY}"
print "> HTTPD_DOCKER_IMAGE: ${HTTPD_DOCKER_IMAGE}"
}
start_proxy() {
begin "Starting proxy ..."
init_proxy
kill_container_if_exist proxy
MOUNT_HTTPD_CONFIG_DIR=$CONF_DIR/httpd
mkdir -p $MOUNT_HTTPD_CONFIG_DIR
${BIN_DIR}/gen-httpd-conf ${HTTPD_CONFIG_DIR} $ENV_FILE $MOUNT_HTTPD_CONFIG_DIR/httpd.conf
print "> EFFECTIVE HTTPD_CONFIG_FILE: $MOUNT_HTTPD_CONFIG_DIR/httpd.conf"
docker run \
--detach \
--name proxy \
--net ${DOCKER_NETWORK} \
--publish 9090:9090 \
--mount "type=bind,source=${MOUNT_HTTPD_CONFIG_DIR},target=/usr/local/apache2/conf" \
${HTTPD_DOCKER_IMAGE}
wait_for_url $PROXIED_RABBITMQ_URL
end "Proxy is ready"
}
test() {
kill_container_if_exist mocha
begin "Running tests with env variables:"
@ -569,7 +244,11 @@ test() {
PUBLIC_RABBITMQ_HOST=${PUBLIC_RABBITMQ_HOST:-$RABBITMQ_HOST}
RABBITMQ_URL=$(calculate_rabbitmq_url $PUBLIC_RABBITMQ_HOST)
RABBITMQ_HOSTNAME=${RABBITMQ_HOSTNAME:-rabbitmq}
SELENIUM_TIMEOUT=${SELENIUM_TIMEOUT:-20000}
SELENIUM_POLLING=${SELENIUM_POLLING:-500}
print "> SELENIUM_TIMEOUT: ${SELENIUM_TIMEOUT}"
print "> SELENIUM_POLLING: ${SELENIUM_POLLING}"
print "> RABBITMQ_HOST: ${RABBITMQ_HOST}"
print "> RABBITMQ_HOSTNAME: ${RABBITMQ_HOSTNAME}"
print "> PUBLIC_RABBITMQ_HOST: ${PUBLIC_RABBITMQ_HOST}"
@ -588,6 +267,8 @@ test() {
--env UAA_URL=${UAA_URL} \
--env FAKE_PORTAL_URL=${FAKEPORTAL_URL} \
--env RUN_LOCAL=false \
--env SELENIUM_TIMEOUT=${SELENIUM_TIMEOUT} \
--env SELENIUM_POLLING=${SELENIUM_POLLING} \
--env PROFILES="${PROFILES}" \
--env ENV_FILE="/code/.env" \
-v ${TEST_DIR}:/code/test \
@ -605,9 +286,17 @@ save_logs() {
save_container_logs selenium
}
save_container_logs() {
docker container ls | grep $1 >/dev/null 2>&1 && docker logs $1 &> $LOGS/$1.log || echo "$1 not running"
echo "Saving logs for $1"
if docker container ls | grep $1 >/dev/null 2>&1; then
docker logs $1 &> $LOGS/$1.log
else
echo "$1 not running"
fi
}
save_container_log() {
echo "Saving container $1 logs to $LOGS/$1.log ..."
docker logs $1 &> $LOGS/$1.log
}
profiles_with_local_or_docker() {
if [[ "$PROFILES" != *"local"* && "$PROFILES" != *"docker"* ]]; then
echo "$PROFILES docker"
@ -616,9 +305,11 @@ profiles_with_local_or_docker() {
fi
}
generate_env_file() {
begin "Generating env file ..."
mkdir -p $CONF_DIR
${BIN_DIR}/gen-env-file $TEST_CONFIG_DIR $ENV_FILE
source $ENV_FILE
end "Finished generating env file."
}
run() {
runWith rabbitmq
@ -640,6 +331,7 @@ run_local_with() {
generate_env_file
build_mocha_image
if [[ "$COMMAND" == "start-rabbitmq" ]]
then
start_local_rabbitmq
@ -707,7 +399,7 @@ teardown_local_others() {
fi
}
test_local() {
begin "Running local test $1"
begin "Running local test ${1:-}"
RABBITMQ_HOST=${RABBITMQ_HOST:-rabbitmq:15672}
PUBLIC_RABBITMQ_HOST=${PUBLIC_RABBITMQ_HOST:-$RABBITMQ_HOST}
@ -715,7 +407,11 @@ test_local() {
export RABBITMQ_HOSTNAME=${RABBITMQ_HOSTNAME:-rabbitmq}
export RABBITMQ_AMQP_USERNAME=${RABBITMQ_AMQP_USERNAME}
export RABBITMQ_AMQP_PASSWORD=${RABBITMQ_AMQP_PASSWORD}
export SELENIUM_TIMEOUT=${SELENIUM_TIMEOUT:-20000}
export SELENIUM_POLLING=${SELENIUM_POLLING:-500}
print "> SELENIUM_TIMEOUT: ${SELENIUM_TIMEOUT}"
print "> SELENIUM_POLLING: ${SELENIUM_POLLING}"
print "> RABBITMQ_HOST: ${RABBITMQ_HOST}"
print "> RABBITMQ_HOSTNAME: ${RABBITMQ_HOSTNAME}"
print "> PUBLIC_RABBITMQ_HOST: ${PUBLIC_RABBITMQ_HOST}"
@ -727,6 +423,7 @@ test_local() {
export RUN_LOCAL=true
export SCREENSHOTS_DIR=${SCREENS}
export PROFILES
export ENV_FILE
npm test $TEST_CASES_DIR/$1
@ -743,17 +440,19 @@ teardown_components() {
begin "Tear down ..."
for i in "${REQUIRED_COMPONENTS[@]}"
do
print "Tear down $i"
$(kill_container_if_exist $i)
local component="$i"
print "Tear down $component"
kill_container_if_exist "$component"
done
end "Finished teardown"
}
save_components_logs() {
begin "Saving Logs to $LOGS ..."
begin "Saving Logs to $LOGS for ${REQUIRED_COMPONENTS[@]} ..."
for i in "${REQUIRED_COMPONENTS[@]}"
do
print "Saving logs for $i"
$(save_container_logs $i)
local component="$i"
print "Saving logs for component $component"
save_container_logs "$component"
done
end "Finished saving logs"
}

View File

@ -4,6 +4,7 @@
"description": "",
"main": "index.js",
"scripts": {
"test": "mocha --recursive --trace-warnings --timeout 45000",
"fakeportal": "node fakeportal/app.js",
"fakeproxy": "node fakeportal/proxy.js",
"amqp10_roundtriptest": "eval $(cat $ENV_FILE ) &&./run-amqp10-roundtriptest",
@ -13,7 +14,7 @@
"author": "",
"license": "ISC",
"dependencies": {
"chromedriver": "^118.0.0",
"chromedriver": "^119.0.0",
"ejs": "^3.1.8",
"express": "^4.18.2",
"geckodriver": "^3.0.2",
@ -21,7 +22,7 @@
"mqtt": "^5.3.3",
"path": "^0.12.7",
"proxy": "^1.0.2",
"selenium-webdriver": "^4.4.0",
"selenium-webdriver": "^4.15.0",
"xmlhttprequest": "^1.8.0"
},
"devDependencies": {

View File

@ -3,7 +3,7 @@
SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
TEST_CASES_PATH=/authnz-msg-protocols
PROFILES="http-user auth-http auth_backends-internal-http "
PROFILES="http-user auth-http auth_backends-http-internal "
source $SCRIPT/../../bin/suite_template
runWith mock-auth-backend-http

View File

@ -0,0 +1,10 @@
#!/usr/bin/env bash
SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
TEST_CASES_PATH=/oauth/with-basic-auth
TEST_CONFIG_PATH=/oauth
PROFILES="keycloak jwks keycloak-oauth-provider enable-basic-auth"
source $SCRIPT/../../bin/suite_template $@
runWith keycloak

View File

@ -4,7 +4,7 @@ SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
TEST_CASES_PATH=/oauth/with-idp-initiated-via-proxy
TEST_CONFIG_PATH=/oauth
PROFILES="uaa fakeportal fakeproxy fakeportal-oauth-provider idp-initiated mgt-prefix"
PROFILES="uaa fakeportal fakeproxy fakeportal-mgt-oauth-provider idp-initiated mgt-prefix uaa-oauth-provider"
source $SCRIPT/../../bin/suite_template $@
runWith uaa fakeportal fakeproxy

View File

@ -4,7 +4,7 @@ SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
TEST_CASES_PATH=/oauth/with-idp-initiated
TEST_CONFIG_PATH=/oauth
PROFILES="uaa fakeportal-oauth-provider idp-initiated mgt-prefix"
PROFILES="uaa fakeportal-mgt-oauth-provider idp-initiated mgt-prefix uaa-oauth-provider"
source $SCRIPT/../../bin/suite_template $@
runWith uaa fakeportal

View File

@ -4,7 +4,7 @@ SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
TEST_CASES_PATH=/oauth/with-idp-initiated-via-proxy
TEST_CONFIG_PATH=/oauth
PROFILES="uaa fakeportal fakeproxy fakeportal-oauth-provider idp-initiated"
PROFILES="uaa fakeportal fakeproxy fakeportal-mgt-oauth-provider idp-initiated uaa-oauth-provider"
source $SCRIPT/../../bin/suite_template $@
runWith uaa fakeportal fakeproxy

View File

@ -4,7 +4,7 @@ SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
TEST_CASES_PATH=/oauth/with-idp-initiated
TEST_CONFIG_PATH=/oauth
PROFILES="uaa fakeportal-oauth-provider idp-initiated"
PROFILES="uaa idp-initiated uaa-oauth-provider fakeportal-mgt-oauth-provider"
source $SCRIPT/../../bin/suite_template $@
runWith uaa fakeportal

View File

@ -4,7 +4,7 @@ SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
TEST_CASES_PATH=/oauth/with-sp-initiated
TEST_CONFIG_PATH=/oauth
PROFILES="keycloak jwks keycloak-oauth-provider"
PROFILES="keycloak keycloak-oauth-provider keycloak-mgt-oauth-provider "
source $SCRIPT/../../bin/suite_template $@
runWith keycloak

View File

@ -0,0 +1,10 @@
#!/usr/bin/env bash
SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
TEST_CASES_PATH=/oauth/with-multi-resources
TEST_CONFIG_PATH=/oauth
PROFILES="uaa keycloak fakeportal multi-resources keycloak-oauth-provider enable-basic-auth"
source $SCRIPT/../../bin/suite_template $@
runWith keycloak uaa fakeportal

View File

@ -4,7 +4,7 @@ SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
TEST_CASES_PATH=/oauth/with-sp-initiated
TEST_CONFIG_PATH=/oauth
PROFILES="uaa uaa-oauth-provider mgt-prefix"
PROFILES="uaa uaa-oauth-provider mgt-prefix uaa-mgt-oauth-provider"
source $SCRIPT/../../bin/suite_template $@
runWith uaa

View File

@ -4,7 +4,7 @@ SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
TEST_CASES_PATH=/oauth/with-idp-down
TEST_CONFIG_PATH=/oauth
PROFILES="uaa uaa-oauth-provider"
PROFILES="uaa uaa-oauth-provider uaa-mgt-oauth-provider"
source $SCRIPT/../../bin/suite_template $@
run

View File

@ -4,7 +4,7 @@ SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
TEST_CASES_PATH=/oauth/with-sp-initiated
TEST_CONFIG_PATH=/oauth
PROFILES="uaa uaa-oauth-provider"
PROFILES="uaa uaa-oauth-provider uaa-mgt-oauth-provider"
source $SCRIPT/../../bin/suite_template $@
runWith uaa

View File

@ -1 +0,0 @@
export OAUTH_SIGNING_KEY_DIR=/config

View File

@ -1,2 +0,0 @@
export KEYCLOAK_URL=http://keycloak:8080/realms/test
export OAUTH_JKWS_URL="https://keycloak:8443/realms/test/protocol/openid-connect/certs"

View File

@ -1,2 +0,0 @@
export OAUTH_SIGNING_KEY_DIR=/config
export UAA_URL=http://uaa:8080

View File

@ -1 +0,0 @@
export OAUTH_SIGNING_KEY_DIR=deps/rabbitmq_management/selenium/test/${OAUTH_SIGNING_KEY_PATH}

View File

@ -1,2 +0,0 @@
export OAUTH_PROVIDER_URL=http://localhost:8080/realms/test
export OAUTH_JKWS_URL="https://localhost:8443/realms/test/protocol/openid-connect/certs"

View File

@ -1,2 +0,0 @@
export OAUTH_SIGNING_KEY_DIR=deps/rabbitmq_management/selenium/test/${OAUTH_SIGNING_KEY_PATH}
export UAA_URL=http://localhost:8080

View File

@ -1,4 +0,0 @@
export OAUTH_SIGNING_KEY_ID=legacy-token-key
export OAUTH_SIGNING_KEY_PATH=oauth/uaa
export OAUTH_CLIENT_SECRET=rabbit_client_code
export OAUTH_SCOPES="openid profile rabbitmq.*"

View File

@ -1,5 +1,5 @@
[accept,amqp10_client,amqp_client,base64url,cowboy,cowlib,eetcd,gun,jose,
prometheus,rabbitmq_amqp1_0,rabbitmq_auth_backend_cache,
oauth2_client,prometheus,rabbitmq_amqp1_0,rabbitmq_auth_backend_cache,
rabbitmq_auth_backend_http,rabbitmq_auth_backend_ldap,
rabbitmq_auth_backend_oauth2,rabbitmq_auth_mechanism_ssl,rabbitmq_aws,
rabbitmq_consistent_hash_exchange,rabbitmq_event_exchange,

View File

@ -0,0 +1 @@
export OAUTH_SERVER_CONFIG_BASEDIR=/config

View File

@ -0,0 +1,3 @@
export KEYCLOAK_URL=https://keycloak:8443/realms/test
export OAUTH_PROVIDER_URL=https://localhost:8443/realms/test
export OAUTH_PROVIDER_CA_CERT=/config/oauth/keycloak/ca_certificate.pem

View File

@ -0,0 +1 @@
export UAA_URL=http://uaa:8080

Some files were not shown because too many files have changed in this diff Show More