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).
|
-module(auth_http_mock).
|
||||||
|
|
||||||
-export([init/2]).
|
-export([init/2]).
|
||||||
|
-include_lib("common_test/include/ct.hrl").
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
%%% CALLBACKS
|
%%% CALLBACKS
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,7 @@ rabbitmq_app(
|
||||||
"@base64url//:erlang_app",
|
"@base64url//:erlang_app",
|
||||||
"@cowlib//:erlang_app",
|
"@cowlib//:erlang_app",
|
||||||
"@jose//:erlang_app",
|
"@jose//:erlang_app",
|
||||||
|
"//deps/oauth2_client:erlang_app",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -82,6 +83,7 @@ eunit(
|
||||||
compiled_suites = [
|
compiled_suites = [
|
||||||
":test_jwks_http_app_beam",
|
":test_jwks_http_app_beam",
|
||||||
":test_jwks_http_handler_beam",
|
":test_jwks_http_handler_beam",
|
||||||
|
":test_openid_http_handler_beam",
|
||||||
":test_jwks_http_sup_beam",
|
":test_jwks_http_sup_beam",
|
||||||
":test_rabbit_auth_backend_oauth2_test_util_beam",
|
":test_rabbit_auth_backend_oauth2_test_util_beam",
|
||||||
],
|
],
|
||||||
|
@ -99,16 +101,26 @@ rabbitmq_integration_suite(
|
||||||
size = "small",
|
size = "small",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
rabbitmq_integration_suite(
|
||||||
|
name = "add_signing_key_command_SUITE",
|
||||||
|
size = "small",
|
||||||
|
)
|
||||||
|
|
||||||
rabbitmq_integration_suite(
|
rabbitmq_integration_suite(
|
||||||
name = "config_schema_SUITE",
|
name = "config_schema_SUITE",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
rabbitmq_integration_suite(
|
||||||
|
name = "rabbit_oauth2_config_SUITE",
|
||||||
|
)
|
||||||
|
|
||||||
rabbitmq_integration_suite(
|
rabbitmq_integration_suite(
|
||||||
name = "jwks_SUITE",
|
name = "jwks_SUITE",
|
||||||
additional_beam = [
|
additional_beam = [
|
||||||
"test/rabbit_auth_backend_oauth2_test_util.beam",
|
"test/rabbit_auth_backend_oauth2_test_util.beam",
|
||||||
"test/jwks_http_app.beam",
|
"test/jwks_http_app.beam",
|
||||||
"test/jwks_http_handler.beam",
|
"test/jwks_http_handler.beam",
|
||||||
|
"test/openid_http_handler.beam",
|
||||||
"test/jwks_http_sup.beam",
|
"test/jwks_http_sup.beam",
|
||||||
],
|
],
|
||||||
deps = [
|
deps = [
|
||||||
|
@ -124,6 +136,7 @@ rabbitmq_suite(
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
rabbitmq_integration_suite(
|
rabbitmq_integration_suite(
|
||||||
name = "system_SUITE",
|
name = "system_SUITE",
|
||||||
size = "medium",
|
size = "medium",
|
||||||
|
@ -132,6 +145,7 @@ rabbitmq_integration_suite(
|
||||||
],
|
],
|
||||||
runtime_deps = [
|
runtime_deps = [
|
||||||
"@emqtt//:erlang_app",
|
"@emqtt//:erlang_app",
|
||||||
|
"//deps/oauth2_client:erlang_app",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,9 @@ BUILD_WITHOUT_QUIC=1
|
||||||
export BUILD_WITHOUT_QUIC
|
export BUILD_WITHOUT_QUIC
|
||||||
|
|
||||||
LOCAL_DEPS = inets public_key
|
LOCAL_DEPS = inets public_key
|
||||||
BUILD_DEPS = rabbit_common
|
BUILD_DEPS = rabbit_common oauth2_client
|
||||||
DEPS = rabbit cowlib jose base64url
|
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_EARLY_PLUGINS = rabbit_common/mk/rabbitmq-early-plugin.mk
|
||||||
DEP_PLUGINS = rabbit_common/mk/rabbitmq-plugin.mk
|
DEP_PLUGINS = rabbit_common/mk/rabbitmq-plugin.mk
|
||||||
|
|
|
@ -9,9 +9,11 @@ def all_beam_files(name = "all_beam_files"):
|
||||||
erlang_bytecode(
|
erlang_bytecode(
|
||||||
name = "other_beam",
|
name = "other_beam",
|
||||||
srcs = [
|
srcs = [
|
||||||
|
"src/Elixir.RabbitMQ.CLI.Ctl.Commands.AddSigningKeyCommand.erl",
|
||||||
"src/Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand.erl",
|
"src/Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand.erl",
|
||||||
"src/rabbit_auth_backend_oauth2.erl",
|
"src/rabbit_auth_backend_oauth2.erl",
|
||||||
"src/rabbit_auth_backend_oauth2_app.erl",
|
"src/rabbit_auth_backend_oauth2_app.erl",
|
||||||
|
"src/rabbit_oauth2_config.erl",
|
||||||
"src/rabbit_oauth2_scope.erl",
|
"src/rabbit_oauth2_scope.erl",
|
||||||
"src/uaa_jwks.erl",
|
"src/uaa_jwks.erl",
|
||||||
"src/uaa_jwt.erl",
|
"src/uaa_jwt.erl",
|
||||||
|
@ -26,6 +28,7 @@ def all_beam_files(name = "all_beam_files"):
|
||||||
deps = [
|
deps = [
|
||||||
"//deps/rabbit_common:erlang_app",
|
"//deps/rabbit_common:erlang_app",
|
||||||
"//deps/rabbitmq_cli:erlang_app",
|
"//deps/rabbitmq_cli:erlang_app",
|
||||||
|
"//deps/oauth2_client:erlang_app",
|
||||||
"@jose//:erlang_app",
|
"@jose//:erlang_app",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -40,9 +43,11 @@ def all_test_beam_files(name = "all_test_beam_files"):
|
||||||
name = "test_other_beam",
|
name = "test_other_beam",
|
||||||
testonly = True,
|
testonly = True,
|
||||||
srcs = [
|
srcs = [
|
||||||
|
"src/Elixir.RabbitMQ.CLI.Ctl.Commands.AddSigningKeyCommand.erl",
|
||||||
"src/Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand.erl",
|
"src/Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand.erl",
|
||||||
"src/rabbit_auth_backend_oauth2.erl",
|
"src/rabbit_auth_backend_oauth2.erl",
|
||||||
"src/rabbit_auth_backend_oauth2_app.erl",
|
"src/rabbit_auth_backend_oauth2_app.erl",
|
||||||
|
"src/rabbit_oauth2_config.erl",
|
||||||
"src/rabbit_oauth2_scope.erl",
|
"src/rabbit_oauth2_scope.erl",
|
||||||
"src/uaa_jwks.erl",
|
"src/uaa_jwks.erl",
|
||||||
"src/uaa_jwt.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/rabbit_common:erlang_app",
|
||||||
"//deps/rabbitmq_cli:erlang_app",
|
"//deps/rabbitmq_cli:erlang_app",
|
||||||
"@jose//:erlang_app",
|
"@jose//:erlang_app",
|
||||||
|
"//deps/oauth2_client:erlang_app",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -82,9 +88,11 @@ def all_srcs(name = "all_srcs"):
|
||||||
filegroup(
|
filegroup(
|
||||||
name = "srcs",
|
name = "srcs",
|
||||||
srcs = [
|
srcs = [
|
||||||
|
"src/Elixir.RabbitMQ.CLI.Ctl.Commands.AddSigningKeyCommand.erl",
|
||||||
"src/Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand.erl",
|
"src/Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand.erl",
|
||||||
"src/rabbit_auth_backend_oauth2.erl",
|
"src/rabbit_auth_backend_oauth2.erl",
|
||||||
"src/rabbit_auth_backend_oauth2_app.erl",
|
"src/rabbit_auth_backend_oauth2_app.erl",
|
||||||
|
"src/rabbit_oauth2_config.erl",
|
||||||
"src/rabbit_oauth2_scope.erl",
|
"src/rabbit_oauth2_scope.erl",
|
||||||
"src/uaa_jwks.erl",
|
"src/uaa_jwks.erl",
|
||||||
"src/uaa_jwt.erl",
|
"src/uaa_jwt.erl",
|
||||||
|
@ -147,7 +155,7 @@ def test_suite_beam_files(name = "test_suite_beam_files"):
|
||||||
outs = ["test/system_SUITE.beam"],
|
outs = ["test/system_SUITE.beam"],
|
||||||
app_name = "rabbitmq_auth_backend_oauth2",
|
app_name = "rabbitmq_auth_backend_oauth2",
|
||||||
erlc_opts = "//:test_erlc_opts",
|
erlc_opts = "//:test_erlc_opts",
|
||||||
deps = ["//deps/amqp_client:erlang_app"],
|
deps = ["//deps/amqp_client:erlang_app","//deps/oauth2_client:erlang_app"],
|
||||||
)
|
)
|
||||||
erlang_bytecode(
|
erlang_bytecode(
|
||||||
name = "test_jwks_http_app_beam",
|
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",
|
erlc_opts = "//:test_erlc_opts",
|
||||||
deps = ["@cowboy//:erlang_app"],
|
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(
|
erlang_bytecode(
|
||||||
name = "test_jwks_http_sup_beam",
|
name = "test_jwks_http_sup_beam",
|
||||||
testonly = True,
|
testonly = True,
|
||||||
|
@ -199,3 +216,20 @@ def test_suite_beam_files(name = "test_suite_beam_files"):
|
||||||
app_name = "rabbitmq_auth_backend_oauth2",
|
app_name = "rabbitmq_auth_backend_oauth2",
|
||||||
erlc_opts = "//:test_erlc_opts",
|
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))
|
list_to_binary(cuttlefish:conf_get("auth_oauth2.additional_scopes_key", Conf))
|
||||||
end}.
|
end}.
|
||||||
|
|
||||||
|
|
||||||
%% Configure the plugin to skip validation of the aud field
|
%% Configure the plugin to skip validation of the aud field
|
||||||
%%
|
%%
|
||||||
%% {verify_aud, true},
|
%% {verify_aud, true},
|
||||||
|
@ -98,9 +99,11 @@
|
||||||
"rabbitmq_auth_backend_oauth2.preferred_username_claims",
|
"rabbitmq_auth_backend_oauth2.preferred_username_claims",
|
||||||
fun(Conf) ->
|
fun(Conf) ->
|
||||||
Settings = cuttlefish_variable:filter_by_prefix("auth_oauth2.preferred_username_claims", 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}.
|
end}.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
%% ID of the default signing key
|
%% ID of the default signing key
|
||||||
%%
|
%%
|
||||||
%% {default_key, <<"key-1">>},
|
%% {default_key, <<"key-1">>},
|
||||||
|
@ -145,6 +148,16 @@
|
||||||
maps:from_list(SigningKeys)
|
maps:from_list(SigningKeys)
|
||||||
end}.
|
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,
|
{mapping,
|
||||||
"auth_oauth2.jwks_url",
|
"auth_oauth2.jwks_url",
|
||||||
"rabbitmq_auth_backend_oauth2.key_config.jwks_url",
|
"rabbitmq_auth_backend_oauth2.key_config.jwks_url",
|
||||||
|
@ -193,3 +206,149 @@
|
||||||
Settings = cuttlefish_variable:filter_by_prefix("auth_oauth2.algorithms", Conf),
|
Settings = cuttlefish_variable:filter_by_prefix("auth_oauth2.algorithms", Conf),
|
||||||
[list_to_binary(V) || {_, V} <- Settings]
|
[list_to_binary(V) || {_, V} <- Settings]
|
||||||
end}.
|
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').
|
-behaviour('Elixir.RabbitMQ.CLI.CommandBehaviour').
|
||||||
|
|
||||||
|
-define(COMMAND, 'Elixir.RabbitMQ.CLI.Ctl.Commands.AddSigningKeyCommand').
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
usage/0,
|
usage/0,
|
||||||
validate/2,
|
validate/2,
|
||||||
|
@ -17,6 +19,7 @@
|
||||||
switches/0,
|
switches/0,
|
||||||
aliases/0,
|
aliases/0,
|
||||||
output/2,
|
output/2,
|
||||||
|
description/0,
|
||||||
formatter/0
|
formatter/0
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
@ -24,118 +27,20 @@
|
||||||
usage() ->
|
usage() ->
|
||||||
<<"add_uaa_key <name> [--json=<json_key>] [--pem=<public_key>] [--pem-file=<pem_file>]">>.
|
<<"add_uaa_key <name> [--json=<json_key>] [--pem=<public_key>] [--pem-file=<pem_file>]">>.
|
||||||
|
|
||||||
switches() ->
|
description() -> <<"DEPRECATED. Use instead add_signing_key">>.
|
||||||
[{json, string},
|
|
||||||
{pem, string},
|
switches() -> ?COMMAND:switches().
|
||||||
{pem_file, string}].
|
|
||||||
|
|
||||||
aliases() -> [].
|
aliases() -> [].
|
||||||
|
|
||||||
validate([], _Options) -> {validation_failure, not_enough_args};
|
validate(Args, Options) -> ?COMMAND:validate(Args, Options).
|
||||||
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'.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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]).
|
expiry_timestamp/1]).
|
||||||
|
|
||||||
% for testing
|
% 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]).
|
-import(rabbit_data_coercion, [to_map/1]).
|
||||||
|
|
||||||
|
@ -30,22 +30,13 @@
|
||||||
%% App environment
|
%% App environment
|
||||||
%%
|
%%
|
||||||
|
|
||||||
-type app_env() :: [{atom(), any()}].
|
|
||||||
|
|
||||||
-define(APP, rabbitmq_auth_backend_oauth2).
|
|
||||||
-define(RESOURCE_SERVER_ID, resource_server_id).
|
-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
|
%% 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
|
%% verify server_server_id aud field is on the aud field
|
||||||
-define(VERIFY_AUD, verify_aud).
|
|
||||||
%% a term used by the IdentityServer community
|
%% 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
|
%% 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
|
%% Key JWT fields
|
||||||
|
@ -82,7 +73,8 @@ check_vhost_access(#auth_user{impl = DecodedTokenFun},
|
||||||
VHost, _AuthzData) ->
|
VHost, _AuthzData) ->
|
||||||
with_decoded_token(DecodedTokenFun(),
|
with_decoded_token(DecodedTokenFun(),
|
||||||
fun(_Token) ->
|
fun(_Token) ->
|
||||||
Scopes = get_scopes(DecodedTokenFun()),
|
DecodedToken = DecodedTokenFun(),
|
||||||
|
Scopes = get_scopes(DecodedToken),
|
||||||
ScopeString = rabbit_oauth2_scope:concat_scopes(Scopes, ","),
|
ScopeString = rabbit_oauth2_scope:concat_scopes(Scopes, ","),
|
||||||
rabbit_log:debug("Matching virtual host '~ts' against the following scopes: ~ts", [VHost, ScopeString]),
|
rabbit_log:debug("Matching virtual host '~ts' against the following scopes: ~ts", [VHost, ScopeString]),
|
||||||
rabbit_oauth2_scope:vhost_access(VHost, Scopes)
|
rabbit_oauth2_scope:vhost_access(VHost, Scopes)
|
||||||
|
@ -132,6 +124,7 @@ expiry_timestamp(#auth_user{impl = DecodedTokenFun}) ->
|
||||||
authenticate(_, AuthProps0) ->
|
authenticate(_, AuthProps0) ->
|
||||||
AuthProps = to_map(AuthProps0),
|
AuthProps = to_map(AuthProps0),
|
||||||
Token = token_from_context(AuthProps),
|
Token = token_from_context(AuthProps),
|
||||||
|
|
||||||
case check_token(Token) of
|
case check_token(Token) of
|
||||||
%% avoid logging the token
|
%% avoid logging the token
|
||||||
{error, _} = E -> E;
|
{error, _} = E -> E;
|
||||||
|
@ -141,9 +134,7 @@ authenticate(_, AuthProps0) ->
|
||||||
{refused, "Authentication using an OAuth 2/JWT token failed: ~tp", [Err]};
|
{refused, "Authentication using an OAuth 2/JWT token failed: ~tp", [Err]};
|
||||||
{ok, DecodedToken} ->
|
{ok, DecodedToken} ->
|
||||||
Func = fun(Token0) ->
|
Func = fun(Token0) ->
|
||||||
Username = username_from(
|
Username = username_from(rabbit_oauth2_config:get_preferred_username_claims(), Token0),
|
||||||
application:get_env(?APP, ?PREFERRED_USERNAME_CLAIMS, []),
|
|
||||||
Token0),
|
|
||||||
Tags = tags_from(Token0),
|
Tags = tags_from(Token0),
|
||||||
|
|
||||||
{ok, #auth_user{username = Username,
|
{ok, #auth_user{username = Username,
|
||||||
|
@ -186,18 +177,16 @@ check_token(DecodedToken) when is_map(DecodedToken) ->
|
||||||
{ok, DecodedToken};
|
{ok, DecodedToken};
|
||||||
|
|
||||||
check_token(Token) ->
|
check_token(Token) ->
|
||||||
Settings = application:get_all_env(?APP),
|
|
||||||
case uaa_jwt:decode_and_verify(Token) of
|
case uaa_jwt:decode_and_verify(Token) of
|
||||||
{error, Reason} -> {refused, {error, Reason}};
|
{error, Reason} ->
|
||||||
{true, Payload} ->
|
{refused, {error, Reason}};
|
||||||
validate_payload(post_process_payload(Payload, Settings));
|
{true, TargetResourceServerId, Payload} ->
|
||||||
{false, _} -> {refused, signature_invalid}
|
Payload0 = post_process_payload(TargetResourceServerId, Payload),
|
||||||
|
validate_payload(TargetResourceServerId, Payload0)
|
||||||
|
% _ -> {refused, signature_invalid}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
post_process_payload(Payload) when is_map(Payload) ->
|
post_process_payload(ResourceServerId, Payload) when is_map(Payload) ->
|
||||||
post_process_payload(Payload, []).
|
|
||||||
|
|
||||||
post_process_payload(Payload, AppEnv) when is_map(Payload) ->
|
|
||||||
Payload0 = maps:map(fun(K, V) ->
|
Payload0 = maps:map(fun(K, V) ->
|
||||||
case K of
|
case K of
|
||||||
?AUD_JWT_FIELD when is_binary(V) -> binary:split(V, <<" ">>, [global, trim_all]);
|
?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,
|
end,
|
||||||
Payload
|
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
|
false -> Payload0
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
@ -217,56 +207,49 @@ post_process_payload(Payload, AppEnv) when is_map(Payload) ->
|
||||||
false -> Payload1
|
false -> Payload1
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Payload3 = case has_configured_scope_aliases(AppEnv) of
|
Payload3 = case rabbit_oauth2_config:has_scope_aliases(ResourceServerId) of
|
||||||
true -> post_process_payload_with_scope_aliases(Payload2, AppEnv);
|
true -> post_process_payload_with_scope_aliases(ResourceServerId, Payload2);
|
||||||
false -> Payload2
|
false -> Payload2
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Payload4 = case maps:is_key(<<"authorization_details">>, Payload3) of
|
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
|
false -> Payload3
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Payload4.
|
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(ResourceServerId :: binary(), Payload :: map()) -> map().
|
||||||
-spec post_process_payload_with_scope_aliases(Payload :: map(), AppEnv :: app_env()) -> map().
|
|
||||||
%% This is for those hopeless environments where the token structure is so out of
|
%% 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.
|
%% 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
|
%% 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.
|
%% convention, or any other convention, in any way. They are just random client role IDs.
|
||||||
%% See rabbitmq/rabbitmq-server#4588 for details.
|
%% 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
|
%% 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
|
%% try the configurable 'extra_scopes_source' field value for alias
|
||||||
Payload2 = post_process_payload_with_scope_alias_in_extra_scopes_source(Payload1, AppEnv),
|
post_process_payload_with_scope_alias_in_extra_scopes_source(ResourceServerId, Payload1).
|
||||||
Payload2.
|
|
||||||
|
|
||||||
-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
|
%% First attempt: use the value in the 'scope' field for alias
|
||||||
post_process_payload_with_scope_alias_in_scope_field(Payload, AppEnv) ->
|
post_process_payload_with_scope_alias_in_scope_field(ResourceServerId, 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, ?SCOPE_JWT_FIELD, ScopeMappings).
|
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(),
|
-spec post_process_payload_with_scope_alias_in_extra_scopes_source(ResourceServerId :: binary(), Payload :: map()) -> map().
|
||||||
AppEnv :: app_env()) -> map().
|
|
||||||
%% Second attempt: use the value in the configurable 'extra scopes source' field for alias
|
%% 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) ->
|
post_process_payload_with_scope_alias_in_extra_scopes_source(ResourceServerId, Payload) ->
|
||||||
ExtraScopesField = proplists:get_value(?COMPLEX_CLAIM_APP_ENV_KEY, AppEnv, undefined),
|
ExtraScopesField = rabbit_oauth2_config:get_additional_scopes_key(ResourceServerId),
|
||||||
case ExtraScopesField of
|
case ExtraScopesField of
|
||||||
%% nothing to inject
|
%% nothing to inject
|
||||||
undefined -> Payload;
|
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)
|
post_process_payload_with_scope_alias_field_named(Payload, ExtraScopesField, ScopeMappings)
|
||||||
end.
|
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(),
|
-spec post_process_payload_with_scope_alias_field_named(Payload :: map(),
|
||||||
Field :: binary(),
|
Field :: binary(),
|
||||||
ScopeAliasMapping :: map()) -> map().
|
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) ->
|
post_process_payload_with_scope_alias_field_named(Payload, FieldName, ScopeAliasMapping) ->
|
||||||
Scopes0 = maps:get(FieldName, Payload, []),
|
Scopes0 = maps:get(FieldName, Payload, []),
|
||||||
Scopes = rabbit_data_coercion:to_list_of_binaries(Scopes0),
|
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).
|
maps:put(?SCOPE_JWT_FIELD, ExpandedScopes, Payload).
|
||||||
|
|
||||||
|
|
||||||
-spec does_include_complex_claim_field(Payload :: map()) -> boolean().
|
-spec does_include_complex_claim_field(ResourceServerId :: binary(), Payload :: map()) -> boolean().
|
||||||
does_include_complex_claim_field(Payload) when is_map(Payload) ->
|
does_include_complex_claim_field(ResourceServerId, Payload) when is_map(Payload) ->
|
||||||
maps:is_key(application:get_env(?APP, ?COMPLEX_CLAIM_APP_ENV_KEY, undefined), Payload).
|
case rabbit_oauth2_config:has_additional_scopes_key(ResourceServerId) of
|
||||||
|
true -> maps:is_key(rabbit_oauth2_config:get_additional_scopes_key(ResourceServerId), Payload);
|
||||||
-spec post_process_payload_with_complex_claim(Payload :: map()) -> map().
|
false -> false
|
||||||
post_process_payload_with_complex_claim(Payload) ->
|
end.
|
||||||
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 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 =
|
AdditionalScopes =
|
||||||
case ComplexClaim of
|
case ComplexClaim of
|
||||||
L when is_list(L) -> L;
|
L when is_list(L) -> L;
|
||||||
|
@ -486,13 +468,10 @@ is_recognized_permission(#{?ACTIONS_FIELD := _, ?LOCATIONS_FIELD:= _ , ?TYPE_FIE
|
||||||
is_recognized_permission(_, _) -> false.
|
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/
|
%% https://oauth.net/2/rich-authorization-requests/
|
||||||
post_process_payload_in_rich_auth_request_format(#{<<"authorization_details">> := Permissions} = Payload) ->
|
post_process_payload_in_rich_auth_request_format(ResourceServerId, #{<<"authorization_details">> := Permissions} = Payload) ->
|
||||||
ResourceServerId = rabbit_data_coercion:to_binary(
|
ResourceServerType = rabbit_oauth2_config:get_resource_server_type(ResourceServerId),
|
||||||
application:get_env(?APP, ?RESOURCE_SERVER_ID, <<>>)),
|
|
||||||
ResourceServerType = rabbit_data_coercion:to_binary(
|
|
||||||
application:get_env(?APP, ?RESOURCE_SERVER_TYPE, <<>>)),
|
|
||||||
|
|
||||||
FilteredPermissionsByType = lists:filter(fun(P) ->
|
FilteredPermissionsByType = lists:filter(fun(P) ->
|
||||||
is_recognized_permission(P, ResourceServerType) end, Permissions),
|
is_recognized_permission(P, ResourceServerType) end, Permissions),
|
||||||
|
@ -503,24 +482,22 @@ post_process_payload_in_rich_auth_request_format(#{<<"authorization_details">> :
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
validate_payload(DecodedToken) ->
|
validate_payload(ResourceServerId, DecodedToken) ->
|
||||||
ResourceServerEnv = application:get_env(?APP, ?RESOURCE_SERVER_ID, <<>>),
|
ScopePrefix = rabbit_oauth2_config:get_scope_prefix(ResourceServerId),
|
||||||
ResourceServerId = rabbit_data_coercion:to_binary(ResourceServerEnv),
|
validate_payload(ResourceServerId, DecodedToken, ScopePrefix).
|
||||||
ScopePrefix = application:get_env(?APP, ?SCOPE_PREFIX, <<ResourceServerId/binary, ".">>),
|
|
||||||
validate_payload(DecodedToken, ResourceServerId, 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
|
case check_aud(Aud, ResourceServerId) of
|
||||||
ok -> {ok, DecodedToken#{?SCOPE_JWT_FIELD => filter_scopes(Scope, ScopePrefix)}};
|
ok -> {ok, DecodedToken#{?SCOPE_JWT_FIELD => filter_scopes(Scope, ScopePrefix)}};
|
||||||
{error, Err} -> {refused, {invalid_aud, Err}}
|
{error, Err} -> {refused, {invalid_aud, Err}}
|
||||||
end;
|
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
|
case check_aud(Aud, ResourceServerId) of
|
||||||
ok -> {ok, DecodedToken};
|
ok -> {ok, DecodedToken};
|
||||||
{error, Err} -> {refused, {invalid_aud, Err}}
|
{error, Err} -> {refused, {invalid_aud, Err}}
|
||||||
end;
|
end;
|
||||||
validate_payload(#{?SCOPE_JWT_FIELD := Scope} = DecodedToken, _ResourceServerId, ScopePrefix) ->
|
validate_payload(ResourceServerId, #{?SCOPE_JWT_FIELD := Scope} = DecodedToken, ScopePrefix) ->
|
||||||
case application:get_env(?APP, ?VERIFY_AUD, true) of
|
case rabbit_oauth2_config:is_verify_aud(ResourceServerId) of
|
||||||
true -> {error, {badarg, {aud_field_is_missing}}};
|
true -> {error, {badarg, {aud_field_is_missing}}};
|
||||||
false -> {ok, DecodedToken#{?SCOPE_JWT_FIELD => filter_scopes(Scope, ScopePrefix)}}
|
false -> {ok, DecodedToken#{?SCOPE_JWT_FIELD => filter_scopes(Scope, ScopePrefix)}}
|
||||||
end.
|
end.
|
||||||
|
@ -531,7 +508,7 @@ filter_scopes(Scopes, ScopePrefix) ->
|
||||||
|
|
||||||
check_aud(_, <<>>) -> ok;
|
check_aud(_, <<>>) -> ok;
|
||||||
check_aud(Aud, ResourceServerId) ->
|
check_aud(Aud, ResourceServerId) ->
|
||||||
case application:get_env(?APP, ?VERIFY_AUD, true) of
|
case rabbit_oauth2_config:is_verify_aud(ResourceServerId) of
|
||||||
true ->
|
true ->
|
||||||
case Aud of
|
case Aud of
|
||||||
List when is_list(List) ->
|
List when is_list(List) ->
|
||||||
|
@ -627,8 +604,7 @@ token_from_context(AuthProps) ->
|
||||||
|
|
||||||
-spec username_from(list(), map()) -> binary() | undefined.
|
-spec username_from(list(), map()) -> binary() | undefined.
|
||||||
username_from(PreferredUsernameClaims, DecodedToken) ->
|
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, PreferredUsernameClaims),
|
||||||
ResolvedUsernameClaims = lists:filtermap(fun(Claim) -> find_claim_in_token(Claim, DecodedToken) end, UsernameClaims),
|
|
||||||
Username = case ResolvedUsernameClaims of
|
Username = case ResolvedUsernameClaims of
|
||||||
[ ] -> <<"unknown">>;
|
[ ] -> <<"unknown">>;
|
||||||
[ _One ] -> _One;
|
[ _One ] -> _One;
|
||||||
|
@ -638,13 +614,6 @@ username_from(PreferredUsernameClaims, DecodedToken) ->
|
||||||
[lists:flatten(io_lib:format("~p",[ResolvedUsernameClaims])), Username]),
|
[lists:flatten(io_lib:format("~p",[ResolvedUsernameClaims])), Username]),
|
||||||
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) ->
|
find_claim_in_token(Claim, Token) ->
|
||||||
case maps:get(Claim, Token, undefined) of
|
case maps:get(Claim, Token, undefined) of
|
||||||
undefined -> false;
|
undefined -> false;
|
||||||
|
|
|
@ -23,4 +23,3 @@ stop(_State) ->
|
||||||
|
|
||||||
init([]) ->
|
init([]) ->
|
||||||
{ok, {{one_for_one,3,10},[]}}.
|
{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).
|
-module(uaa_jwks).
|
||||||
-export([get/1, ssl_options/0]).
|
-export([get/2, ssl_options/1]).
|
||||||
|
|
||||||
-spec get(string() | binary()) -> {ok, term()} | {error, term()}.
|
-spec get(string() | binary(), term()) -> {ok, term()} | {error, term()}.
|
||||||
get(JwksUrl) ->
|
get(JwksUrl, KeyConfig) ->
|
||||||
httpc:request(get, {JwksUrl, []}, [{ssl, ssl_options()}, {timeout, 60000}], []).
|
httpc:request(get, {JwksUrl, []}, [{ssl, ssl_options(KeyConfig)}, {timeout, 60000}], []).
|
||||||
|
|
||||||
-spec ssl_options() -> list().
|
-spec ssl_options(term()) -> list().
|
||||||
ssl_options() ->
|
ssl_options(KeyConfig) ->
|
||||||
UaaEnv = application:get_env(rabbitmq_auth_backend_oauth2, key_config, []),
|
PeerVerification = proplists:get_value(peer_verification, KeyConfig, verify_none),
|
||||||
PeerVerification = proplists:get_value(peer_verification, UaaEnv, verify_none),
|
Depth = proplists:get_value(depth, KeyConfig, 10),
|
||||||
Depth = proplists:get_value(depth, UaaEnv, 10),
|
FailIfNoPeerCert = proplists:get_value(fail_if_no_peer_cert, KeyConfig, false),
|
||||||
FailIfNoPeerCert = proplists:get_value(fail_if_no_peer_cert, UaaEnv, false),
|
CrlCheck = proplists:get_value(crl_check, KeyConfig, false),
|
||||||
CrlCheck = proplists:get_value(crl_check, UaaEnv, false),
|
|
||||||
SslOpts0 = [{verify, PeerVerification},
|
SslOpts0 = [{verify, PeerVerification},
|
||||||
{depth, Depth},
|
{depth, Depth},
|
||||||
{fail_if_no_peer_cert, FailIfNoPeerCert},
|
{fail_if_no_peer_cert, FailIfNoPeerCert},
|
||||||
{crl_check, CrlCheck},
|
{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 ->
|
wildcard ->
|
||||||
[{customize_hostname_check, [{match_fun, public_key:pkix_verify_hostname_match_fun(https)}]} | SslOpts0];
|
[{customize_hostname_check, [{match_fun, public_key:pkix_verify_hostname_match_fun(https)}]} | SslOpts0];
|
||||||
none ->
|
none ->
|
||||||
SslOpts0
|
SslOpts0
|
||||||
end.
|
end.
|
||||||
|
|
||||||
cacertfile(UaaEnv) ->
|
cacertfile(KeyConfig) ->
|
||||||
case proplists:get_value(cacertfile, UaaEnv) of
|
case proplists:get_value(cacertfile, KeyConfig) of
|
||||||
undefined -> [];
|
undefined -> [];
|
||||||
CaCertFile -> [{cacertfile, CaCertFile}]
|
CaCertFile -> [{cacertfile, CaCertFile}]
|
||||||
end.
|
end.
|
||||||
|
|
|
@ -7,104 +7,99 @@
|
||||||
-module(uaa_jwt).
|
-module(uaa_jwt).
|
||||||
|
|
||||||
-export([add_signing_key/3,
|
-export([add_signing_key/3,
|
||||||
remove_signing_key/1,
|
|
||||||
decode_and_verify/1,
|
decode_and_verify/1,
|
||||||
get_jwk/1,
|
get_jwk/2,
|
||||||
verify_signing_key/2,
|
verify_signing_key/2]).
|
||||||
signing_keys/0]).
|
|
||||||
|
|
||||||
-export([client_id/1, sub/1, client_id/2, sub/2]).
|
-export([client_id/1, sub/1, client_id/2, sub/2]).
|
||||||
|
|
||||||
-include_lib("jose/include/jose_jwk.hrl").
|
-include_lib("jose/include/jose_jwk.hrl").
|
||||||
|
-include_lib("oauth2_client/include/oauth2_client.hrl").
|
||||||
|
|
||||||
-define(APP, rabbitmq_auth_backend_oauth2).
|
-define(APP, rabbitmq_auth_backend_oauth2).
|
||||||
|
|
||||||
-type key_type() :: json | pem | map.
|
-type key_type() :: json | pem | map.
|
||||||
|
|
||||||
-spec add_signing_key(binary(), key_type(), binary() | map()) -> {ok, map()} | {error, term()}.
|
-spec add_signing_key(binary(), key_type(), binary() | map()) -> {ok, map()} | {error, term()}.
|
||||||
|
|
||||||
add_signing_key(KeyId, Type, Value) ->
|
add_signing_key(KeyId, Type, Value) ->
|
||||||
case verify_signing_key(Type, Value) of
|
case verify_signing_key(Type, Value) of
|
||||||
ok ->
|
ok ->
|
||||||
SigningKeys0 = signing_keys(),
|
{ok, rabbit_oauth2_config:add_signing_key(KeyId, {Type, Value})};
|
||||||
SigningKeys1 = maps:put(KeyId, {Type, Value}, SigningKeys0),
|
|
||||||
ok = update_uaa_jwt_signing_keys(SigningKeys1),
|
|
||||||
{ok, SigningKeys1};
|
|
||||||
{error, _} = Err ->
|
{error, _} = Err ->
|
||||||
Err
|
Err
|
||||||
end.
|
end.
|
||||||
|
|
||||||
remove_signing_key(KeyId) ->
|
-spec update_jwks_signing_keys(term()) -> ok | {error, term()}.
|
||||||
UaaEnv = application:get_env(?APP, key_config, []),
|
update_jwks_signing_keys(ResourceServerId) ->
|
||||||
Keys0 = proplists:get_value(signing_keys, UaaEnv),
|
case rabbit_oauth2_config:get_oauth_provider_for_resource_server_id(ResourceServerId, [jwks_uri]) of
|
||||||
Keys1 = maps:remove(KeyId, Keys0),
|
{error, _} = Error ->
|
||||||
update_uaa_jwt_signing_keys(UaaEnv, Keys1).
|
rabbit_log:error("Failed to obtain JWKS URL for resource-server-id ~p ", [ResourceServerId]),
|
||||||
|
Error;
|
||||||
-spec update_uaa_jwt_signing_keys(map()) -> ok.
|
{ok, #oauth_provider{jwks_uri = JwksUrl, ssl_options = SslOptions}} ->
|
||||||
update_uaa_jwt_signing_keys(SigningKeys) ->
|
rabbit_log:debug("Downloading keys from ~p (ssl_options: ~p)", [JwksUrl, SslOptions]),
|
||||||
UaaEnv0 = application:get_env(?APP, key_config, []),
|
case uaa_jwks:get(JwksUrl, SslOptions) of
|
||||||
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
|
|
||||||
{ok, {_, _, JwksBody}} ->
|
{ok, {_, _, JwksBody}} ->
|
||||||
KeyList = maps:get(<<"keys">>, jose:decode(erlang:iolist_to_binary(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)),
|
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 ->
|
{error, _} = Err ->
|
||||||
|
rabbit_log:error("Error Downloadings keys ~p", [Err]),
|
||||||
Err
|
Err
|
||||||
end
|
end
|
||||||
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) ->
|
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} ->
|
{ok, KeyId} ->
|
||||||
case get_jwk(KeyId) of
|
rabbit_log:debug("Resolved signing_key_id : ~p", [KeyId]),
|
||||||
|
case get_jwk(KeyId, ResourceServerId) of
|
||||||
{ok, JWK} ->
|
{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 ->
|
{error, _} = Err ->
|
||||||
Err
|
Err
|
||||||
end;
|
end;
|
||||||
{error, _} = Err ->
|
{error, _} = Err ->
|
||||||
Err
|
Err
|
||||||
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec get_jwk(binary()) -> {ok, map()} | {error, term()}.
|
-spec get_jwk(binary(), binary()) -> {ok, map()} | {error, term()}.
|
||||||
get_jwk(KeyId) ->
|
get_jwk(KeyId, ResourceServerId) ->
|
||||||
get_jwk(KeyId, true).
|
get_jwk(KeyId, ResourceServerId, true).
|
||||||
|
|
||||||
get_jwk(KeyId, AllowUpdateJwks) ->
|
get_jwk(KeyId, ResourceServerId, AllowUpdateJwks) ->
|
||||||
Keys = signing_keys(),
|
case rabbit_oauth2_config:get_signing_key(KeyId, ResourceServerId) of
|
||||||
case maps:get(KeyId, Keys, undefined) of
|
|
||||||
undefined ->
|
undefined ->
|
||||||
if
|
if
|
||||||
AllowUpdateJwks ->
|
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 ->
|
ok ->
|
||||||
get_jwk(KeyId, false);
|
get_jwk(KeyId, ResourceServerId, false);
|
||||||
{error, no_jwks_url} ->
|
{error, no_jwks_url} ->
|
||||||
{error, key_not_found};
|
{error, key_not_found};
|
||||||
{error, _} = Err ->
|
{error, _} = Err ->
|
||||||
Err
|
Err
|
||||||
end;
|
end;
|
||||||
true ->
|
true ->
|
||||||
|
rabbit_log:debug("Signing key ~p not found. Download not allowed ", [KeyId]),
|
||||||
{error, key_not_found}
|
{error, key_not_found}
|
||||||
end;
|
end;
|
||||||
{Type, Value} ->
|
{Type, Value} ->
|
||||||
|
rabbit_log:debug("Signing key found : ~p, ~p ", [Type, Value]),
|
||||||
case Type of
|
case Type of
|
||||||
json -> uaa_jwt_jwk:make_jwk(Value);
|
json -> uaa_jwt_jwk:make_jwk(Value);
|
||||||
pem -> uaa_jwt_jwk:from_pem(Value);
|
pem -> uaa_jwt_jwk:from_pem(Value);
|
||||||
|
@ -131,9 +126,6 @@ verify_signing_key(Type, Value) ->
|
||||||
Err -> Err
|
Err -> Err
|
||||||
end.
|
end.
|
||||||
|
|
||||||
signing_keys() ->
|
|
||||||
UaaEnv = application:get_env(?APP, key_config, []),
|
|
||||||
proplists:get_value(signing_keys, UaaEnv, #{}).
|
|
||||||
|
|
||||||
-spec client_id(map()) -> binary() | undefined.
|
-spec client_id(map()) -> binary() | undefined.
|
||||||
client_id(DecodedToken) ->
|
client_id(DecodedToken) ->
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
%%
|
%%
|
||||||
-module(uaa_jwt_jwt).
|
-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_jwt.hrl").
|
||||||
-include_lib("jose/include/jose_jws.hrl").
|
-include_lib("jose/include/jose_jws.hrl").
|
||||||
|
@ -19,10 +19,10 @@ decode(Token) ->
|
||||||
{error, {invalid_token, Type, Err, Stacktrace}}
|
{error, {invalid_token, Type, Err, Stacktrace}}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
decode_and_verify(Jwk, Token) ->
|
decode_and_verify(ResourceServerId, Jwk, Token) ->
|
||||||
UaaEnv = application:get_env(rabbitmq_auth_backend_oauth2, key_config, []),
|
KeyConfig = rabbit_oauth2_config:get_key_config(ResourceServerId),
|
||||||
Verify =
|
Verify =
|
||||||
case proplists:get_value(algorithms, UaaEnv) of
|
case proplists:get_value(algorithms, KeyConfig) of
|
||||||
undefined ->
|
undefined ->
|
||||||
jose_jwt:verify(Jwk, Token);
|
jose_jwt:verify(Jwk, Token);
|
||||||
Algs ->
|
Algs ->
|
||||||
|
@ -33,20 +33,51 @@ decode_and_verify(Jwk, Token) ->
|
||||||
{false, #jose_jwt{fields = Fields}, _} -> {false, Fields}
|
{false, #jose_jwt{fields = Fields}, _} -> {false, Fields}
|
||||||
end.
|
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
|
try
|
||||||
case jose_jwt:peek_protected(Token) of
|
case jose_jwt:peek_protected(Token) of
|
||||||
#jose_jws{fields = #{<<"kid">> := Kid}} -> {ok, Kid};
|
#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
|
end
|
||||||
catch Type:Err:Stacktrace ->
|
catch Type:Err:Stacktrace ->
|
||||||
{error, {invalid_token, Type, Err, Stacktrace}}
|
{error, {invalid_token, Type, Err, Stacktrace}}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
get_default_key() ->
|
get_default_key(ResourceServerId) ->
|
||||||
UaaEnv = application:get_env(rabbitmq_auth_backend_oauth2, key_config, []),
|
KeyConfig = rabbit_oauth2_config:get_key_config(ResourceServerId),
|
||||||
case proplists:get_value(default_key, UaaEnv, undefined) of
|
case proplists:get_value(default_key, KeyConfig, undefined) of
|
||||||
undefined -> {error, no_key};
|
undefined -> {error, no_key};
|
||||||
Val -> {ok, Val}
|
Val -> {ok, Val}
|
||||||
end.
|
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),
|
CertsDir = ?config(rmq_certsdir, Config),
|
||||||
Keyfile = filename:join([CertsDir, <<"client">>, <<"key.pem">>]),
|
Keyfile = filename:join([CertsDir, <<"client">>, <<"key.pem">>]),
|
||||||
ok = ?COMMAND:validate([<<"foo">>], #{pem_file => Keyfile}).
|
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.id1 = test/config_schema_SUITE_data/certs/key.pem
|
||||||
auth_oauth2.signing_keys.id2 = test/config_schema_SUITE_data/certs/cert.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.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.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem
|
||||||
auth_oauth2.https.peer_verification = verify_none
|
auth_oauth2.https.peer_verification = verify_none
|
||||||
auth_oauth2.https.depth = 5
|
auth_oauth2.https.depth = 5
|
||||||
|
@ -28,6 +29,7 @@
|
||||||
{extra_scopes_source, <<"my_custom_scope_key">>},
|
{extra_scopes_source, <<"my_custom_scope_key">>},
|
||||||
{preferred_username_claims, [<<"user_name">>, <<"username">>, <<"email">>]},
|
{preferred_username_claims, [<<"user_name">>, <<"username">>, <<"email">>]},
|
||||||
{verify_aud, true},
|
{verify_aud, true},
|
||||||
|
{issuer, "https://my-jwt-issuer"},
|
||||||
{key_config, [
|
{key_config, [
|
||||||
{default_key, <<"id1">>},
|
{default_key, <<"id1">>},
|
||||||
{signing_keys,
|
{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, happy_path},
|
||||||
{group, unhappy_path},
|
{group, unhappy_path},
|
||||||
{group, unvalidated_jwks_server},
|
{group, no_peer_verification},
|
||||||
{group, no_peer_verification}
|
{group, multi_resource}
|
||||||
].
|
].
|
||||||
|
|
||||||
groups() ->
|
groups() ->
|
||||||
|
@ -48,8 +48,14 @@ groups() ->
|
||||||
test_failed_token_refresh_case1,
|
test_failed_token_refresh_case1,
|
||||||
test_failed_token_refresh_case2
|
test_failed_token_refresh_case2
|
||||||
]},
|
]},
|
||||||
{unvalidated_jwks_server, [], [test_failed_connection_with_unvalidated_jwks_server]},
|
{no_peer_verification, [], [
|
||||||
{no_peer_verification, [], [{group, happy_path}, {group, unhappy_path}]}
|
{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]),
|
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});
|
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) ->
|
init_per_group(_Group, Config) ->
|
||||||
add_vhosts(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.
|
Config.
|
||||||
|
|
||||||
end_per_group(no_peer_verification, 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),
|
rabbit_ct_helpers:testcase_started(Config, Testcase),
|
||||||
Config;
|
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) ->
|
init_per_testcase(Testcase, Config) ->
|
||||||
rabbit_ct_helpers:testcase_started(Config, Testcase),
|
rabbit_ct_helpers:testcase_started(Config, Testcase),
|
||||||
Config.
|
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_list orelse
|
||||||
Testcase =:= test_successful_connection_with_complex_claim_as_a_binary ->
|
Testcase =:= test_successful_connection_with_complex_claim_as_a_binary ->
|
||||||
rabbit_ct_broker_helpers:delete_vhost(Config, <<"vhost1">>),
|
rabbit_ct_broker_helpers:delete_vhost(Config, <<"vhost1">>),
|
||||||
ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env,
|
ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, unset_env,
|
||||||
[rabbitmq_auth_backend_oauth2, extra_scopes_source, undefined]),
|
[rabbitmq_auth_backend_oauth2, extra_scopes_source]),
|
||||||
rabbit_ct_helpers:testcase_started(Config, Testcase),
|
rabbit_ct_helpers:testcase_started(Config, Testcase),
|
||||||
Config;
|
Config;
|
||||||
|
|
||||||
end_per_testcase(Testcase, Config) when Testcase =:= test_successful_connection_with_algorithm_restriction orelse
|
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_algorithm_restriction ->
|
||||||
Testcase =:= test_failed_connection_with_unvalidated_jwks_server ->
|
|
||||||
rabbit_ct_broker_helpers:delete_vhost(Config, <<"vhost1">>),
|
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)]),
|
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),
|
rabbit_ct_helpers:testcase_finished(Config, Testcase),
|
||||||
|
@ -183,32 +211,52 @@ preconfigure_node(Config) ->
|
||||||
[rabbitmq_auth_backend_oauth2, resource_server_id, ?RESOURCE_SERVER_ID]),
|
[rabbitmq_auth_backend_oauth2, resource_server_id, ?RESOURCE_SERVER_ID]),
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
start_jwks_server(Config) ->
|
start_jwks_server(Config0) ->
|
||||||
Jwk = ?UTIL_MOD:fixture_jwk(),
|
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
|
%% 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,
|
JwksServerPort = PortBase + 100,
|
||||||
|
Config = rabbit_ct_helpers:set_config(Config0, [{jwksServerPort, JwksServerPort}]),
|
||||||
|
|
||||||
%% Both URLs direct to the same JWKS server
|
%% Both URLs direct to the same JWKS server
|
||||||
%% The NonStrictJwksUrl identity cannot be validated while StrictJwksUrl identity can be validated
|
%% The NonStrictJwksUrl identity cannot be validated while StrictJwksUrl identity can be validated
|
||||||
NonStrictJwksUrl = "https://127.0.0.1:" ++ integer_to_list(JwksServerPort) ++ "/jwks",
|
NonStrictJwksUrl = non_strict_jwks_url(Config),
|
||||||
StrictJwksUrl = "https://localhost:" ++ integer_to_list(JwksServerPort) ++ "/jwks",
|
StrictJwksUrl = strict_jwks_url(Config),
|
||||||
|
|
||||||
ok = application:set_env(jwks_http, keys, [Jwk]),
|
|
||||||
{ok, _} = application:ensure_all_started(ssl),
|
{ok, _} = application:ensure_all_started(ssl),
|
||||||
{ok, _} = application:ensure_all_started(cowboy),
|
{ok, _} = application:ensure_all_started(cowboy),
|
||||||
CertsDir = ?config(rmq_certsdir, Config),
|
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},
|
KeyConfig = [{jwks_url, StrictJwksUrl},
|
||||||
{peer_verification, verify_peer},
|
{peer_verification, verify_peer},
|
||||||
{cacertfile, filename:join([CertsDir, "testca", "cacert.pem"])}],
|
{cacertfile, filename:join([CertsDir, "testca", "cacert.pem"])}],
|
||||||
ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env,
|
ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env,
|
||||||
[rabbitmq_auth_backend_oauth2, key_config, KeyConfig]),
|
[rabbitmq_auth_backend_oauth2, key_config, KeyConfig]),
|
||||||
rabbit_ct_helpers:set_config(Config,
|
rabbit_ct_helpers:set_config(Config,
|
||||||
[{non_strict_jwks_url, NonStrictJwksUrl},
|
[
|
||||||
|
{non_strict_jwks_url, NonStrictJwksUrl},
|
||||||
{strict_jwks_url, StrictJwksUrl},
|
{strict_jwks_url, StrictJwksUrl},
|
||||||
{key_config, KeyConfig},
|
{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) ->
|
stop_jwks_server(Config) ->
|
||||||
ok = jwks_http_app:stop(),
|
ok = jwks_http_app:stop(),
|
||||||
|
@ -225,6 +273,9 @@ generate_valid_token(Config, Scopes, Audience) ->
|
||||||
undefined -> ?UTIL_MOD:fixture_jwk();
|
undefined -> ?UTIL_MOD:fixture_jwk();
|
||||||
Value -> Value
|
Value -> Value
|
||||||
end,
|
end,
|
||||||
|
generate_valid_token(Config, Jwk, Scopes, Audience).
|
||||||
|
|
||||||
|
generate_valid_token(_Config, Jwk, Scopes, Audience) ->
|
||||||
Token = case Audience of
|
Token = case Audience of
|
||||||
undefined -> ?UTIL_MOD:fixture_token_with_scopes(Scopes);
|
undefined -> ?UTIL_MOD:fixture_token_with_scopes(Scopes);
|
||||||
DefinedAudience -> maps:put(<<"aud">>, DefinedAudience, ?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),
|
Token = generate_valid_token(Config),
|
||||||
rabbit_ct_helpers:set_config(Config, {fixture_jwt, Token}).
|
rabbit_ct_helpers:set_config(Config, {fixture_jwt, Token}).
|
||||||
|
|
||||||
|
|
||||||
%%
|
%%
|
||||||
%% Test Cases
|
%% Test Cases
|
||||||
%%
|
%%
|
||||||
|
|
||||||
test_successful_connection_with_a_full_permission_token_and_all_defaults(Config) ->
|
test_successful_connection_with_a_full_permission_token_and_all_defaults(Config) ->
|
||||||
{_Algo, Token} = rabbit_ct_helpers:get_config(Config, fixture_jwt),
|
{_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),
|
Conn = open_unmanaged_connection(Config, 0, <<"username">>, Token),
|
||||||
{ok, Ch} = amqp_connection:open_channel(Conn),
|
{ok, Ch} = amqp_connection:open_channel(Conn),
|
||||||
#'queue.declare_ok'{queue = _} =
|
#'queue.declare_ok'{queue = _} =
|
||||||
amqp_channel:call(Ch, #'queue.declare'{exclusive = true}),
|
amqp_channel:call(Ch, #'queue.declare'{exclusive = true}),
|
||||||
close_connection_and_channel(Conn, Ch).
|
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) ->
|
test_successful_connection_with_a_full_permission_token_and_explicitly_configured_vhost(Config) ->
|
||||||
{_Algo, Token} = generate_valid_token(Config, [<<"rabbitmq.configure:vhost1/*">>,
|
{_Algo, Token} = generate_valid_token(Config, [<<"rabbitmq.configure:vhost1/*">>,
|
||||||
<<"rabbitmq.write:vhost1/*">>,
|
<<"rabbitmq.write:vhost1/*">>,
|
||||||
|
@ -290,7 +382,7 @@ test_successful_connection_with_simple_strings_for_aud_and_scope(Config) ->
|
||||||
{_Algo, Token} = generate_valid_token(
|
{_Algo, Token} = generate_valid_token(
|
||||||
Config,
|
Config,
|
||||||
<<"rabbitmq.configure:*/* rabbitmq.write:*/* rabbitmq.read:*/*">>,
|
<<"rabbitmq.configure:*/* rabbitmq.write:*/* rabbitmq.read:*/*">>,
|
||||||
<<"hare rabbitmq">>
|
[<<"hare">>, <<"rabbitmq">>]
|
||||||
),
|
),
|
||||||
Conn = open_unmanaged_connection(Config, 0, <<"username">>, Token),
|
Conn = open_unmanaged_connection(Config, 0, <<"username">>, Token),
|
||||||
{ok, Ch} = amqp_connection:open_channel(Conn),
|
{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) ->
|
test_successful_connection_with_complex_claim_as_a_binary(Config) ->
|
||||||
{_Algo, Token} = generate_valid_token_with_extra_fields(
|
{_Algo, Token} = generate_valid_token_with_extra_fields(
|
||||||
Config,
|
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),
|
Conn = open_unmanaged_connection(Config, 0, <<"username">>, Token),
|
||||||
{ok, Ch} = amqp_connection:open_channel(Conn),
|
{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),
|
{_Algo, Token} = rabbit_ct_helpers:get_config(Config, fixture_jwt),
|
||||||
?assertMatch({error, {auth_failure, _}},
|
?assertMatch({error, {auth_failure, _}},
|
||||||
open_unmanaged_connection(Config, 0, <<"username">>, Token)).
|
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).
|
-module(jwks_http_app).
|
||||||
|
|
||||||
-export([start/2, stop/0]).
|
-export([start/3, stop/0]).
|
||||||
|
|
||||||
start(Port, CertsDir) ->
|
start(Port, CertsDir, Mounts) ->
|
||||||
Dispatch =
|
Endpoints = [ {Mount, jwks_http_handler, [{keys, Keys}]} || {Mount,Keys} <- Mounts ] ++
|
||||||
cowboy_router:compile(
|
[{"/.well-known/openid-configuration", openid_http_handler, []}],
|
||||||
[
|
Dispatch = cowboy_router:compile([{'_', Endpoints}]),
|
||||||
{'_', [
|
|
||||||
{"/jwks", jwks_http_handler, []}
|
|
||||||
]}
|
|
||||||
]
|
|
||||||
),
|
|
||||||
{ok, _} = cowboy:start_tls(jwks_http_listener,
|
{ok, _} = cowboy:start_tls(jwks_http_listener,
|
||||||
[{port, Port},
|
[{port, Port},
|
||||||
{certfile, filename:join([CertsDir, "server", "cert.pem"])},
|
{certfile, filename:join([CertsDir, "server", "cert.pem"])},
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
-export([init/2, terminate/3]).
|
-export([init/2, terminate/3]).
|
||||||
|
|
||||||
init(Req, State) ->
|
init(Req, State) ->
|
||||||
{ok, Keys} = application:get_env(jwks_http, keys),
|
Keys = proplists:get_value(keys, State, []),
|
||||||
Body = rabbit_json:encode(#{keys => Keys}),
|
Body = rabbit_json:encode(#{keys => Keys}),
|
||||||
Headers = #{<<"content-type">> => <<"application/json">>},
|
Headers = #{<<"content-type">> => <<"application/json">>},
|
||||||
Req2 = cowboy_req:reply(200, Headers, Body, Req),
|
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).
|
jose_jws:compact(Signed).
|
||||||
|
|
||||||
fixture_jwk() ->
|
fixture_jwk() ->
|
||||||
|
fixture_jwk(<<"token-key">>).
|
||||||
|
|
||||||
|
fixture_jwk(TokenKey) ->
|
||||||
#{<<"alg">> => <<"HS256">>,
|
#{<<"alg">> => <<"HS256">>,
|
||||||
<<"k">> => <<"dG9rZW5rZXk">>,
|
<<"k">> => <<"dG9rZW5rZXk">>,
|
||||||
<<"kid">> => <<"token-key">>,
|
<<"kid">> => TokenKey,
|
||||||
<<"kty">> => <<"oct">>,
|
<<"kty">> => <<"oct">>,
|
||||||
<<"use">> => <<"sig">>,
|
<<"use">> => <<"sig">>,
|
||||||
<<"value">> => <<"tokenkey">>}.
|
<<"value">> => TokenKey}.
|
||||||
|
|
||||||
full_permission_scopes() ->
|
full_permission_scopes() ->
|
||||||
[<<"rabbitmq.configure:*/*">>,
|
[<<"rabbitmq.configure:*/*">>,
|
||||||
|
@ -78,7 +81,6 @@ fixture_token_with_scopes(Scopes) ->
|
||||||
token_with_scopes_and_expiration(Scopes, Expiration) ->
|
token_with_scopes_and_expiration(Scopes, Expiration) ->
|
||||||
%% expiration is a timestamp with precision in seconds
|
%% expiration is a timestamp with precision in seconds
|
||||||
#{<<"exp">> => Expiration,
|
#{<<"exp">> => Expiration,
|
||||||
<<"kid">> => <<"token-key">>,
|
|
||||||
<<"iss">> => <<"unit_test">>,
|
<<"iss">> => <<"unit_test">>,
|
||||||
<<"foo">> => <<"bar">>,
|
<<"foo">> => <<"bar">>,
|
||||||
<<"aud">> => [<<"rabbitmq">>],
|
<<"aud">> => [<<"rabbitmq">>],
|
||||||
|
@ -87,7 +89,6 @@ token_with_scopes_and_expiration(Scopes, Expiration) ->
|
||||||
token_without_scopes() ->
|
token_without_scopes() ->
|
||||||
%% expiration is a timestamp with precision in seconds
|
%% expiration is a timestamp with precision in seconds
|
||||||
#{
|
#{
|
||||||
<<"kid">> => <<"token-key">>,
|
|
||||||
<<"iss">> => <<"unit_test">>,
|
<<"iss">> => <<"unit_test">>,
|
||||||
<<"foo">> => <<"bar">>,
|
<<"foo">> => <<"bar">>,
|
||||||
<<"aud">> => [<<"rabbitmq">>]
|
<<"aud">> => [<<"rabbitmq">>]
|
||||||
|
@ -115,14 +116,12 @@ fixture_token_with_full_permissions() ->
|
||||||
plain_token_without_scopes_and_aud() ->
|
plain_token_without_scopes_and_aud() ->
|
||||||
%% expiration is a timestamp with precision in seconds
|
%% expiration is a timestamp with precision in seconds
|
||||||
#{<<"exp">> => default_expiration_moment(),
|
#{<<"exp">> => default_expiration_moment(),
|
||||||
<<"kid">> => <<"token-key">>,
|
|
||||||
<<"iss">> => <<"unit_test">>,
|
<<"iss">> => <<"unit_test">>,
|
||||||
<<"foo">> => <<"bar">>}.
|
<<"foo">> => <<"bar">>}.
|
||||||
|
|
||||||
token_with_scope_alias_in_scope_field(Value) ->
|
token_with_scope_alias_in_scope_field(Value) ->
|
||||||
%% expiration is a timestamp with precision in seconds
|
%% expiration is a timestamp with precision in seconds
|
||||||
#{<<"exp">> => default_expiration_moment(),
|
#{<<"exp">> => default_expiration_moment(),
|
||||||
<<"kid">> => <<"token-key">>,
|
|
||||||
<<"iss">> => <<"unit_test">>,
|
<<"iss">> => <<"unit_test">>,
|
||||||
<<"foo">> => <<"bar">>,
|
<<"foo">> => <<"bar">>,
|
||||||
<<"aud">> => [<<"rabbitmq">>],
|
<<"aud">> => [<<"rabbitmq">>],
|
||||||
|
@ -131,7 +130,6 @@ token_with_scope_alias_in_scope_field(Value) ->
|
||||||
token_with_scope_alias_in_claim_field(Claims, Scopes) ->
|
token_with_scope_alias_in_claim_field(Claims, Scopes) ->
|
||||||
%% expiration is a timestamp with precision in seconds
|
%% expiration is a timestamp with precision in seconds
|
||||||
#{<<"exp">> => default_expiration_moment(),
|
#{<<"exp">> => default_expiration_moment(),
|
||||||
<<"kid">> => <<"token-key">>,
|
|
||||||
<<"iss">> => <<"unit_test">>,
|
<<"iss">> => <<"unit_test">>,
|
||||||
<<"foo">> => <<"bar">>,
|
<<"foo">> => <<"bar">>,
|
||||||
<<"aud">> => [<<"rabbitmq">>],
|
<<"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_expired_token,
|
||||||
test_failed_connection_with_a_non_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_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, [], [
|
{token_refresh, [], [
|
||||||
|
@ -213,11 +214,23 @@ init_per_testcase(Testcase, Config) when Testcase =:= test_successful_connection
|
||||||
rabbit_ct_helpers:testcase_started(Config, Testcase),
|
rabbit_ct_helpers:testcase_started(Config, Testcase),
|
||||||
Config;
|
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) ->
|
init_per_testcase(Testcase, Config) ->
|
||||||
rabbit_ct_helpers:testcase_started(Config, Testcase),
|
rabbit_ct_helpers:testcase_started(Config, Testcase),
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
%%
|
%%
|
||||||
%% Per-case Teardown
|
%% Per-case Teardown
|
||||||
%%
|
%%
|
||||||
|
@ -270,6 +283,14 @@ end_per_testcase(Testcase, Config) when Testcase =:= test_successful_connection_
|
||||||
rabbit_ct_helpers:testcase_finished(Config, Testcase),
|
rabbit_ct_helpers:testcase_finished(Config, Testcase),
|
||||||
Config;
|
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) ->
|
end_per_testcase(Testcase, Config) ->
|
||||||
rabbit_ct_broker_helpers:delete_vhost(Config, <<"vhost1">>),
|
rabbit_ct_broker_helpers:delete_vhost(Config, <<"vhost1">>),
|
||||||
rabbit_ct_helpers:testcase_finished(Config, Testcase),
|
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(
|
{_Algo, Token} = generate_valid_token(
|
||||||
Config,
|
Config,
|
||||||
<<"rabbitmq.configure:*/* rabbitmq.write:*/* rabbitmq.read:*/*">>,
|
<<"rabbitmq.configure:*/* rabbitmq.write:*/* rabbitmq.read:*/*">>,
|
||||||
<<"hare rabbitmq">>
|
[<<"hare">>, <<"rabbitmq">>]
|
||||||
),
|
),
|
||||||
Conn = open_unmanaged_connection(Config, 0, <<"username">>, Token),
|
Conn = open_unmanaged_connection(Config, 0, <<"username">>, Token),
|
||||||
{ok, Ch} = amqp_connection:open_channel(Conn),
|
{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">>),
|
{_Algo, Token} = generate_valid_token(Config, <<"non-existent alias a8798s7doaisd79">>),
|
||||||
?assertMatch({error, not_allowed},
|
?assertMatch({error, not_allowed},
|
||||||
open_unmanaged_connection(Config, 0, <<"vhost2">>, <<"username">>, Token)).
|
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() ->
|
all() ->
|
||||||
[
|
[
|
||||||
test_own_scope,
|
test_own_scope,
|
||||||
test_validate_payload_resource_server_id_mismatch,
|
|
||||||
test_validate_payload_with_scope_prefix,
|
test_validate_payload_with_scope_prefix,
|
||||||
test_validate_payload,
|
test_validate_payload,
|
||||||
test_validate_payload_without_scope,
|
test_validate_payload_without_scope,
|
||||||
test_validate_payload_when_verify_aud_false,
|
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_unsuccessful_access_without_scopes,
|
||||||
test_successful_access_with_a_token_with_variables_in_scopes,
|
test_successful_access_with_a_token_with_variables_in_scopes,
|
||||||
test_successful_access_with_a_parsed_token,
|
test_successful_access_with_a_parsed_token,
|
||||||
|
@ -31,26 +28,39 @@ all() ->
|
||||||
test_unsuccessful_access_with_a_bogus_token,
|
test_unsuccessful_access_with_a_bogus_token,
|
||||||
test_restricted_vhost_access_with_a_valid_token,
|
test_restricted_vhost_access_with_a_valid_token,
|
||||||
test_insufficient_permissions_in_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_token_expiration,
|
||||||
test_incorrect_kid,
|
test_incorrect_kid,
|
||||||
test_post_process_token_payload,
|
test_post_process_token_payload,
|
||||||
test_post_process_token_payload_keycloak,
|
test_post_process_token_payload_keycloak,
|
||||||
test_post_process_payload_rich_auth_request,
|
test_post_process_payload_rich_auth_request,
|
||||||
test_post_process_payload_rich_auth_request_using_regular_expression_with_cluster,
|
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_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_unsuccessful_access_with_a_token_that_uses_missing_scope_alias_in_extra_scope_source_field,
|
||||||
test_default_ssl_options,
|
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) ->
|
init_per_suite(Config) ->
|
||||||
|
@ -68,21 +78,45 @@ end_per_suite(Config) ->
|
||||||
Env),
|
Env),
|
||||||
rabbit_ct_helpers:run_teardown_steps(Config).
|
rabbit_ct_helpers:run_teardown_steps(Config).
|
||||||
|
|
||||||
|
init_per_group(with_rabbitmq_node, Config) ->
|
||||||
|
Config1 = rabbit_ct_helpers:set_config(Config, [
|
||||||
|
{rmq_nodename_suffix, 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) ->
|
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, 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;
|
Config;
|
||||||
|
|
||||||
init_per_testcase(test_validate_payload_when_verify_aud_false, 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, verify_aud, false),
|
||||||
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
|
|
||||||
Config;
|
Config;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
init_per_testcase(test_post_process_payload_rich_auth_request, 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_type, <<"rabbitmq-type">>),
|
||||||
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
|
|
||||||
Config;
|
Config;
|
||||||
|
|
||||||
init_per_testcase(test_post_process_payload_rich_auth_request_using_regular_expression_with_cluster, 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(),
|
Jwk = ?UTIL_MOD:fixture_jwk(),
|
||||||
Token = maps:put(<<"aud">>, Audience, ?UTIL_MOD:fixture_token_with_scopes(Scopes)),
|
Token = maps:put(<<"aud">>, Audience, ?UTIL_MOD:fixture_token_with_scopes(Scopes)),
|
||||||
{_, EncodedToken} = ?UTIL_MOD:sign_token_hs(Token, Jwk),
|
{_, EncodedToken} = ?UTIL_MOD:sign_token_hs(Token, Jwk),
|
||||||
{true, Payload} = uaa_jwt_jwt:decode_and_verify(Jwk, EncodedToken),
|
case rabbit_oauth2_config:find_audience_in_resource_server_ids(Audience) of
|
||||||
rabbit_auth_backend_oauth2:post_process_payload(Payload).
|
{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(_) ->
|
test_post_process_token_payload_keycloak(_) ->
|
||||||
Pairs = [
|
Pairs = [
|
||||||
|
@ -202,8 +240,8 @@ post_process_payload_with_keycloak_authorization(Authorization) ->
|
||||||
Jwk = ?UTIL_MOD:fixture_jwk(),
|
Jwk = ?UTIL_MOD:fixture_jwk(),
|
||||||
Token = maps:put(<<"authorization">>, Authorization, ?UTIL_MOD:fixture_token_with_scopes([])),
|
Token = maps:put(<<"authorization">>, Authorization, ?UTIL_MOD:fixture_token_with_scopes([])),
|
||||||
{_, EncodedToken} = ?UTIL_MOD:sign_token_hs(Token, Jwk),
|
{_, EncodedToken} = ?UTIL_MOD:sign_token_hs(Token, Jwk),
|
||||||
{true, Payload} = uaa_jwt_jwt:decode_and_verify(Jwk, EncodedToken),
|
{true, Payload} = uaa_jwt_jwt:decode_and_verify(<<"rabbitmq">>, Jwk, EncodedToken),
|
||||||
rabbit_auth_backend_oauth2:post_process_payload(Payload).
|
rabbit_auth_backend_oauth2:post_process_payload(<<"rabbitmq">>, Payload).
|
||||||
|
|
||||||
test_post_process_payload_rich_auth_request_using_regular_expression_with_cluster(_) ->
|
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(
|
lists:foreach(
|
||||||
fun({Case, Permissions, ExpectedScope}) ->
|
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)
|
?assertEqual(lists:sort(ExpectedScope), lists:sort(maps:get(<<"scope">>, Payload)), Case)
|
||||||
end, Pairs).
|
end, Pairs).
|
||||||
|
|
||||||
|
@ -542,16 +580,16 @@ test_post_process_payload_rich_auth_request(_) ->
|
||||||
|
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
fun({Case, Permissions, ExpectedScope}) ->
|
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)
|
?assertEqual(lists:sort(ExpectedScope), lists:sort(maps:get(<<"scope">>, Payload)), Case)
|
||||||
end, Pairs).
|
end, Pairs).
|
||||||
|
|
||||||
post_process_payload_with_rich_auth_request(Permissions) ->
|
post_process_payload_with_rich_auth_request(ResourceServerId, Permissions) ->
|
||||||
Jwk = ?UTIL_MOD:fixture_jwk(),
|
Jwk = ?UTIL_MOD:fixture_jwk(),
|
||||||
Token = maps:put(<<"authorization_details">>, Permissions, ?UTIL_MOD:plain_token_without_scopes_and_aud()),
|
Token = maps:put(<<"authorization_details">>, Permissions, ?UTIL_MOD:plain_token_without_scopes_and_aud()),
|
||||||
{_, EncodedToken} = ?UTIL_MOD:sign_token_hs(Token, Jwk),
|
{_, EncodedToken} = ?UTIL_MOD:sign_token_hs(Token, Jwk),
|
||||||
{true, Payload} = uaa_jwt_jwt:decode_and_verify(Jwk, EncodedToken),
|
{true, Payload} = uaa_jwt_jwt:decode_and_verify(<<"rabbitmq">>, Jwk, EncodedToken),
|
||||||
rabbit_auth_backend_oauth2:post_process_payload(Payload).
|
rabbit_auth_backend_oauth2:post_process_payload(ResourceServerId, Payload).
|
||||||
|
|
||||||
test_post_process_token_payload_complex_claims(_) ->
|
test_post_process_token_payload_complex_claims(_) ->
|
||||||
Pairs = [
|
Pairs = [
|
||||||
|
@ -612,22 +650,21 @@ test_post_process_token_payload_complex_claims(_) ->
|
||||||
],
|
],
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
fun({Authorization, ExpectedScope}) ->
|
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))
|
?assertEqual(ExpectedScope, maps:get(<<"scope">>, Payload))
|
||||||
end, Pairs).
|
end, Pairs).
|
||||||
|
|
||||||
post_process_payload_with_complex_claim_authorization(Authorization) ->
|
post_process_payload_with_complex_claim_authorization(ResourceServerId, Authorization) ->
|
||||||
Jwk = ?UTIL_MOD:fixture_jwk(),
|
Jwk = ?UTIL_MOD:fixture_jwk(),
|
||||||
Token = maps:put(<<"additional_rabbitmq_scopes">>, Authorization, ?UTIL_MOD:fixture_token_with_scopes([])),
|
Token = maps:put(<<"additional_rabbitmq_scopes">>, Authorization, ?UTIL_MOD:fixture_token_with_scopes([])),
|
||||||
{_, EncodedToken} = ?UTIL_MOD:sign_token_hs(Token, Jwk),
|
{_, EncodedToken} = ?UTIL_MOD:sign_token_hs(Token, Jwk),
|
||||||
{true, Payload} = uaa_jwt_jwt:decode_and_verify(Jwk, EncodedToken),
|
{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(_) ->
|
test_successful_authentication_without_scopes(_) ->
|
||||||
Jwk = ?UTIL_MOD:fixture_jwk(),
|
Jwk = ?UTIL_MOD:fixture_jwk(),
|
||||||
UaaEnv = [{signing_keys, #{<<"token-key">> => {map, 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, key_config, UaaEnv),
|
||||||
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
|
|
||||||
|
|
||||||
Username = <<"username">>,
|
Username = <<"username">>,
|
||||||
Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk),
|
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(),
|
Jwk = ?UTIL_MOD:fixture_jwk(),
|
||||||
UaaEnv = [{signing_keys, #{<<"token-key">> => {map, 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, key_config, UaaEnv),
|
||||||
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
|
|
||||||
|
|
||||||
Username = <<"username">>,
|
Username = <<"username">>,
|
||||||
Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk),
|
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(),
|
Jwk = ?UTIL_MOD:fixture_jwk(),
|
||||||
UaaEnv = [{signing_keys, #{<<"token-key">> => {map, 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, key_config, UaaEnv),
|
||||||
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
|
|
||||||
|
|
||||||
VHost = <<"vhost">>,
|
VHost = <<"vhost">>,
|
||||||
Username = <<"username">>,
|
Username = <<"username">>,
|
||||||
|
@ -662,8 +697,8 @@ test_successful_access_with_a_token(_) ->
|
||||||
|
|
||||||
{ok, #auth_user{username = Username} = User} =
|
{ok, #auth_user{username = Username} = User} =
|
||||||
rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]),
|
rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]),
|
||||||
{ok, #auth_user{username = Username} = User} =
|
% {ok, #auth_user{username = Username} = User} =
|
||||||
rabbit_auth_backend_oauth2:user_login_authentication(Username, #{password => Token}),
|
% 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">>, none)),
|
||||||
assert_resource_access_granted(User, VHost, <<"foo">>, configure),
|
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(),
|
Jwk = ?UTIL_MOD:fixture_jwk(),
|
||||||
UaaEnv = [{signing_keys, #{<<"token-key">> => {map, 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, key_config, UaaEnv),
|
||||||
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
|
|
||||||
|
|
||||||
VHost = <<"my-vhost">>,
|
VHost = <<"my-vhost">>,
|
||||||
Username = <<"username">>,
|
Username = <<"username">>,
|
||||||
|
@ -696,7 +730,6 @@ test_successful_access_with_a_parsed_token(_) ->
|
||||||
Jwk = ?UTIL_MOD:fixture_jwk(),
|
Jwk = ?UTIL_MOD:fixture_jwk(),
|
||||||
UaaEnv = [{signing_keys, #{<<"token-key">> => {map, 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, key_config, UaaEnv),
|
||||||
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
|
|
||||||
|
|
||||||
Username = <<"username">>,
|
Username = <<"username">>,
|
||||||
Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk),
|
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(),
|
Jwk = ?UTIL_MOD:fixture_jwk(),
|
||||||
UaaEnv = [{signing_keys, #{<<"token-key">> => {map, 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, key_config, UaaEnv),
|
||||||
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
|
|
||||||
Username = <<"username">>,
|
Username = <<"username">>,
|
||||||
Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(
|
Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(
|
||||||
[<<"rabbitmq.tag:management">>, <<"rabbitmq.tag:policymaker">>]), Username), Jwk),
|
[<<"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(),
|
Jwk = ?UTIL_MOD:fixture_jwk(),
|
||||||
UaaEnv = [{signing_keys, #{<<"token-key">> => {map, 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, key_config, UaaEnv),
|
||||||
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
|
|
||||||
Alias = <<"client-alias-1">>,
|
Alias = <<"client-alias-1">>,
|
||||||
application:set_env(rabbitmq_auth_backend_oauth2, scope_aliases, #{
|
application:set_env(rabbitmq_auth_backend_oauth2, scope_aliases, #{
|
||||||
Alias => [
|
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(
|
Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub(
|
||||||
?UTIL_MOD:token_with_scope_alias_in_scope_field(Alias), Username), Jwk),
|
?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}]),
|
rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]),
|
||||||
assert_vhost_access_granted(AuthUser, VHost),
|
assert_vhost_access_granted(AuthUser, VHost),
|
||||||
assert_vhost_access_denied(AuthUser, <<"some-other-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(),
|
Jwk = ?UTIL_MOD:fixture_jwk(),
|
||||||
UaaEnv = [{signing_keys, #{<<"token-key">> => {map, 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, key_config, UaaEnv),
|
||||||
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
|
|
||||||
application:set_env(rabbitmq_auth_backend_oauth2, scope_prefix, <<>>),
|
application:set_env(rabbitmq_auth_backend_oauth2, scope_prefix, <<>>),
|
||||||
Alias = <<"client-alias-1">>,
|
Alias = <<"client-alias-1">>,
|
||||||
application:set_env(rabbitmq_auth_backend_oauth2, scope_aliases, #{
|
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(
|
Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub(
|
||||||
?UTIL_MOD:token_with_scope_alias_in_scope_field(Alias), Username), Jwk),
|
?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}]),
|
rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]),
|
||||||
assert_vhost_access_granted(AuthUser, VHost),
|
assert_vhost_access_granted(AuthUser, VHost),
|
||||||
assert_vhost_access_denied(AuthUser, <<"some-other-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(),
|
Jwk = ?UTIL_MOD:fixture_jwk(),
|
||||||
UaaEnv = [{signing_keys, #{<<"token-key">> => {map, 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, key_config, UaaEnv),
|
||||||
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
|
|
||||||
Role1 = <<"client-aliases-1">>,
|
Role1 = <<"client-aliases-1">>,
|
||||||
Role2 = <<"client-aliases-2">>,
|
Role2 = <<"client-aliases-2">>,
|
||||||
Role3 = <<"client-aliases-3">>,
|
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(
|
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),
|
?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}]),
|
rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]),
|
||||||
assert_vhost_access_granted(AuthUser, VHost),
|
assert_vhost_access_granted(AuthUser, VHost),
|
||||||
assert_vhost_access_denied(AuthUser, <<"some-other-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}}}],
|
UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}],
|
||||||
application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv),
|
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, extra_scopes_source, <<"claims">>),
|
||||||
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
|
|
||||||
Alias = <<"client-alias-1">>,
|
Alias = <<"client-alias-1">>,
|
||||||
application:set_env(rabbitmq_auth_backend_oauth2, scope_aliases, #{
|
application:set_env(rabbitmq_auth_backend_oauth2, scope_aliases, #{
|
||||||
Alias => [
|
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}}}],
|
UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}],
|
||||||
application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv),
|
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, extra_scopes_source, <<"claims">>),
|
||||||
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
|
|
||||||
Role1 = <<"client-aliases-1">>,
|
Role1 = <<"client-aliases-1">>,
|
||||||
Role2 = <<"client-aliases-2">>,
|
Role2 = <<"client-aliases-2">>,
|
||||||
Role3 = <<"client-aliases-3">>,
|
Role3 = <<"client-aliases-3">>,
|
||||||
|
@ -1027,10 +1054,9 @@ test_unsuccessful_access_without_scopes(_) ->
|
||||||
UaaEnv = [{signing_keys, #{<<"token-key">> => {map, 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, 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}]),
|
rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]),
|
||||||
|
|
||||||
ct:log("authuser ~p ~p ", [AuthUser, CredentialsFun()]),
|
|
||||||
assert_vhost_access_denied(AuthUser, <<"vhost">>).
|
assert_vhost_access_denied(AuthUser, <<"vhost">>).
|
||||||
|
|
||||||
test_restricted_vhost_access_with_a_valid_token(_) ->
|
test_restricted_vhost_access_with_a_valid_token(_) ->
|
||||||
|
@ -1100,48 +1126,29 @@ test_incorrect_kid(_) ->
|
||||||
Jwk = ?UTIL_MOD:fixture_jwk(),
|
Jwk = ?UTIL_MOD:fixture_jwk(),
|
||||||
Jwk1 = Jwk#{<<"kid">> := AltKid},
|
Jwk1 = Jwk#{<<"kid">> := AltKid},
|
||||||
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
|
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),
|
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]}}]},
|
||||||
?assertMatch({refused, "Authentication using an OAuth 2/JWT token failed: ~tp", [{error,key_not_found}]},
|
|
||||||
rabbit_auth_backend_oauth2:user_login_authentication(Username, #{password => Token})).
|
rabbit_auth_backend_oauth2:user_login_authentication(Username, #{password => Token})).
|
||||||
|
|
||||||
test_command_json(_) ->
|
login_and_check_vhost_access(Username, Token, Vhost) ->
|
||||||
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),
|
|
||||||
{ok, #auth_user{username = Username} = User} =
|
{ok, #auth_user{username = Username} = User} =
|
||||||
rabbit_auth_backend_oauth2:user_login_authentication(Username, #{password => Token}),
|
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(_) ->
|
test_username_from(_) ->
|
||||||
Pairs = [
|
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 ">>,
|
{ <<"resolve username from 1st claim in the array of configured claims ">>,
|
||||||
[<<"user_name">>, <<"email">>],
|
[<<"user_name">>, <<"email">>],
|
||||||
#{
|
#{
|
||||||
|
@ -1158,7 +1165,7 @@ test_username_from(_) ->
|
||||||
<<"rabbit_user">>
|
<<"rabbit_user">>
|
||||||
},
|
},
|
||||||
{ <<"resolve username from configured string claim ">>,
|
{ <<"resolve username from configured string claim ">>,
|
||||||
<<"email">>,
|
[<<"email">>],
|
||||||
#{
|
#{
|
||||||
<<"email">> => <<"rabbit_user">>
|
<<"email">> => <<"rabbit_user">>
|
||||||
},
|
},
|
||||||
|
@ -1182,7 +1189,6 @@ test_username_from(_) ->
|
||||||
|
|
||||||
test_command_pem_file(Config) ->
|
test_command_pem_file(Config) ->
|
||||||
Username = <<"username">>,
|
Username = <<"username">>,
|
||||||
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
|
|
||||||
CertsDir = ?config(rmq_certsdir, Config),
|
CertsDir = ?config(rmq_certsdir, Config),
|
||||||
Keyfile = filename:join([CertsDir, "client", "key.pem"]),
|
Keyfile = filename:join([CertsDir, "client", "key.pem"]),
|
||||||
Jwk = jose_jwk:from_pem_file(Keyfile),
|
Jwk = jose_jwk:from_pem_file(Keyfile),
|
||||||
|
@ -1193,45 +1199,14 @@ test_command_pem_file(Config) ->
|
||||||
|
|
||||||
'Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand':run(
|
'Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand':run(
|
||||||
[<<"token-key">>],
|
[<<"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">>),
|
Token = ?UTIL_MOD:sign_token_rsa(?UTIL_MOD:fixture_token(), Jwk, <<"token-key">>),
|
||||||
{ok, #auth_user{username = Username} = User} =
|
rabbit_ct_broker_helpers:rpc(Config, 0, unit_SUITE, login_and_check_vhost_access, [Username, Token, none]).
|
||||||
rabbit_auth_backend_oauth2:user_login_authentication(Username, #{password => Token}),
|
|
||||||
|
|
||||||
?assertEqual(true, rabbit_auth_backend_oauth2:check_vhost_access(User, <<"vhost">>, none)).
|
|
||||||
|
|
||||||
|
|
||||||
test_command_pem_file_no_kid(Config) ->
|
|
||||||
Username = <<"username">>,
|
|
||||||
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
|
|
||||||
CertsDir = ?config(rmq_certsdir, Config),
|
|
||||||
Keyfile = filename:join([CertsDir, "client", "key.pem"]),
|
|
||||||
Jwk = jose_jwk:from_pem_file(Keyfile),
|
|
||||||
|
|
||||||
PublicJwk = jose_jwk:to_public(Jwk),
|
|
||||||
PublicKeyFile = filename:join([CertsDir, "client", "public.pem"]),
|
|
||||||
jose_jwk:to_pem_file(PublicKeyFile, PublicJwk),
|
|
||||||
|
|
||||||
'Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand':run(
|
|
||||||
[<<"token-key">>],
|
|
||||||
#{node => node(), pem_file => PublicKeyFile}),
|
|
||||||
|
|
||||||
%% Set default key
|
|
||||||
{ok, UaaEnv0} = application:get_env(rabbitmq_auth_backend_oauth2, key_config),
|
|
||||||
UaaEnv1 = proplists:delete(default_key, UaaEnv0),
|
|
||||||
UaaEnv2 = [{default_key, <<"token-key">>} | UaaEnv1],
|
|
||||||
application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv2),
|
|
||||||
|
|
||||||
Token = ?UTIL_MOD:sign_token_no_kid(?UTIL_MOD:fixture_token(), Jwk),
|
|
||||||
{ok, #auth_user{username = Username} = User} =
|
|
||||||
rabbit_auth_backend_oauth2:user_login_authentication(Username, #{password => Token}),
|
|
||||||
|
|
||||||
?assertEqual(true, rabbit_auth_backend_oauth2:check_vhost_access(User, <<"vhost">>, none)).
|
|
||||||
|
|
||||||
test_command_pem(Config) ->
|
test_command_pem(Config) ->
|
||||||
Username = <<"username">>,
|
Username = <<"username">>,
|
||||||
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
|
|
||||||
CertsDir = ?config(rmq_certsdir, Config),
|
CertsDir = ?config(rmq_certsdir, Config),
|
||||||
Keyfile = filename:join([CertsDir, "client", "key.pem"]),
|
Keyfile = filename:join([CertsDir, "client", "key.pem"]),
|
||||||
Jwk = jose_jwk:from_pem_file(Keyfile),
|
Jwk = jose_jwk:from_pem_file(Keyfile),
|
||||||
|
@ -1240,18 +1215,13 @@ test_command_pem(Config) ->
|
||||||
|
|
||||||
'Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand':run(
|
'Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand':run(
|
||||||
[<<"token-key">>],
|
[<<"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">>),
|
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_ct_broker_helpers:rpc(Config, 0, unit_SUITE, login_and_check_vhost_access, [Username, Token, none]).
|
||||||
rabbit_auth_backend_oauth2:user_login_authentication(Username, #{password => Token}),
|
|
||||||
|
|
||||||
?assertEqual(true, rabbit_auth_backend_oauth2:check_vhost_access(User, <<"vhost">>, none)).
|
|
||||||
|
|
||||||
|
|
||||||
test_command_pem_no_kid(Config) ->
|
test_command_pem_no_kid(Config) ->
|
||||||
Username = <<"username">>,
|
Username = <<"username">>,
|
||||||
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
|
|
||||||
CertsDir = ?config(rmq_certsdir, Config),
|
CertsDir = ?config(rmq_certsdir, Config),
|
||||||
Keyfile = filename:join([CertsDir, "client", "key.pem"]),
|
Keyfile = filename:join([CertsDir, "client", "key.pem"]),
|
||||||
Jwk = jose_jwk:from_pem_file(Keyfile),
|
Jwk = jose_jwk:from_pem_file(Keyfile),
|
||||||
|
@ -1260,19 +1230,10 @@ test_command_pem_no_kid(Config) ->
|
||||||
|
|
||||||
'Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand':run(
|
'Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand':run(
|
||||||
[<<"token-key">>],
|
[<<"token-key">>],
|
||||||
#{node => node(), pem => Pem}),
|
#{node => rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename), pem => Pem}),
|
||||||
|
|
||||||
%% This is the default key
|
|
||||||
{ok, UaaEnv0} = application:get_env(rabbitmq_auth_backend_oauth2, key_config),
|
|
||||||
UaaEnv1 = proplists:delete(default_key, UaaEnv0),
|
|
||||||
UaaEnv2 = [{default_key, <<"token-key">>} | UaaEnv1],
|
|
||||||
application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv2),
|
|
||||||
|
|
||||||
Token = ?UTIL_MOD:sign_token_no_kid(?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk),
|
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_ct_broker_helpers:rpc(Config, 0, unit_SUITE, login_and_check_vhost_access, [Username, Token, none]).
|
||||||
rabbit_auth_backend_oauth2:user_login_authentication(Username, #{password => Token}),
|
|
||||||
|
|
||||||
?assertEqual(true, rabbit_auth_backend_oauth2:check_vhost_access(User, <<"vhost">>, none)).
|
|
||||||
|
|
||||||
|
|
||||||
test_own_scope(_) ->
|
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,
|
?assertEqual({refused, {invalid_aud, {resource_id_not_found_in_aud, ?RESOURCE_SERVER_ID,
|
||||||
[<<"foo">>,<<"bar">>]}}},
|
[<<"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, []}}},
|
?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(_) ->
|
test_validate_payload_with_scope_prefix(_) ->
|
||||||
Scenarios = [ { <<>>,
|
Scenarios = [ { <<>>,
|
||||||
|
@ -1321,7 +1282,7 @@ test_validate_payload_with_scope_prefix(_) ->
|
||||||
|
|
||||||
lists:map(fun({ ScopePrefix, Token, ExpectedScopes}) ->
|
lists:map(fun({ ScopePrefix, Token, ExpectedScopes}) ->
|
||||||
?assertEqual({ok, #{<<"aud">> => [?RESOURCE_SERVER_ID], <<"scope">> => 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
|
end
|
||||||
, Scenarios).
|
, Scenarios).
|
||||||
|
|
||||||
|
@ -1332,13 +1293,13 @@ test_validate_payload(_) ->
|
||||||
<<"foobar">>, <<"rabbitmq.other.third">>]},
|
<<"foobar">>, <<"rabbitmq.other.third">>]},
|
||||||
?assertEqual({ok, #{<<"aud">> => [?RESOURCE_SERVER_ID],
|
?assertEqual({ok, #{<<"aud">> => [?RESOURCE_SERVER_ID],
|
||||||
<<"scope">> => [<<"bar">>, <<"other.third">>]}},
|
<<"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(_) ->
|
test_validate_payload_without_scope(_) ->
|
||||||
KnownResourceServerId = #{<<"aud">> => [?RESOURCE_SERVER_ID]
|
KnownResourceServerId = #{<<"aud">> => [?RESOURCE_SERVER_ID]
|
||||||
},
|
},
|
||||||
?assertEqual({ok, #{<<"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(_) ->
|
test_validate_payload_when_verify_aud_false(_) ->
|
||||||
WithoutAud = #{
|
WithoutAud = #{
|
||||||
|
@ -1347,7 +1308,7 @@ test_validate_payload_when_verify_aud_false(_) ->
|
||||||
<<"foobar">>, <<"rabbitmq.other.third">>]},
|
<<"foobar">>, <<"rabbitmq.other.third">>]},
|
||||||
?assertEqual({ok, #{
|
?assertEqual({ok, #{
|
||||||
<<"scope">> => [<<"bar">>, <<"other.third">>]}},
|
<<"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 = #{
|
WithAudWithUnknownResourceId = #{
|
||||||
<<"aud">> => [<<"unknown">>],
|
<<"aud">> => [<<"unknown">>],
|
||||||
|
@ -1356,7 +1317,7 @@ test_validate_payload_when_verify_aud_false(_) ->
|
||||||
<<"foobar">>, <<"rabbitmq.other.third">>]},
|
<<"foobar">>, <<"rabbitmq.other.third">>]},
|
||||||
?assertEqual({ok, #{<<"aud">> => [<<"unknown">>],
|
?assertEqual({ok, #{<<"aud">> => [<<"unknown">>],
|
||||||
<<"scope">> => [<<"bar">>, <<"other.third">>]}},
|
<<"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(_) ->
|
test_default_ssl_options(_) ->
|
||||||
?assertEqual([
|
?assertEqual([
|
||||||
|
@ -1365,7 +1326,7 @@ test_default_ssl_options(_) ->
|
||||||
{fail_if_no_peer_cert, false},
|
{fail_if_no_peer_cert, false},
|
||||||
{crl_check, false},
|
{crl_check, false},
|
||||||
{crl_cache, {ssl_crl_cache, {internal, [{http, 10000}]}}}
|
{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(_) ->
|
test_default_ssl_options_with_cacertfile(_) ->
|
||||||
?assertEqual([
|
?assertEqual([
|
||||||
|
@ -1375,7 +1336,7 @@ test_default_ssl_options_with_cacertfile(_) ->
|
||||||
{crl_check, false},
|
{crl_check, false},
|
||||||
{crl_cache, {ssl_crl_cache, {internal, [{http, 10000}]}}},
|
{crl_cache, {ssl_crl_cache, {internal, [{http, 10000}]}}},
|
||||||
{cacertfile, filename:join(["testca", "cacert.pem"])}
|
{cacertfile, filename:join(["testca", "cacert.pem"])}
|
||||||
], uaa_jwks:ssl_options()).
|
], uaa_jwks:ssl_options(rabbit_oauth2_config:get_key_config())).
|
||||||
|
|
||||||
%%
|
%%
|
||||||
%% Helpers
|
%% Helpers
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
../../../.gitignore
|
||||||
.sw?
|
.sw?
|
||||||
.*.sw?
|
.*.sw?
|
||||||
*.beam
|
*.beam
|
||||||
*.pem
|
|
||||||
erl_crash.dump
|
erl_crash.dump
|
||||||
MnesiaCore.*
|
MnesiaCore.*
|
||||||
/.erlang.mk/
|
/.erlang.mk/
|
||||||
|
|
|
@ -89,6 +89,7 @@ rabbitmq_app(
|
||||||
"@cowboy//:erlang_app",
|
"@cowboy//:erlang_app",
|
||||||
"@cowlib//:erlang_app",
|
"@cowlib//:erlang_app",
|
||||||
"@ranch//:erlang_app",
|
"@ranch//:erlang_app",
|
||||||
|
"//deps/oauth2_client:erlang_app",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -207,6 +208,11 @@ rabbitmq_suite(
|
||||||
size = "small",
|
size = "small",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
rabbitmq_suite(
|
||||||
|
name = "rabbit_mgmt_wm_auth_SUITE",
|
||||||
|
size = "small",
|
||||||
|
)
|
||||||
|
|
||||||
rabbitmq_suite(
|
rabbitmq_suite(
|
||||||
name = "stats_SUITE",
|
name = "stats_SUITE",
|
||||||
size = "small",
|
size = "small",
|
||||||
|
|
|
@ -21,7 +21,7 @@ define PROJECT_APP_EXTRA_KEYS
|
||||||
{broker_version_requirements, []}
|
{broker_version_requirements, []}
|
||||||
endef
|
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
|
TEST_DEPS = rabbitmq_ct_helpers rabbitmq_ct_client_helpers proper
|
||||||
LOCAL_DEPS += ranch ssl crypto public_key
|
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:erlang_app",
|
||||||
"//deps/rabbit_common:erlang_app",
|
"//deps/rabbit_common:erlang_app",
|
||||||
"//deps/rabbitmq_management_agent: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:erlang_app",
|
||||||
"//deps/rabbit_common:erlang_app",
|
"//deps/rabbit_common:erlang_app",
|
||||||
"//deps/rabbitmq_management_agent: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",
|
app_name = "rabbitmq_management",
|
||||||
erlc_opts = "//:test_erlc_opts",
|
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(
|
erlang_bytecode(
|
||||||
name = "stats_SUITE_beam_files",
|
name = "stats_SUITE_beam_files",
|
||||||
testonly = True,
|
testonly = True,
|
||||||
|
|
|
@ -432,7 +432,6 @@ fun(Conf) ->
|
||||||
end}.
|
end}.
|
||||||
|
|
||||||
|
|
||||||
%% OAuth 2/SSO access only
|
|
||||||
|
|
||||||
{mapping, "management.disable_basic_auth", "rabbitmq_management.disable_basic_auth",
|
{mapping, "management.disable_basic_auth", "rabbitmq_management.disable_basic_auth",
|
||||||
[{datatype, {enum, [true, false]}}]}.
|
[{datatype, {enum, [true, false]}}]}.
|
||||||
|
@ -448,15 +447,21 @@ end}.
|
||||||
|
|
||||||
%% ===========================================================================
|
%% ===========================================================================
|
||||||
%% Authorization
|
%% Authorization
|
||||||
|
%% OAuth 2/SSO access only
|
||||||
|
|
||||||
%% Enable OAuth2 in the management ui
|
%% Enable OAuth2 in the management ui
|
||||||
{mapping, "management.oauth_enabled", "rabbitmq_management.oauth_enabled",
|
{mapping, "management.oauth_enabled", "rabbitmq_management.oauth_enabled",
|
||||||
[{datatype, {enum, [true, false]}}]}.
|
[{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
|
%% The URL of the OIDC/OAuth2 provider
|
||||||
{mapping, "management.oauth_provider_url", "rabbitmq_management.oauth_provider_url",
|
{mapping, "management.oauth_provider_url", "rabbitmq_management.oauth_provider_url",
|
||||||
[{datatype, string}]}.
|
[{datatype, string}]}.
|
||||||
|
|
||||||
|
|
||||||
%% Your client application's identifier as registered with the OIDC/OAuth2
|
%% Your client application's identifier as registered with the OIDC/OAuth2
|
||||||
{mapping, "management.oauth_client_id", "rabbitmq_management.oauth_client_id",
|
{mapping, "management.oauth_client_id", "rabbitmq_management.oauth_client_id",
|
||||||
[{datatype, string}]}.
|
[{datatype, string}]}.
|
||||||
|
@ -483,6 +488,95 @@ end}.
|
||||||
{mapping, "management.oauth_initiated_logon_type", "rabbitmq_management.oauth_initiated_logon_type",
|
{mapping, "management.oauth_initiated_logon_type", "rabbitmq_management.oauth_initiated_logon_type",
|
||||||
[{datatype, {enum, [sp_initiated, idp_initiated]}}]}.
|
[{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();
|
oauth_initiateLogout();
|
||||||
} else {
|
} else {
|
||||||
if (!status.loggedIn) {
|
if (!status.loggedIn) {
|
||||||
replace_content('outer', format('login_oauth', {}));
|
|
||||||
clear_auth();
|
clear_auth();
|
||||||
} else {
|
} else {
|
||||||
oauth.logged_in = true;
|
oauth.logged_in = true;
|
||||||
|
|
|
@ -22,27 +22,63 @@ function startWithOAuthLogin () {
|
||||||
store_pref("oauth-return-to", window.location.hash);
|
store_pref("oauth-return-to", window.location.hash);
|
||||||
|
|
||||||
if (!oauth.logged_in) {
|
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) {
|
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 {
|
} else {
|
||||||
replace_content('outer', format('login_oauth', {}))
|
try {
|
||||||
|
validate_openid_configuration(JSON.parse(req.responseText))
|
||||||
|
render_login_oauth()
|
||||||
start_app_login()
|
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 {
|
} else {
|
||||||
replace_content('outer', format('login_oauth', {}))
|
console.log("Rendering login with " + JSON.stringify(oauth.resource_servers))
|
||||||
|
render_login_oauth()
|
||||||
start_app_login()
|
start_app_login()
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
start_app_login()
|
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) {
|
function renderWarningMessageInLoginStatus(message) {
|
||||||
replace_content('outer', format('login_oauth', {}))
|
render_login_oauth(message)
|
||||||
replace_content('login-status', '<p class="warning">' + message + '</p> <button id="loginWindow" onclick="oauth_initiateLogin()">Click here to log in</button>')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -72,7 +108,7 @@ function start_app_login () {
|
||||||
app = new Sammy.Application(function () {
|
app = new Sammy.Application(function () {
|
||||||
this.get('/', function () {})
|
this.get('/', function () {})
|
||||||
this.get('#/', function () {})
|
this.get('#/', function () {})
|
||||||
if (!oauth.enabled) {
|
if (!oauth.enabled || !oauth.oauth_disable_basic_auth) {
|
||||||
this.put('#/login', function() {
|
this.put('#/login', function() {
|
||||||
set_basic_auth(this.params['username'], this.params['password'])
|
set_basic_auth(this.params['username'], this.params['password'])
|
||||||
check_login()
|
check_login()
|
||||||
|
|
|
@ -2,24 +2,6 @@
|
||||||
var mgr;
|
var mgr;
|
||||||
var _management_logger;
|
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() {
|
function rabbit_base_uri() {
|
||||||
return window.location.protocol + "//" + window.location.hostname + rabbit_port() + rabbit_path_prefix()
|
return window.location.protocol + "//" + window.location.hostname + rabbit_port() + rabbit_path_prefix()
|
||||||
|
@ -30,59 +12,113 @@ function rabbit_path_prefix() {
|
||||||
function rabbit_port() {
|
function rabbit_port() {
|
||||||
return window.location.port ? ":" + window.location.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) {
|
if (!authSettings.oauth_response_type) {
|
||||||
authSettings.oauth_response_type = "code"; // although the default value in oidc client
|
authSettings.oauth_response_type = "code"; // although the default value in oidc client
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!authSettings.oauth_scopes) {
|
if (!authSettings.oauth_scopes) {
|
||||||
authSettings.oauth_scopes = "openid profile";
|
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;
|
return authSettings;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function oauth_initialize(authSettings) {
|
function oauth_initialize_user_manager(resource_server) {
|
||||||
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);
|
|
||||||
|
|
||||||
oidcSettings = {
|
oidcSettings = {
|
||||||
//userStore: new WebStorageStateStore({ store: window.localStorage }),
|
//userStore: new WebStorageStateStore({ store: window.localStorage }),
|
||||||
authority: authSettings.oauth_provider_url,
|
authority: resource_server.provider_url,
|
||||||
client_id: authSettings.oauth_client_id,
|
client_id: resource_server.client_id,
|
||||||
response_type: authSettings.oauth_response_type,
|
response_type: resource_server.response_type,
|
||||||
scope: authSettings.oauth_scopes,
|
scope: resource_server.scopes,
|
||||||
resource: authSettings.oauth_resource_id,
|
resource: resource_server.id,
|
||||||
redirect_uri: rabbit_base_uri() + "/js/oidc-oauth/login-callback.html",
|
redirect_uri: rabbit_base_uri() + "/js/oidc-oauth/login-callback.html",
|
||||||
post_logout_redirect_uri: rabbit_base_uri() + "/",
|
post_logout_redirect_uri: rabbit_base_uri() + "/",
|
||||||
|
|
||||||
automaticSilentRenew: true,
|
automaticSilentRenew: true,
|
||||||
revokeAccessTokenOnSignout: true,
|
revokeAccessTokenOnSignout: true,
|
||||||
extraQueryParams: {
|
extraQueryParams: {
|
||||||
audience: authSettings.oauth_resource_id, // required by oauth0
|
audience: resource_server.id, // required by oauth0
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
if (authSettings.oauth_client_secret != "") {
|
if (resource_server.client_secret != "") {
|
||||||
oidcSettings.client_secret = authSettings.oauth_client_secret;
|
oidcSettings.client_secret = resource_server.client_secret;
|
||||||
}
|
}
|
||||||
if (authSettings.oauth_metadata_url != "") {
|
if (resource_server.metadata_url != "") {
|
||||||
oidcSettings.metadataUrl = authSettings.oauth_metadata_url;
|
oidcSettings.metadataUrl = resource_server.metadata_url;
|
||||||
}
|
}
|
||||||
|
|
||||||
oidc.Log.setLevel(oidc.Log.DEBUG);
|
oidc.Log.setLevel(oidc.Log.DEBUG);
|
||||||
|
@ -108,6 +144,32 @@ function oauth_initialize(authSettings) {
|
||||||
set_token_auth(oauth.access_token)
|
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;
|
return oauth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,20 +195,38 @@ function oauth_is_logged_in() {
|
||||||
return { "user": user, "loggedIn": !user.expired };
|
return { "user": user, "loggedIn": !user.expired };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function oauth_initiateLogin() {
|
function lookup_resource_server(resource_server_id) {
|
||||||
if (oauth.sp_initiated) {
|
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() {
|
mgr.signinRedirect({ state: { } }).then(function() {
|
||||||
_management_logger.debug("signinRedirect done");
|
_management_logger.debug("signinRedirect done")
|
||||||
}).catch(function(err) {
|
}).catch(function(err) {
|
||||||
_management_logger.error(err);
|
_management_logger.error(err)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
location.href = oauth.authority;
|
location.href = resource_server.provider_url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function oauth_redirectToHome(oauth) {
|
function oauth_redirectToHome(oauth) {
|
||||||
console.log("oauth_redirectToHome set_token_auth")
|
|
||||||
set_token_auth(oauth.access_token)
|
set_token_auth(oauth.access_token)
|
||||||
|
|
||||||
path = get_pref("oauth-return-to");
|
path = get_pref("oauth-return-to");
|
||||||
|
@ -183,3 +263,21 @@ function oauth_completeLogout() {
|
||||||
clear_auth()
|
clear_auth()
|
||||||
mgr.signoutRedirectCallback().then(_ => oauth_redirectToLogin())
|
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 AUTH_SCHEME = "auth-scheme"
|
||||||
const LOGGED_IN = 'loggedIn'
|
const LOGGED_IN = 'loggedIn'
|
||||||
const LOGIN_SESSION_TIMEOUT = "login_session_timeout"
|
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() {
|
function has_auth_credentials() {
|
||||||
return get_local_pref(CREDENTIALS) != undefined && get_local_pref(AUTH_SCHEME) != undefined &&
|
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_local_pref(AUTH_SCHEME)
|
||||||
clear_cookie_value(LOGIN_SESSION_TIMEOUT)
|
clear_cookie_value(LOGIN_SESSION_TIMEOUT)
|
||||||
clear_cookie_value(LOGGED_IN)
|
clear_cookie_value(LOGGED_IN)
|
||||||
|
clear_local_pref(AUTH_RESOURCE)
|
||||||
}
|
}
|
||||||
function set_basic_auth(username, password) {
|
function set_basic_auth(username, password) {
|
||||||
set_auth("Basic", b64_encode_utf8(username + ":" + password), default_hard_session_timeout())
|
set_auth("Basic", b64_encode_utf8(username + ":" + password), default_hard_session_timeout())
|
||||||
|
|
|
@ -1,5 +1,76 @@
|
||||||
<div id="login">
|
<div id="login">
|
||||||
<p><img src="img/rabbitmqlogo.svg" alt="RabbitMQ logo" width="204" height="37"/></p>
|
<p><img src="img/rabbitmqlogo.svg" alt="RabbitMQ logo" width="204" height="37"/></p>
|
||||||
|
|
||||||
<div id="login-status"><button id="loginWindow" onclick="oauth_initiateLogin()">Click here to log in</button></div>
|
|
||||||
|
<!-- begin login status -->
|
||||||
|
<div id="login-status">
|
||||||
|
<% if (typeof message == 'string') { %>
|
||||||
|
<p class="warning"><%=fmt_string(message)%> </p>
|
||||||
|
<% } %>
|
||||||
|
<% if (typeof errorCode == 'number' && errorCode == 401) { %>
|
||||||
|
<button id="logout" onclick="oauth_initiateLogout()">Click here to logout</button>
|
||||||
|
<%} %>
|
||||||
</div>
|
</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:
|
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
|
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:
|
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
|
=> => 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:
|
To run a suite with a particular docker image you do it like this:
|
||||||
```
|
```
|
||||||
cd deps/rabbitmq_management/selenium
|
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:
|
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:
|
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:
|
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:
|
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
|
**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.
|
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"
|
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
|
#!/usr/bin/env bash
|
||||||
SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
|
||||||
|
#set -x
|
||||||
|
|
||||||
ENV_FILE="/tmp/rabbitmq/.env"
|
ENV_FILE="/tmp/rabbitmq/.env"
|
||||||
FIND_PATH=$1
|
FIND_PATH=$1
|
||||||
ENV_FILE=$2
|
ENV_FILE=$2
|
||||||
|
@ -9,12 +11,40 @@ FIND_PARENT_PATH="$(dirname "$FIND_PATH")"
|
||||||
generate_env_file() {
|
generate_env_file() {
|
||||||
parentdir="$(dirname "$ENV_FILE")"
|
parentdir="$(dirname "$ENV_FILE")"
|
||||||
mkdir -p $parentdir
|
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
|
do
|
||||||
cat $f >> $ENV_FILE
|
FILE_ARRAY+=($f)
|
||||||
done
|
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
|
generate_env_file
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
#set -x
|
||||||
|
|
||||||
SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
SUITE=$(caller)
|
SUITE=$(caller)
|
||||||
SUITE=$(basename "${SUITE}" .sh )
|
SUITE=$(basename "${SUITE}" .sh )
|
||||||
|
|
||||||
SELENIUM_DOCKER_IMAGE=selenium/standalone-chrome:103.0
|
tabs 1
|
||||||
UAA_DOCKER_IMAGE=cloudfoundry/uaa:75.21.0
|
declare -i PADDING_LEVEL=0
|
||||||
KEYCLOAK_DOCKER_IMAGE=quay.io/keycloak/keycloak:20.0
|
declare -i STEP=1
|
||||||
HTTPD_DOCKER_IMAGE=httpd:latest
|
|
||||||
PADDING=""
|
|
||||||
declare -a REQUIRED_COMPONENTS
|
declare -a REQUIRED_COMPONENTS
|
||||||
|
|
||||||
find_selenium_dir() {
|
find_selenium_dir() {
|
||||||
|
@ -32,6 +32,13 @@ SCREENS=${SELENIUM_ROOT_FOLDER}/screens/${SUITE}
|
||||||
CONF_DIR=/tmp/selenium/${SUITE}
|
CONF_DIR=/tmp/selenium/${SUITE}
|
||||||
ENV_FILE=$CONF_DIR/.env
|
ENV_FILE=$CONF_DIR/.env
|
||||||
|
|
||||||
|
for f in $SCRIPT/components/*; do
|
||||||
|
if [[ ! "$f" == *README.md ]]
|
||||||
|
then
|
||||||
|
source $f;
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
parse_arguments() {
|
parse_arguments() {
|
||||||
if [[ "$#" -gt 0 ]]
|
if [[ "$#" -gt 0 ]]
|
||||||
then
|
then
|
||||||
|
@ -55,18 +62,26 @@ parse_arguments() {
|
||||||
|
|
||||||
COMMAND=$(parse_arguments $@)
|
COMMAND=$(parse_arguments $@)
|
||||||
|
|
||||||
tabs 4
|
|
||||||
|
|
||||||
|
|
||||||
print() {
|
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() {
|
begin() {
|
||||||
print "\n$@"
|
print "\n[$STEP] $@"
|
||||||
PADDING="${PADDING}\t"
|
PADDING_LEVEL=$(($PADDING_LEVEL + 1))
|
||||||
|
STEP=$(($STEP + 1))
|
||||||
}
|
}
|
||||||
end() {
|
end() {
|
||||||
PADDING=`echo $PADDING | rev | cut -c 4- | rev`
|
PADDING_LEVEL=$(($PADDING_LEVEL - 1))
|
||||||
print "$@"
|
print "$@"
|
||||||
}
|
}
|
||||||
ensure_docker_network() {
|
ensure_docker_network() {
|
||||||
|
@ -92,39 +107,27 @@ init_suite() {
|
||||||
print "> PROFILES: ${PROFILES} "
|
print "> PROFILES: ${PROFILES} "
|
||||||
print "> ENV_FILE: ${ENV_FILE} "
|
print "> ENV_FILE: ${ENV_FILE} "
|
||||||
print "> COMMAND: ${COMMAND}"
|
print "> COMMAND: ${COMMAND}"
|
||||||
end "Initialized suite ..."
|
end "Initialized suite"
|
||||||
|
|
||||||
|
mkdir -p ${LOGS}/${SUITE}
|
||||||
|
mkdir -p ${SCREENS}/${SUITE}
|
||||||
}
|
}
|
||||||
|
|
||||||
build_mocha_image() {
|
build_mocha_image() {
|
||||||
begin "Ensuring mocha-test image ..."
|
begin "Ensuring mocha-test image ..."
|
||||||
tag=($(md5sum $SELENIUM_ROOT_FOLDER/package.json))
|
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/..
|
docker build -t mocha-test:$tag --target test $SCRIPT/..
|
||||||
print "> Built docker image mocha-test:$tag"
|
print "> Built docker image mocha-test:$tag"
|
||||||
fi
|
fi
|
||||||
end "mocha-test image exists"
|
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() {
|
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() {
|
wait_for_message() {
|
||||||
attemps_left=10
|
attemps_left=10
|
||||||
|
@ -135,185 +138,36 @@ wait_for_message() {
|
||||||
((attemps_left--))
|
((attemps_left--))
|
||||||
if [[ "$attemps_left" -lt 1 ]]; then
|
if [[ "$attemps_left" -lt 1 ]]; then
|
||||||
print "Timed out waiting"
|
print "Timed out waiting"
|
||||||
|
save_container_log $1
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
done
|
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() {
|
wait_for_oidc_endpoint() {
|
||||||
NAME=$1
|
NAME=$1
|
||||||
BASE_URL=$2
|
BASE_URL=$2
|
||||||
if [[ $BASE_URL == *"localhost"** ]]; then
|
if [[ $BASE_URL == *"localhost"** || $BASE_URL == *"0.0.0.0"** ]]; then
|
||||||
wait_for_oidc_endpoint_local $NAME $BASE_URL
|
wait_for_oidc_endpoint_local $@
|
||||||
else
|
else
|
||||||
wait_for_oidc_endpoint_docker $NAME $BASE_URL
|
wait_for_oidc_endpoint_docker $@
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
wait_for_oidc_endpoint_local() {
|
wait_for_oidc_endpoint_local() {
|
||||||
NAME=$1
|
NAME=$1
|
||||||
BASE_URL=$2
|
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
|
max_retry=10
|
||||||
counter=0
|
counter=0
|
||||||
print "Waiting for OIDC discovery endpoint $NAME ... (BASE_URL: $BASE_URL)"
|
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
|
do
|
||||||
sleep 5
|
sleep $DELAY_BETWEEN_ATTEMPTS
|
||||||
[[ counter -eq $max_retry ]] && print "Failed!" && exit 1
|
[[ counter -eq $max_retry ]] && print "Failed!" && exit 1
|
||||||
print "Trying again. Try #$counter"
|
print "Trying again. Try #$counter"
|
||||||
((counter++))
|
((counter++))
|
||||||
|
@ -323,13 +177,20 @@ wait_for_oidc_endpoint_local() {
|
||||||
wait_for_oidc_endpoint_docker() {
|
wait_for_oidc_endpoint_docker() {
|
||||||
NAME=$1
|
NAME=$1
|
||||||
BASE_URL=$2
|
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
|
max_retry=10
|
||||||
counter=0
|
counter=0
|
||||||
print "Waiting for OIDC discovery endpoint $NAME ... (BASE_URL: $BASE_URL)"
|
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
|
do
|
||||||
sleep 5
|
sleep $DELAY_BETWEEN_ATTEMPTS
|
||||||
[[ counter -eq $max_retry ]] && print "Failed!" && exit 1
|
[[ counter -eq $max_retry ]] && print "Failed!" && exit 1
|
||||||
print "Trying again. Try #$counter"
|
print "Trying again. Try #$counter"
|
||||||
((counter++))
|
((counter++))
|
||||||
|
@ -339,156 +200,6 @@ wait_for_oidc_endpoint_docker() {
|
||||||
calculate_rabbitmq_url() {
|
calculate_rabbitmq_url() {
|
||||||
echo "${RABBITMQ_SCHEME:-http}://$1${PUBLIC_RABBITMQ_PATH:-$RABBITMQ_PATH}"
|
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() {
|
wait_for_url() {
|
||||||
BASE_URL=$1
|
BASE_URL=$1
|
||||||
|
@ -525,42 +236,6 @@ wait_for_url_docker() {
|
||||||
done
|
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() {
|
test() {
|
||||||
kill_container_if_exist mocha
|
kill_container_if_exist mocha
|
||||||
begin "Running tests with env variables:"
|
begin "Running tests with env variables:"
|
||||||
|
@ -569,7 +244,11 @@ test() {
|
||||||
PUBLIC_RABBITMQ_HOST=${PUBLIC_RABBITMQ_HOST:-$RABBITMQ_HOST}
|
PUBLIC_RABBITMQ_HOST=${PUBLIC_RABBITMQ_HOST:-$RABBITMQ_HOST}
|
||||||
RABBITMQ_URL=$(calculate_rabbitmq_url $PUBLIC_RABBITMQ_HOST)
|
RABBITMQ_URL=$(calculate_rabbitmq_url $PUBLIC_RABBITMQ_HOST)
|
||||||
RABBITMQ_HOSTNAME=${RABBITMQ_HOSTNAME:-rabbitmq}
|
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_HOST: ${RABBITMQ_HOST}"
|
||||||
print "> RABBITMQ_HOSTNAME: ${RABBITMQ_HOSTNAME}"
|
print "> RABBITMQ_HOSTNAME: ${RABBITMQ_HOSTNAME}"
|
||||||
print "> PUBLIC_RABBITMQ_HOST: ${PUBLIC_RABBITMQ_HOST}"
|
print "> PUBLIC_RABBITMQ_HOST: ${PUBLIC_RABBITMQ_HOST}"
|
||||||
|
@ -588,6 +267,8 @@ test() {
|
||||||
--env UAA_URL=${UAA_URL} \
|
--env UAA_URL=${UAA_URL} \
|
||||||
--env FAKE_PORTAL_URL=${FAKEPORTAL_URL} \
|
--env FAKE_PORTAL_URL=${FAKEPORTAL_URL} \
|
||||||
--env RUN_LOCAL=false \
|
--env RUN_LOCAL=false \
|
||||||
|
--env SELENIUM_TIMEOUT=${SELENIUM_TIMEOUT} \
|
||||||
|
--env SELENIUM_POLLING=${SELENIUM_POLLING} \
|
||||||
--env PROFILES="${PROFILES}" \
|
--env PROFILES="${PROFILES}" \
|
||||||
--env ENV_FILE="/code/.env" \
|
--env ENV_FILE="/code/.env" \
|
||||||
-v ${TEST_DIR}:/code/test \
|
-v ${TEST_DIR}:/code/test \
|
||||||
|
@ -605,9 +286,17 @@ save_logs() {
|
||||||
save_container_logs selenium
|
save_container_logs selenium
|
||||||
}
|
}
|
||||||
save_container_logs() {
|
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() {
|
profiles_with_local_or_docker() {
|
||||||
if [[ "$PROFILES" != *"local"* && "$PROFILES" != *"docker"* ]]; then
|
if [[ "$PROFILES" != *"local"* && "$PROFILES" != *"docker"* ]]; then
|
||||||
echo "$PROFILES docker"
|
echo "$PROFILES docker"
|
||||||
|
@ -616,9 +305,11 @@ profiles_with_local_or_docker() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
generate_env_file() {
|
generate_env_file() {
|
||||||
|
begin "Generating env file ..."
|
||||||
mkdir -p $CONF_DIR
|
mkdir -p $CONF_DIR
|
||||||
${BIN_DIR}/gen-env-file $TEST_CONFIG_DIR $ENV_FILE
|
${BIN_DIR}/gen-env-file $TEST_CONFIG_DIR $ENV_FILE
|
||||||
source $ENV_FILE
|
source $ENV_FILE
|
||||||
|
end "Finished generating env file."
|
||||||
}
|
}
|
||||||
run() {
|
run() {
|
||||||
runWith rabbitmq
|
runWith rabbitmq
|
||||||
|
@ -640,6 +331,7 @@ run_local_with() {
|
||||||
generate_env_file
|
generate_env_file
|
||||||
build_mocha_image
|
build_mocha_image
|
||||||
|
|
||||||
|
|
||||||
if [[ "$COMMAND" == "start-rabbitmq" ]]
|
if [[ "$COMMAND" == "start-rabbitmq" ]]
|
||||||
then
|
then
|
||||||
start_local_rabbitmq
|
start_local_rabbitmq
|
||||||
|
@ -707,7 +399,7 @@ teardown_local_others() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
test_local() {
|
test_local() {
|
||||||
begin "Running local test $1"
|
begin "Running local test ${1:-}"
|
||||||
|
|
||||||
RABBITMQ_HOST=${RABBITMQ_HOST:-rabbitmq:15672}
|
RABBITMQ_HOST=${RABBITMQ_HOST:-rabbitmq:15672}
|
||||||
PUBLIC_RABBITMQ_HOST=${PUBLIC_RABBITMQ_HOST:-$RABBITMQ_HOST}
|
PUBLIC_RABBITMQ_HOST=${PUBLIC_RABBITMQ_HOST:-$RABBITMQ_HOST}
|
||||||
|
@ -715,7 +407,11 @@ test_local() {
|
||||||
export RABBITMQ_HOSTNAME=${RABBITMQ_HOSTNAME:-rabbitmq}
|
export RABBITMQ_HOSTNAME=${RABBITMQ_HOSTNAME:-rabbitmq}
|
||||||
export RABBITMQ_AMQP_USERNAME=${RABBITMQ_AMQP_USERNAME}
|
export RABBITMQ_AMQP_USERNAME=${RABBITMQ_AMQP_USERNAME}
|
||||||
export RABBITMQ_AMQP_PASSWORD=${RABBITMQ_AMQP_PASSWORD}
|
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_HOST: ${RABBITMQ_HOST}"
|
||||||
print "> RABBITMQ_HOSTNAME: ${RABBITMQ_HOSTNAME}"
|
print "> RABBITMQ_HOSTNAME: ${RABBITMQ_HOSTNAME}"
|
||||||
print "> PUBLIC_RABBITMQ_HOST: ${PUBLIC_RABBITMQ_HOST}"
|
print "> PUBLIC_RABBITMQ_HOST: ${PUBLIC_RABBITMQ_HOST}"
|
||||||
|
@ -727,6 +423,7 @@ test_local() {
|
||||||
export RUN_LOCAL=true
|
export RUN_LOCAL=true
|
||||||
export SCREENSHOTS_DIR=${SCREENS}
|
export SCREENSHOTS_DIR=${SCREENS}
|
||||||
|
|
||||||
|
export PROFILES
|
||||||
export ENV_FILE
|
export ENV_FILE
|
||||||
npm test $TEST_CASES_DIR/$1
|
npm test $TEST_CASES_DIR/$1
|
||||||
|
|
||||||
|
@ -743,17 +440,19 @@ teardown_components() {
|
||||||
begin "Tear down ..."
|
begin "Tear down ..."
|
||||||
for i in "${REQUIRED_COMPONENTS[@]}"
|
for i in "${REQUIRED_COMPONENTS[@]}"
|
||||||
do
|
do
|
||||||
print "Tear down $i"
|
local component="$i"
|
||||||
$(kill_container_if_exist $i)
|
print "Tear down $component"
|
||||||
|
kill_container_if_exist "$component"
|
||||||
done
|
done
|
||||||
end "Finished teardown"
|
end "Finished teardown"
|
||||||
}
|
}
|
||||||
save_components_logs() {
|
save_components_logs() {
|
||||||
begin "Saving Logs to $LOGS ..."
|
begin "Saving Logs to $LOGS for ${REQUIRED_COMPONENTS[@]} ..."
|
||||||
for i in "${REQUIRED_COMPONENTS[@]}"
|
for i in "${REQUIRED_COMPONENTS[@]}"
|
||||||
do
|
do
|
||||||
print "Saving logs for $i"
|
local component="$i"
|
||||||
$(save_container_logs $i)
|
print "Saving logs for component $component"
|
||||||
|
save_container_logs "$component"
|
||||||
done
|
done
|
||||||
end "Finished saving logs"
|
end "Finished saving logs"
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"test": "mocha --recursive --trace-warnings --timeout 45000",
|
||||||
"fakeportal": "node fakeportal/app.js",
|
"fakeportal": "node fakeportal/app.js",
|
||||||
"fakeproxy": "node fakeportal/proxy.js",
|
"fakeproxy": "node fakeportal/proxy.js",
|
||||||
"amqp10_roundtriptest": "eval $(cat $ENV_FILE ) &&./run-amqp10-roundtriptest",
|
"amqp10_roundtriptest": "eval $(cat $ENV_FILE ) &&./run-amqp10-roundtriptest",
|
||||||
|
@ -13,7 +14,7 @@
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chromedriver": "^118.0.0",
|
"chromedriver": "^119.0.0",
|
||||||
"ejs": "^3.1.8",
|
"ejs": "^3.1.8",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"geckodriver": "^3.0.2",
|
"geckodriver": "^3.0.2",
|
||||||
|
@ -21,7 +22,7 @@
|
||||||
"mqtt": "^5.3.3",
|
"mqtt": "^5.3.3",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"proxy": "^1.0.2",
|
"proxy": "^1.0.2",
|
||||||
"selenium-webdriver": "^4.4.0",
|
"selenium-webdriver": "^4.15.0",
|
||||||
"xmlhttprequest": "^1.8.0"
|
"xmlhttprequest": "^1.8.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
|
||||||
TEST_CASES_PATH=/authnz-msg-protocols
|
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
|
source $SCRIPT/../../bin/suite_template
|
||||||
runWith mock-auth-backend-http
|
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_CASES_PATH=/oauth/with-idp-initiated-via-proxy
|
||||||
TEST_CONFIG_PATH=/oauth
|
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 $@
|
source $SCRIPT/../../bin/suite_template $@
|
||||||
runWith uaa fakeportal fakeproxy
|
runWith uaa fakeportal fakeproxy
|
||||||
|
|
|
@ -4,7 +4,7 @@ SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
|
||||||
TEST_CASES_PATH=/oauth/with-idp-initiated
|
TEST_CASES_PATH=/oauth/with-idp-initiated
|
||||||
TEST_CONFIG_PATH=/oauth
|
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 $@
|
source $SCRIPT/../../bin/suite_template $@
|
||||||
runWith uaa fakeportal
|
runWith uaa fakeportal
|
||||||
|
|
|
@ -4,7 +4,7 @@ SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
|
||||||
TEST_CASES_PATH=/oauth/with-idp-initiated-via-proxy
|
TEST_CASES_PATH=/oauth/with-idp-initiated-via-proxy
|
||||||
TEST_CONFIG_PATH=/oauth
|
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 $@
|
source $SCRIPT/../../bin/suite_template $@
|
||||||
runWith uaa fakeportal fakeproxy
|
runWith uaa fakeportal fakeproxy
|
||||||
|
|
|
@ -4,7 +4,7 @@ SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
|
||||||
TEST_CASES_PATH=/oauth/with-idp-initiated
|
TEST_CASES_PATH=/oauth/with-idp-initiated
|
||||||
TEST_CONFIG_PATH=/oauth
|
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 $@
|
source $SCRIPT/../../bin/suite_template $@
|
||||||
runWith uaa fakeportal
|
runWith uaa fakeportal
|
||||||
|
|
|
@ -4,7 +4,7 @@ SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
|
||||||
TEST_CASES_PATH=/oauth/with-sp-initiated
|
TEST_CASES_PATH=/oauth/with-sp-initiated
|
||||||
TEST_CONFIG_PATH=/oauth
|
TEST_CONFIG_PATH=/oauth
|
||||||
PROFILES="keycloak jwks keycloak-oauth-provider"
|
PROFILES="keycloak keycloak-oauth-provider keycloak-mgt-oauth-provider "
|
||||||
|
|
||||||
source $SCRIPT/../../bin/suite_template $@
|
source $SCRIPT/../../bin/suite_template $@
|
||||||
runWith keycloak
|
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_CASES_PATH=/oauth/with-sp-initiated
|
||||||
TEST_CONFIG_PATH=/oauth
|
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 $@
|
source $SCRIPT/../../bin/suite_template $@
|
||||||
runWith uaa
|
runWith uaa
|
||||||
|
|
|
@ -4,7 +4,7 @@ SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
|
||||||
TEST_CASES_PATH=/oauth/with-idp-down
|
TEST_CASES_PATH=/oauth/with-idp-down
|
||||||
TEST_CONFIG_PATH=/oauth
|
TEST_CONFIG_PATH=/oauth
|
||||||
PROFILES="uaa uaa-oauth-provider"
|
PROFILES="uaa uaa-oauth-provider uaa-mgt-oauth-provider"
|
||||||
|
|
||||||
source $SCRIPT/../../bin/suite_template $@
|
source $SCRIPT/../../bin/suite_template $@
|
||||||
run
|
run
|
||||||
|
|
|
@ -4,7 +4,7 @@ SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
|
||||||
TEST_CASES_PATH=/oauth/with-sp-initiated
|
TEST_CASES_PATH=/oauth/with-sp-initiated
|
||||||
TEST_CONFIG_PATH=/oauth
|
TEST_CONFIG_PATH=/oauth
|
||||||
PROFILES="uaa uaa-oauth-provider"
|
PROFILES="uaa uaa-oauth-provider uaa-mgt-oauth-provider"
|
||||||
|
|
||||||
source $SCRIPT/../../bin/suite_template $@
|
source $SCRIPT/../../bin/suite_template $@
|
||||||
runWith uaa
|
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,
|
[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_http,rabbitmq_auth_backend_ldap,
|
||||||
rabbitmq_auth_backend_oauth2,rabbitmq_auth_mechanism_ssl,rabbitmq_aws,
|
rabbitmq_auth_backend_oauth2,rabbitmq_auth_mechanism_ssl,rabbitmq_aws,
|
||||||
rabbitmq_consistent_hash_exchange,rabbitmq_event_exchange,
|
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