Create Oauth2 client
This commit is contained in:
parent
c50565c53a
commit
d827b72ce1
|
@ -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
|
|
@ -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"],
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
../../CODE_OF_CONDUCT.md
|
|
@ -0,0 +1 @@
|
|||
../../CONTRIBUTING.md
|
|
@ -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.
|
|
@ -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.
|
|
@ -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)
|
|
@ -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
|
|
@ -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"],
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
../../erlang.mk
|
|
@ -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{}.
|
|
@ -0,0 +1 @@
|
|||
../../rabbitmq-components.mk
|
|
@ -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).
|
|
@ -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.
|
|
@ -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}].
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"],
|
||||
)
|
||||
|
|
|
@ -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}.
|
||||
|
|
137
deps/rabbitmq_auth_backend_oauth2/src/Elixir.RabbitMQ.CLI.Ctl.Commands.AddSigningKeyCommand.erl
vendored
Normal file
137
deps/rabbitmq_auth_backend_oauth2/src/Elixir.RabbitMQ.CLI.Ctl.Commands.AddSigningKeyCommand.erl
vendored
Normal 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'.
|
|
@ -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().
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -23,4 +23,3 @@ stop(_State) ->
|
|||
|
||||
init([]) ->
|
||||
{ok, {{one_for_one,3,10},[]}}.
|
||||
|
||||
|
|
|
@ -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.
|
|
@ -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.
|
||||
|
|
|
@ -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) ->
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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}).
|
|
@ -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}).
|
||||
|
||||
|
|
|
@ -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">>}
|
||||
]
|
||||
|
||||
}
|
||||
}
|
||||
]}
|
||||
],[]
|
||||
}
|
||||
|
||||
].
|
||||
|
|
|
@ -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)).
|
||||
|
|
|
@ -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"])},
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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.
|
|
@ -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">>],
|
||||
|
|
|
@ -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">>)).
|
|
@ -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).
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
../../../.gitignore
|
||||
.sw?
|
||||
.*.sw?
|
||||
*.beam
|
||||
*.pem
|
||||
erl_crash.dump
|
||||
MnesiaCore.*
|
||||
/.erlang.mk/
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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}.
|
||||
|
||||
%% ===========================================================================
|
||||
|
||||
|
||||
|
|
|
@ -37,7 +37,6 @@
|
|||
oauth_initiateLogout();
|
||||
} else {
|
||||
if (!status.loggedIn) {
|
||||
replace_content('outer', format('login_oauth', {}));
|
||||
clear_auth();
|
||||
} else {
|
||||
oauth.logged_in = true;
|
||||
|
|
|
@ -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>')
|
||||
}
|
||||
function renderWarningMessageInLoginStatus(message) {
|
||||
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()
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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>
|
||||
</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> </th>
|
||||
<td><input type="submit" value="Login"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</div></div>
|
||||
</div> <!-- section -->
|
||||
|
||||
<% } %>
|
||||
<!-- end login with basic auth -->
|
||||
<% } %>
|
||||
|
||||
|
||||
</div> <!-- login -->
|
||||
|
|
|
@ -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"
|
||||
```
|
||||
|
|
|
@ -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.
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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
|
||||
|
|
10
deps/rabbitmq_management/selenium/suites/authnz-mgt/oauth-and-basic-auth.sh
vendored
Executable file
10
deps/rabbitmq_management/selenium/suites/authnz-mgt/oauth-and-basic-auth.sh
vendored
Executable 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
10
deps/rabbitmq_management/selenium/suites/authnz-mgt/oauth-with-multi-resources.sh
vendored
Executable file
10
deps/rabbitmq_management/selenium/suites/authnz-mgt/oauth-with-multi-resources.sh
vendored
Executable 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
export OAUTH_SIGNING_KEY_DIR=/config
|
|
@ -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"
|
|
@ -1,2 +0,0 @@
|
|||
export OAUTH_SIGNING_KEY_DIR=/config
|
||||
export UAA_URL=http://uaa:8080
|
|
@ -1 +0,0 @@
|
|||
export OAUTH_SIGNING_KEY_DIR=deps/rabbitmq_management/selenium/test/${OAUTH_SIGNING_KEY_PATH}
|
|
@ -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"
|
|
@ -1,2 +0,0 @@
|
|||
export OAUTH_SIGNING_KEY_DIR=deps/rabbitmq_management/selenium/test/${OAUTH_SIGNING_KEY_PATH}
|
||||
export UAA_URL=http://localhost:8080
|
|
@ -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.*"
|
|
@ -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,
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export OAUTH_SERVER_CONFIG_BASEDIR=/config
|
|
@ -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
|
|
@ -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
Loading…
Reference in New Issue