Skip to content

Commit

Permalink
Merge pull request #7365 from rabbitmq/oauth2-login-with-authorizatio…
Browse files Browse the repository at this point in the history
…n-header

Read JWT token from Authorization header to login to management ui
  • Loading branch information
michaelklishin authored Apr 4, 2023
2 parents 124ccda + 829d9d9 commit f1af3bb
Show file tree
Hide file tree
Showing 71 changed files with 750 additions and 645 deletions.
1 change: 1 addition & 0 deletions .github/workflows/test-selenium.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ jobs:
path: |
logs/*
screens/*
/tmp/selenium/*
summary-selenium:
needs:
Expand Down
3 changes: 2 additions & 1 deletion deps/rabbitmq_management/priv/www/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@
<script src="js/prefs.js" type="text/javascript"></script>
<script src="js/formatters.js" type="text/javascript"></script>
<script src="js/charts.js" type="text/javascript"></script>
<script src="js/oidc-oauth/oidc-client-ts.js" type="text/javascript"></script>
<script src="js/oidc-oauth/helper.js"></script>
<script src="js/oidc-oauth/oidc-client-ts.js" type="text/javascript"></script>
<script src="js/oidc-oauth/bootstrap.js"></script>

<link href="css/main.css" rel="stylesheet" type="text/css"/>
<link href="favicon.ico" rel="shortcut icon" type="image/x-icon"/>
Expand Down
15 changes: 11 additions & 4 deletions deps/rabbitmq_management/priv/www/js/oidc-oauth/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
var mgr;
var _management_logger;

function oauth_initialize_if_required() {
/*
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
Expand All @@ -18,10 +19,16 @@ function oauth_initialize_if_required() {
}
}
*/

function rabbit_base_uri() {
rabbit_port = window.location.port ? ":" + window.location.port : ""
rabbit_path_prefix = window.location.pathname.replace(/(\/js\/oidc-oauth\/.*$|\/+$)/, "")
return window.location.protocol + "//" + window.location.hostname + rabbit_port + rabbit_path_prefix
}

function auth_settings_apply_defaults(authSettings) {
if (authSettings.enable_uaa == "true") {
if (authSettings.enable_uaa) {

if (!authSettings.oauth_provider_url) {
authSettings.oauth_provider_url = authSettings.uaa_location
Expand Down Expand Up @@ -73,8 +80,8 @@ function oauth_initialize(authSettings) {
response_type: authSettings.oauth_response_type,
scope: authSettings.oauth_scopes, // for uaa we may need to include <resource-server-id>.*
resource: authSettings.oauth_resource_id,
redirect_uri: rabbit_base_uri + "/js/oidc-oauth/login-callback.html",
post_logout_redirect_uri: rabbit_base_uri + "/",
redirect_uri: rabbit_base_uri() + "/js/oidc-oauth/login-callback.html",
post_logout_redirect_uri: rabbit_base_uri() + "/",

automaticSilentRenew: true,
revokeAccessTokenOnSignout: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
<script src="../prefs.js" ></script>
<script src="./oidc-client-ts.js" ></script>
<script src="./helper.js"></script>
<script src="./bootstrap.js"></script>
<script type="text/javascript">

if (oauth_initialize_if_required()) oauth_completeLogin()
</script>
</body>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<body>
<script src="./oidc-client-ts.js" ></script>
<script src="./helper.js"></script>
<script src="./bootstrap.js"></script>
<script type="text/javascript">
if (oauth_initialize_if_required()) {
oauth_completeLogout()
Expand Down
144 changes: 72 additions & 72 deletions deps/rabbitmq_management/selenium/README.md
Original file line number Diff line number Diff line change
@@ -1,71 +1,47 @@
# Automated End-to-End testing of the management ui with Selenium

We are using Selenium webdriver to simulate running the management ui in a browser.
And Mocha as the testing framework for Javascript.
Selenium webdriver is used to drive web browser's interactions on the management ui.
And Mocha is used as the testing framework for Javascript.

To run the tests we need:
The following must be installed to run the tests:
- make
- docker
- curl

# How tests are organized
# Organization of test cases

All test cases and their configuration files are under the `test` folder and grouped into subfolders based on the area of functionality they are testing. For instance, under `oauth` folder, we have test cases about OAuth 2.0.
Furthermore, within an area of functionality like OAuth 2.0 we want to test under different configuration.
For instance under `test/oauth/with-sp-initiated` we group all test cases which run against an Authorization server via the Authorization Code Flow. Whereas under `test/oauth/with-idp-down` we group all test cases which run against an Authorization server which is down.
`test` folder contains the test cases written in Javascript using Selenium webdriver. Test cases are grouped into folders based on the area of functionality.
For instance, `test/basic-auth` contains test cases that validates basic authentication. Another example, a bit
more complex, is `test/oauth` where the test cases are stored in subfolders. For instance, `test/oauth/with-sp-initiated` which validate OAuth 2 authorization where users come to RabbitMQ without any token and RabbitMQ initiates the authorization process.

And under `suites` folder we have the test suites. A test suite is a script which executes all test cases
under a folder and using a given configuration and launching a number of components the test suite depends on.
The `test` folder also contains the necessary configuration files. For instance, `test/basic-auth` contains `rabbitmq.conf` file which is also shared by other test cases such as `test/definitions` or `test/limits`.

Some test cases only depend on RabbitMQ such as the `basic-auth.sh`. In this test suite, we specify the location of the test case relative to the `test` folder.
And we call the function `run`. We always have to source the file under `bin/suite_template`.
`suites` folder contains one bash script per test suite. A test suite executes all the test cases under
a folder with certain configuration. More on configuration on the next section.

```
TEST_CASES_PATH=/basic-auth
source $SCRIPT/../bin/suite_template
run
````

When our test suite requires of other components such as UAA, we use the function call `runWith` as we see below for the test case `oauth-with-uaa.sh`. In this test case, in addition to declaring the location of the test case, we also specify the location of the configuration files required to launch the components this suite
depends on. We also specify which profiles are activated which in turn defines the settings and configuration files used in the suite. And said earlier, the suite runs via the function `runWith` specifying the components that should be started and stopped and its logs captured too.

```
TEST_CASES_PATH=/oauth/with-sp-initiated
TEST_CONFIG_PATH=/oauth
PROFILES="uaa uaa-oauth-provider"
source $SCRIPT/../bin/suite_template
runWith uaa
```
`bin` folder contains as it is expected utility scripts used to run the test suites.


# How to run the tests

There are two ways to run the tests.

**The interactive mode** - If we are writing code in any of RabbitMQ plugins or
libraries, we most likely want to run tests interactively, i.e. against our local installed Chrome browser, so that we
can see the simulated user interactions in the browser. In this mode, we are also running RabbitMQ directly
from source to speed things up. Otherwise, we would have to build a docker image for every change we wanted to test.
**Headless mode** - This is the mode used by the CI. But you can also run it locally. In this mode, you do
not see any browser interaction, everything happens in the background, i.e. rabbitmq, tests, the browser, and any component the test depends on such as UAA.

**Headless mode** - If we are not making any code changes to RabbitMQ and instead
we are only writing tests or simply we want to run them, then we want to run the tests in headless mode.
**The interactive mode** - This mode is convenient when we are still working on RabbitMQ source code and/or in the selenium tests. In this mode, you run RabbitMQ and tests directly from source to speed things up. The components, such as, UAA or keycloak, run in docker.

**IMPORTANT**: RabbitMQ and UAA configuration is different for each mode. The reason is the hostname used
for both, RabbitMQ and UAA. In headless mode, everything runs in containers and we refer to each container but its
name, e.g. `rabbitmq` or `uaa`. Whereas in interactive mode, we run most of the components locally (i.e `localhost`), such as RabbitMQ or UAA.

## Run tests in headless-mode

In this mode, we run suite of tests. This is how to run one suite:
To run just one suite, you proceed as follows:
```
suites/oauth-with-uaa.sh
```

And this is how we run all suites:
And to is run all suites, like the CI does, you run:
```
run-suites.sh
./run-suites.sh
```

If you want to test your local changes, you can still build an image with these 2 commands from the
Expand All @@ -81,61 +57,85 @@ The last command prints something like this:
=> => naming to docker.io/pivotalrabbitmq/rabbitmq:3.11.0-rc.2.51.g4f3e539.dirty 0.0s
```

To run a suite with a particular docker image we do it like this:
To run a suite with a particular docker image you do it like this:
```
cd deps/rabbitmq_management/selenium/suites
RABBITMQ_DOCKER_IMAGE=pivotalrabbitmq/rabbitmq:3.11.0-rc.2.51.g4f3e539.dirty ./oauth-with-uaa-with-mgt-prefix.sh
cd deps/rabbitmq_management/selenium
RABBITMQ_DOCKER_IMAGE=pivotalrabbitmq/rabbitmq:3.11.0-rc.2.51.g4f3e539.dirty suites/oauth-with-uaa-with-mgt-prefix.sh
```

## Run tests interactively using your local chrome browser

In this mode when we are actively developing and hence we need full control of the
runtime environment for the tests.
For this reason, every test suite should have its own Makefile to help developer bootstrap
the required runtime.

Let's say we want to run the test suite `test/oauth/with-uaa`. We proceed as follows:

Get node.js dependencies ready. Selenium tests are ultimately written in Javascript and
requires node.js runtime to run them:
First you make sure that you have Node.js ready to run the test cases.
```
cd selenium
npm install
```

Start UAA:
Before you can run a single test case or all the test cases for a suite, you need to
run RabbitMQ from source and all the components the test cases depends on, if any.

For instance, say you want to run the test cases for the suite `suites/oauth-with-uaa.sh`.

First, open a terminal and launch RabbitMQ in the foreground:
```
cd test/oauth
make start-uaa
suites/oauth-with-uaa.sh start-rabbitmq
```

Start RabbitMQ from source (it runs `make run-broker`):
Then, launch all the components, the suite depends on, in the background:
```
make start-rabbitmq
suites/oauth-with-uaa.sh start-others
```

To run all tests under `with-uaa`:
And finally, run all the test cases for the suite:
```
make test TEST=with-uaa
suites/oauth-with-uaa.sh test
```
Or to run a single tests under the suite:

Or just one test case:
```
make test TEST=with-uaa/landing.js
suites/oauth-with-uaa.sh test happy-login.js
```

**VERY IMPORTANT NOTE**: `make start-rabbitmq` will always load `rabbitmq-localhost.config`
regardless of the test suite we are running. Therefore, if your suite requires a specific
configuration ensure that configuration is in `rabbitmq-localhost.config`.
**NOTE**: Nowadays, it is not possible to run all test in interactive mode. It is doable but it has not
been implemented yet.


## Test case configuration

RabbitMQ and other components such as UAA, or Keycloak, require configuration files which varies
depending on the test case scenario. These configuration files must be dynamically generated using these two other files:
- one or many configuration files
- and one or many **.env** file which declare environment variables used to template the former configuration file in order to generate a final configuration file

Configuration files may contain reference to environment variables. And configuration files
may should follow this naming convention: `<prefix>[.<profile>]*<suffix>`. For instance:
- `basic-auth/rabbitmq.conf` It is a configuration file whose **prefix** is `rabbitmq`, the **suffix** is `.conf` and it has no profile associated to it. Inside, it has no reference to environment variables hence the final
configuration file is the raw configuration file.
- `oauth/rabbitmq.conf` Same as `basic-auth/rabbitmq.conf` but this file does have reference to environment variables so the final file will have those variable replaced with their final values
- `oauth/rabbitmq.mgt-prefix.conf` It is a configuration file with the profile `mgt-prefix`

The .env files should follow the naming convention: `.env.<profile>[.<profile>]*`. For instance:
- `.env.docker` It is an .env file which is used when the profile `docker` is activated
- `oauth/.env.docker.uaa` It is a .env file used when using `oauth` as test folder and the profiles `docker` and `uaa` are both activated

If you had a specific configuration file, such as `rabbitmq-localhost-keycloak.config` you can run
`make start-rabbitmq` with that configuration like this:
To generate a `rabbitmq.conf` file the process is as follows:
1. Merge any applicable .env file from the test case's configuration folder and from the parent folder, i.e. under `/test` folder and generate a `/tmp/rabbitmq/.env` file
2. Merge any applicable rabbitmq.conf file from the test case's configuration and resolve all the environment variable using `/tmp/rabbitmq/.env` file to produce `/tmp/selenium/<test-suite-name>/rabbitmq/rabbitmq.conf`

## Profiles

The most common profiles are:
- `docker` profile used to indicate that RabbitMQ, the tests and selenium+browser run in docker. This profile is automatically activated when running in headless mode
- `local` profile used to indicate that RabbitMQ and the tests and the browser run locally. This profile is
automatically activated when running in interactive mode

The rest of the components the test cases depends on will typically run in docker such as uaa, keycloak, and the rest.

Besides these two profiles, mutually exclusive, you can have as many profiles as needed. It is just a matter of naming the appropriate file (.env, or rabbitmq.conf, etc) with the profile and activating the profile in the test suite script. For instance `suites/oauth-with-uaa.sh` activates two profiles by declaring them in `PROFILES` environment variable as shown below:
```
make RABBITMQ_CONFIG_FILE=rabbitmq-localhost-keycloak.config start-rabbitmq
PROFILES="uaa uaa-oauth-provider"
```

We do not have this issue when we run the headless suites because they use dedicated files
for each suite. Doing the same when running locally, i.e using `localhost`, would be too tedious.

## Chrome vs Chrome driver version mismatch

If you find the following error when you first attempt to run one of the selenium tests
Expand Down
20 changes: 20 additions & 0 deletions deps/rabbitmq_management/selenium/bin/gen-httpd-conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

#set -x

TEST_PATH=${1:?First parameter is the directory env and config files are relative to}
ENV_FILE=${2:?Second parameter is a comma-separated list of .env file which has exported template variables}
FINAL_CONFIG_FILE=${3:?Forth parameter is the name of the final config file. It is relative to where this script is run from}

source $ENV_FILE

parentdir="$(dirname "$FINAL_CONFIG_FILE")"
mkdir -p $parentdir

echo "" > $FINAL_CONFIG_FILE

for f in $($SCRIPT/find-template-files $TEST_PATH "httpd" "conf")
do
envsubst < $f >> $FINAL_CONFIG_FILE
done
3 changes: 3 additions & 0 deletions deps/rabbitmq_management/selenium/bin/gen-keycloak-json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ FINAL_CONFIG_FILE=${3:?Forth parameter is the name of the final config file. It

source $ENV_FILE

parentdir="$(dirname "$FINAL_CONFIG_FILE")"
mkdir -p $parentdir

echo "" > $FINAL_CONFIG_FILE

for f in $($SCRIPT/find-template-files $KEYCLOAK_PATH "test-realm" "json")
Expand Down
3 changes: 3 additions & 0 deletions deps/rabbitmq_management/selenium/bin/gen-rabbitmq-conf
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ FINAL_CONFIG_FILE=${3:?Forth parameter is the name of the final config file. It

source $ENV_FILE

parentdir="$(dirname "$FINAL_CONFIG_FILE")"
mkdir -p $parentdir

echo "" > $FINAL_CONFIG_FILE

for f in $($SCRIPT/find-template-files $TEST_PATH "rabbitmq" "conf")
Expand Down
3 changes: 3 additions & 0 deletions deps/rabbitmq_management/selenium/bin/gen-uaa-yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ FINAL_CONFIG_FILE=${3:?Forth parameter is the name of the final config file. It

source $ENV_FILE

parentdir="$(dirname "$FINAL_CONFIG_FILE")"
mkdir -p $parentdir

echo "" > $FINAL_CONFIG_FILE

for f in $($SCRIPT/find-template-files $UAA_PATH "uaa" "yml")
Expand Down
Loading

0 comments on commit f1af3bb

Please sign in to comment.