Merge pull request #11448 from rabbitmq/dynamic-load-oauth-deps-ui

Dynamically load oauth-related libraries
This commit is contained in:
Michael Klishin 2024-06-20 21:37:51 -04:00 committed by GitHub
commit c67c940bcc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 503 additions and 230 deletions

View File

@ -16,48 +16,17 @@
<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/helper.js"></script>
<script src="js/oidc-oauth/oidc-client-ts.js" type="text/javascript"></script>
<script src="js/oidc-oauth/bootstrap.js"></script>
<script src="js/oidc-oauth/bootstrap.js" type="module"></script>
<link href="css/main.css" rel="stylesheet" type="text/css"/>
<link href="favicon.ico" rel="shortcut icon" type="image/x-icon"/>
<script type="application/javascript">
var oauth = oauth_initialize_if_required();
if (oauth.enabled) {
if (!oauth.sp_initiated) {
oauth.logged_in = has_auth_credentials();
oauth.access_token = get_auth_credentials(); // DEPRECATED
} else {
oauth_is_logged_in().then( status => {
if (status.loggedIn && !has_auth_credentials()) {
oauth.logged_in = false;
oauth_initiateLogout();
} else {
if (!status.loggedIn) {
clear_auth();
} else {
oauth.logged_in = true;
oauth.access_token = status.user.access_token; // DEPRECATED
oauth.expiryDate = new Date(status.user.expires_at * 1000); // it is epoch in seconds
let current = new Date();
_management_logger.debug('token expires in ', (oauth.expiryDate-current)/1000,
'secs at : ', oauth.expiryDate );
oauth.user_name = status.user.profile['user_name'];
if (!oauth.user_name || oauth.user_name == '') {
oauth.user_name = status.user.profile['sub'];
}
oauth.scopes = status.user.scope;
}
}
});
}
}
<script type="module">
window.oauth = oauth_initialize_if_required();
</script>
<!--[if lte IE 8]>
<script src="js/excanvas.min.js" type="text/javascript"></script>
<link href="css/evil.css" rel="stylesheet" type="text/css"/>

View File

@ -698,6 +698,7 @@ function DisplayControl() {
}
// Set up the above vars
function setup_global_vars(overview) {
rates_mode = overview.rates_mode;
@ -715,7 +716,7 @@ function setup_global_vars(overview) {
user_name = fmt_escape_html(user.name);
$('#header #logout').prepend(
'User ' + (user_administrator && !oauth.enabled ? '<a href="#/users/' + user_name + '">' + user_name + '</a>' : user_name)
'User ' + (user_administrator && user.is_internal_user ? '<a href="#/users/' + user_name + '">' + user_name + '</a>' : user_name)
);
var product = overview.rabbitmq_version;

View File

@ -4,10 +4,12 @@ $(document).ready(function() {
var url = new URL(url_string);
var error = url.searchParams.get('error');
if (error) {
renderWarningMessageInLoginStatus(fmt_escape_html(error));
if (oauth.enabled) {
renderWarningMessageInLoginStatus(oauth, fmt_escape_html(error));
}
} else {
if (oauth.enabled) {
startWithOAuthLogin();
startWithOAuthLogin(oauth);
} else {
startWithLoginPage();
}
@ -27,85 +29,18 @@ function removeDuplicates(array){
}
return output
}
function warningMessageOAuthResource(oauthResource, reason) {
return "OAuth resource [<b>" + (oauthResource["label"] != null ? oauthResource.label : oauthResource.id) +
"</b>] not available. OpenId Discovery endpoint " + readiness_url(oauthResource) + reason
}
function warningMessageOAuthResources(commonProviderURL, oauthResources, reason) {
return "OAuth resources [ <b>" + oauthResources.map(resource => resource["label"] != null ? resource.label : resource.id).join("</b>,<b>")
+ "</b>] not available. OpenId Discovery endpoint " + commonProviderURL + reason
}
function startWithOAuthLogin () {
function startWithOAuthLogin (oauth) {
store_pref("oauth-return-to", window.location.hash);
if (!oauth.logged_in) {
// Find out how many distinct oauthServers are configured
let oauthServers = removeDuplicates(oauth.resource_servers.filter((resource) => resource.sp_initiated))
oauthServers.forEach(function(entry) { console.log(readiness_url(entry)) })
if (oauthServers.length > 0) { // some resources are sp_initiated but there could be idp_initiated too
Promise.allSettled(oauthServers.map(oauthServer => fetch(readiness_url(oauthServer)).then(res => res.json())))
.then(results => {
results.forEach(function(entry) { console.log(entry) })
let notReadyServers = []
let notCompliantServers = []
for (let i = 0; i < results.length; i++) {
switch (results[i].status) {
case "fulfilled":
try {
validate_openid_configuration(results[i].value)
}catch(e) {
console.log("Unable to connect to " + oauthServers[i].oauth_provider_url + ". " + e)
notCompliantServers.push(oauthServers[i].oauth_provider_url)
}
break
case "rejected":
notReadyServers.push(oauthServers[i].oauth_provider_url)
break
}
}
const spOauthServers = oauth.resource_servers.filter((resource) => resource.sp_initiated)
const groupByProviderURL = spOauthServers.reduce((group, oauthServer) => {
const { oauth_provider_url } = oauthServer;
group[oauth_provider_url] = group[oauth_provider_url] ?? [];
group[oauth_provider_url].push(oauthServer);
return group;
}, {})
let warnings = []
for(var url in groupByProviderURL){
console.log(url + ': ' + groupByProviderURL[url]);
const notReadyResources = groupByProviderURL[url].filter((oauthserver) => notReadyServers.includes(oauthserver.oauth_provider_url))
const notCompliantResources = groupByProviderURL[url].filter((oauthserver) => notCompliantServers.includes(oauthserver.oauth_provider_url))
if (notReadyResources.length == 1) {
warnings.push(warningMessageOAuthResource(notReadyResources[0], " not reachable"))
}else if (notReadyResources.length > 1) {
warnings.push(warningMessageOAuthResources(url, notReadyResources, " not reachable"))
}
if (notCompliantResources.length == 1) {
warnings.push(warningMessageOAuthResource(notCompliantResources[0], " not compliant"))
}else if (notCompliantResources.length > 1) {
warnings.push(warningMessageOAuthResources(url, notCompliantResources, " not compliant"))
}
}
console.log("warnings:" + warnings)
oauth.declared_resource_servers_count = oauth.resource_servers.length
oauth.resource_servers = oauth.resource_servers.filter((resource) =>
!notReadyServers.includes(resource.oauth_provider_url) && !notCompliantServers.includes(resource.oauth_provider_url))
render_login_oauth(warnings)
start_app_login()
})
}else { // there are only idp_initiated resources
render_login_oauth()
start_app_login()
}
hasAnyResourceServerReady(oauth, (oauth, warnings) => { render_login_oauth(oauth, warnings); start_app_login(); })
} else {
start_app_login()
}
}
function render_login_oauth(messages) {
function render_login_oauth(oauth, messages) {
let formatData = {}
formatData.warnings = []
formatData.notAuthorized = false
@ -118,7 +53,6 @@ function render_login_oauth(messages) {
} else if (typeof messages == "string") {
formatData.warnings = [messages]
formatData.notAuthorized = messages == "Not authorized"
console.log("Single error message")
}
replace_content('outer', format('login_oauth', formatData))
@ -127,13 +61,11 @@ function render_login_oauth(messages) {
$('#login').on('click', 'div.section h2, div.section-hidden h2', function() {
toggle_visibility($(this));
});
}
function renderWarningMessageInLoginStatus(message) {
render_login_oauth(message)
function renderWarningMessageInLoginStatus(oauth, message) {
render_login_oauth(oauth, message)
}
function dispatcher_add(fun) {
dispatcher_modules.push(fun);
if (dispatcher_modules.length == extension_count) {
@ -187,9 +119,10 @@ function check_login () {
if (user == false || user.error) {
clear_auth();
if (oauth.enabled) {
hide_popup_warn();
renderWarningMessageInLoginStatus('Not authorized');
//hide_popup_warn();
renderWarningMessageInLoginStatus(oauth, 'Not authorized');
} else {
//hide_popup_warn();
replace_content('login-status', '<p>Login failed</p>');
}
return false;
@ -323,6 +256,7 @@ function dynamic_load(filename) {
element.setAttribute('type', 'text/javascript');
element.setAttribute('src', 'js/' + filename);
document.getElementsByTagName('head')[0].appendChild(element);
return element;
}
function update_interval() {
@ -350,6 +284,10 @@ function update_interval() {
function go_to(url) {
this.location = url;
}
function go_to_home() {
// location.href = rabbit_path_prefix() + "/"
location.href = "/"
}
function set_timer_interval(interval) {
timer_interval = interval;
@ -1472,16 +1410,16 @@ function sync_req(type, params0, path_template, options) {
else
// rabbitmq/rabbitmq-management#732
// https://developer.mozilla.org/en-US/docs/Glossary/Truthy
return {result: true, http_status: req.status, req_params: params};
return {result: true, http_status: req.status, req_params: params, responseText: req.responseText};
}
else {
return false;
}
}
function initiate_logout(error = "") {
function initiate_logout(oauth, error = "") {
clear_pref('auth');
clear_cookie_value('auth');
renderWarningMessageInLoginStatus(error);
renderWarningMessageInLoginStatus(oauth, error);
}
function check_bad_response(req, full_page_404) {
// 1223 == 204 - see https://www.enhanceie.com/ie/bugs.asp
@ -1502,7 +1440,7 @@ function check_bad_response(req, full_page_404) {
if (error == 'bad_request' || error == 'not_found' || error == 'not_authorised' || error == 'not_authorized') {
if ((req.status == 401 || req.status == 403) && oauth.enabled) {
initiate_logout(reason);
initiate_logout(oauth, reason);
} else {
show_popup('warn', fmt_escape_html(reason));
}

View File

@ -1,3 +1,4 @@
import {oidc} from './oidc-client-ts.js';
var mgr;
var _management_logger;
@ -85,9 +86,61 @@ function auth_settings_apply_defaults(authSettings) {
return authSettings;
}
var oauth_settings = { oauth_enabled : false}
export function set_oauth_settings(settings) {
oauth_settings = settings
}
function get_oauth_settings() {
return oauth_settings
}
export function oauth_initialize_if_required(state = "index") {
let oauth = oauth_initialize(get_oauth_settings())
if (!oauth.enabled) return oauth;
switch (state) {
case 'login-callback':
oauth_completeLogin(); break;
case 'logout-callback':
oauth_completeLogout(); break;
default:
oauth = oauth_initiate(oauth);
}
return oauth;
}
export function oauth_initiate(oauth) {
if (oauth.enabled) {
if (!oauth.sp_initiated) {
oauth.logged_in = has_auth_credentials();
} else {
oauth_is_logged_in().then( status => {
if (status.loggedIn && !has_auth_credentials()) {
oauth.logged_in = false;
oauth_initiateLogout();
} else {
if (!status.loggedIn) {
clear_auth();
} else {
oauth.logged_in = true;
oauth.expiryDate = new Date(status.user.expires_at * 1000); // it is epoch in seconds
let current = new Date();
_management_logger.debug('token expires in ', (oauth.expiryDate-current)/1000,
'secs at : ', oauth.expiryDate );
oauth.user_name = status.user.profile['user_name'];
if (!oauth.user_name || oauth.user_name == '') {
oauth.user_name = status.user.profile['sub'];
}
oauth.scopes = status.user.scope;
}
}
});
}
}
return oauth;
}
function oauth_initialize_user_manager(resource_server) {
oidcSettings = {
let oidcSettings = {
userStore: new oidc.WebStorageStateStore({ store: window.localStorage }),
authority: resource_server.oauth_provider_url,
client_id: resource_server.oauth_client_id,
@ -119,7 +172,7 @@ function oauth_initialize_user_manager(resource_server) {
oidc.Log.setLogger(console);
mgr = new oidc.UserManager(oidcSettings);
oauth.readiness_url = mgr.settings.metadataUrl;
// oauth.readiness_url = mgr.settings.metadataUrl;
_management_logger = new oidc.Logger("Management");
@ -133,15 +186,13 @@ function oauth_initialize_user_manager(resource_server) {
_management_logger.error("token expiring failed due to ", err);
});
mgr.events.addUserLoaded(function(user) {
console.log("addUserLoaded setting oauth.access_token ")
oauth.access_token = user.access_token // DEPRECATED
set_token_auth(oauth.access_token)
set_token_auth(user.access_token)
});
}
function oauth_initialize(authSettings) {
export function oauth_initialize(authSettings) {
authSettings = auth_settings_apply_defaults(authSettings);
oauth = {
let oauth = {
"logged_in": false,
"enabled" : authSettings.oauth_enabled,
"resource_servers" : authSettings.resource_servers,
@ -149,12 +200,12 @@ function oauth_initialize(authSettings) {
}
if (!oauth.enabled) return oauth;
resource_server = null
let 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())
resource_server = lookup_resource_server(get_auth_resource(), oauth.resource_servers)
}
if (resource_server) {
@ -189,18 +240,18 @@ function oauth_is_logged_in() {
return { "user": user, "loggedIn": !user.expired };
});
}
function lookup_resource_server(resource_server_id) {
function lookup_resource_server(resource_server_id, resource_servers) {
let i = 0;
while (i < oauth.resource_servers.length && oauth.resource_servers[i].id != resource_server_id) {
while (i < resource_servers.length && resource_servers[i].id != resource_server_id) {
i++;
}
if (i < oauth.resource_servers.length) return oauth.resource_servers[i]
if (i < resource_servers.length) return resource_servers[i]
else return null
}
function oauth_initiateLogin(resource_server_id) {
resource_server = lookup_resource_server(resource_server_id)
export function oauth_initiateLogin(resource_server_id) {
let resource_server = lookup_resource_server(resource_server_id, oauth.resource_servers)
if (!resource_server) return;
set_auth_resource(resource_server_id)
@ -220,19 +271,15 @@ function oauth_initiateLogin(resource_server_id) {
}
}
function oauth_redirectToHome(oauth) {
set_token_auth(oauth.access_token)
path = get_pref("oauth-return-to");
function oauth_redirectToHome() {
let path = get_pref("oauth-return-to")
clear_pref("oauth-return-to")
go_to(path)
go_to( !path ? "" : path)
}
function go_to(path) {
location.href = rabbit_path_prefix() + "/" + path
}
function go_to_home() {
location.href = rabbit_path_prefix() + "/"
}
function go_to_authority() {
location.href = oauth.authority
}
@ -242,14 +289,17 @@ function oauth_redirectToLogin(error) {
location.href = rabbit_path_prefix() + "/?error=" + error
}
}
function oauth_completeLogin() {
mgr.signinRedirectCallback().then(user => oauth_redirectToHome(user)).catch(function(err) {
export function oauth_completeLogin() {
mgr.signinRedirectCallback().then(function(user) {
set_token_auth(user.access_token);
oauth_redirectToHome();
}).catch(function(err) {
_management_logger.error(err)
oauth_redirectToLogin(err)
});
}
function oauth_initiateLogout() {
export function oauth_initiateLogout() {
if (oauth.sp_initiated) {
mgr.metadataService.getEndSessionEndpoint().then(endpoint => {
if (endpoint == undefined) {
@ -268,7 +318,7 @@ function oauth_initiateLogout() {
}
}
function oauth_completeLogout() {
export function oauth_completeLogout() {
clear_auth()
mgr.signoutRedirectCallback().then(_ => oauth_redirectToLogin())
}
@ -287,3 +337,74 @@ function validate_openid_configuration(payload) {
}
}
function warningMessageOAuthResource(oauthResource, reason) {
return "OAuth resource [<b>" + (oauthResource["label"] != null ? oauthResource.label : oauthResource.id) +
"</b>] not available. OpenId Discovery endpoint " + readiness_url(oauthResource) + reason
}
function warningMessageOAuthResources(commonProviderURL, oauthResources, reason) {
return "OAuth resources [ <b>" + oauthResources.map(resource => resource["label"] != null ? resource.label : resource.id).join("</b>,<b>")
+ "</b>] not available. OpenId Discovery endpoint " + commonProviderURL + reason
}
export function hasAnyResourceServerReady(oauth, onReadyCallback) {
// Find out how many distinct oauthServers are configured
let oauthServers = removeDuplicates(oauth.resource_servers.filter((resource) => resource.sp_initiated))
oauthServers.forEach(function(entry) { console.log(readiness_url(entry)) })
if (oauthServers.length > 0) { // some resources are sp_initiated but there could be idp_initiated too
Promise.allSettled(oauthServers.map(oauthServer => fetch(readiness_url(oauthServer)).then(res => res.json())))
.then(results => {
results.forEach(function(entry) { console.log(entry) })
let notReadyServers = []
let notCompliantServers = []
for (let i = 0; i < results.length; i++) {
switch (results[i].status) {
case "fulfilled":
try {
validate_openid_configuration(results[i].value)
}catch(e) {
console.log("Unable to connect to " + oauthServers[i].oauth_provider_url + ". " + e)
notCompliantServers.push(oauthServers[i].oauth_provider_url)
}
break
case "rejected":
notReadyServers.push(oauthServers[i].oauth_provider_url)
break
}
}
const spOauthServers = oauth.resource_servers.filter((resource) => resource.sp_initiated)
const groupByProviderURL = spOauthServers.reduce((group, oauthServer) => {
const { oauth_provider_url } = oauthServer;
group[oauth_provider_url] = group[oauth_provider_url] ?? [];
group[oauth_provider_url].push(oauthServer);
return group;
}, {})
let warnings = []
for(var url in groupByProviderURL){
console.log(url + ': ' + groupByProviderURL[url]);
const notReadyResources = groupByProviderURL[url].filter((oauthserver) => notReadyServers.includes(oauthserver.oauth_provider_url))
const notCompliantResources = groupByProviderURL[url].filter((oauthserver) => notCompliantServers.includes(oauthserver.oauth_provider_url))
if (notReadyResources.length == 1) {
warnings.push(warningMessageOAuthResource(notReadyResources[0], " not reachable"))
}else if (notReadyResources.length > 1) {
warnings.push(warningMessageOAuthResources(url, notReadyResources, " not reachable"))
}
if (notCompliantResources.length == 1) {
warnings.push(warningMessageOAuthResource(notCompliantResources[0], " not compliant"))
}else if (notCompliantResources.length > 1) {
warnings.push(warningMessageOAuthResources(url, notCompliantResources, " not compliant"))
}
}
console.log("warnings:" + warnings)
oauth.declared_resource_servers_count = oauth.resource_servers.length
oauth.resource_servers = oauth.resource_servers.filter((resource) =>
!notReadyServers.includes(resource.oauth_provider_url) && !notCompliantServers.includes(resource.oauth_provider_url))
onReadyCallback(oauth, warnings)
})
}else {
onReadyCallback(oauth, [])
}
}

View File

@ -7,12 +7,11 @@
<script src="../jquery-3.5.1.min.js"></script>
<script src="../base64.js" type="text/javascript"></script>
<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">
<script src="./bootstrap.js" type="module" ></script>
<script type="module">
window.oauth = oauth_initialize_if_required("login-callback");
if (oauth_initialize_if_required()) oauth_completeLogin()
</script>
</body>
</html>

View File

@ -4,15 +4,15 @@
</head>
<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()
}
<script src="./bootstrap.js" type="module" ></script>
<script type="module">
window.oauth = oauth_initialize_if_required("logout-callback");
</script>
</body>
</html>

View File

@ -3180,3 +3180,4 @@ var oidc = (() => {
return __toCommonJS(src_exports);
})();
//# sourceMappingURL=oidc-client-ts.js.map
export {oidc};

View File

@ -47,7 +47,7 @@ start_devkeycloak() {
wait_for_oidc_endpoint devkeycloak $DEVKEYCLOAK_URL $MOUNT_DEVKEYCLOAK_CONF_DIR/ca_certificate.pem
end "devkeycloak is ready"
print " Note: If you modify devkeycloak configuration. Make sure to run the following command to export the configuration."
print " Note: If you modify devkeycloak configuration, make sure to run the following command to export the configuration."
print " docker exec -it devkeycloak /opt/keycloak/bin/kc.sh export --users realm_file --realm test --dir /opt/keycloak/data/import/"
}

View File

@ -46,7 +46,7 @@ start_prodkeycloak() {
wait_for_oidc_endpoint prodkeycloak $PRODKEYCLOAK_URL $MOUNT_PRODKEYCLOAK_CONF_DIR/ca_certificate.pem
end "prodkeycloak is ready"
print " Note: If you modify prodkeycloak configuration. Make sure to run the following command to export the configuration."
print " Note: If you modify prodkeycloak configuration, make sure to run the following command to export the configuration."
print " docker exec -it prodkeycloak /opt/keycloak/bin/kc.sh export --users realm_file --realm test --dir /opt/keycloak/data/import/"
}

View File

@ -1,5 +1,6 @@
authnz-mgt/basic-auth-behind-proxy.sh
authnz-mgt/basic-auth.sh
authnz-mgt/basic-auth-with-mgt-prefix.sh
authnz-mgt/multi-oauth-with-basic-auth-when-idps-down.sh
authnz-mgt/multi-oauth-with-basic-auth.sh
authnz-mgt/multi-oauth-without-basic-auth-and-resource-label-and-scopes.sh

View File

@ -13,7 +13,7 @@
"author": "",
"license": "ISC",
"dependencies": {
"chromedriver": "^123.0.0",
"chromedriver": "^125.0.0",
"ejs": "^3.1.8",
"express": "^4.18.2",
"geckodriver": "^3.0.2",

View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
TEST_CASES_PATH=/basic-auth
PROFILES="mgt-prefix"
source $SCRIPT/../../bin/suite_template $@
run

View File

@ -44,6 +44,13 @@
"monitoring"
],
"limits": {}
},
{
"name": "rabbit_no_management",
"password_hash": "Joz9zzUBOrX10lB3GisWN5oTXK+wj0gxS/nyrfTYmBOuhps5",
"hashing_algorithm": "rabbit_password_hashing_sha256",
"tags": [ ],
"limits": {}
}
],
"vhosts": [
@ -65,6 +72,13 @@
"configure": ".*",
"write": ".*",
"read": ".*"
},
{
"user": "rabbit_no_management",
"vhost": "/",
"configure": ".*",
"write": ".*",
"read": ".*"
}
]

View File

@ -1,4 +1,4 @@
auth_backends.1 = rabbit_auth_backend_internal
management.login_session_timeout = 150
management.login_session_timeout = 1
load_definitions = ${IMPORT_DIR}/users.json

View File

@ -0,0 +1 @@
management.path_prefix = /my-prefix/another-prefix

View File

@ -0,0 +1,39 @@
const { By, Key, until, Builder } = require('selenium-webdriver')
require('chromedriver')
const assert = require('assert')
const { buildDriver, goToHome, captureScreensFor, teardown, delay } = require('../utils')
const LoginPage = require('../pageobjects/LoginPage')
const OverviewPage = require('../pageobjects/OverviewPage')
describe('Once user is logged in', function () {
let homePage
let idpLogin
let overview
let captureScreen
this.timeout(65000) // hard-coded to 25secs because this test requires 35sec to run
before(async function () {
driver = buildDriver()
await goToHome(driver)
login = new LoginPage(driver)
overview = new OverviewPage(driver)
captureScreen = captureScreensFor(driver, __filename)
await login.login('guest', 'guest')
await overview.isLoaded()
})
it('it has to login after the session expires', async function () {
await delay(60000)
await login.isLoaded()
await login.login('guest', 'guest')
await overview.isLoaded()
await overview.clickOnConnectionsTab() // and we can still interact with the ui
})
after(async function () {
await teardown(driver, this, captureScreen)
})
})

View File

@ -0,0 +1,59 @@
const { By, Key, until, Builder } = require('selenium-webdriver')
require('chromedriver')
const assert = require('assert')
const { buildDriver, goToHome, captureScreensFor, teardown, delay } = require('../utils')
const LoginPage = require('../pageobjects/LoginPage')
const OverviewPage = require('../pageobjects/OverviewPage')
describe('An user without management tag', function () {
let homePage
let idpLogin
let overview
let captureScreen
before(async function () {
driver = buildDriver()
await goToHome(driver)
login = new LoginPage(driver)
overview = new OverviewPage(driver)
captureScreen = captureScreensFor(driver, __filename)
assert.ok(!await login.isPopupWarningDisplayed())
await login.login('rabbit_no_management', 'rabbit_no_management')
await !overview.isLoaded()
})
it('cannot log in into the management ui', async function () {
const visible = await login.isWarningVisible()
assert.ok(visible)
})
it('should get "Login failed" warning message', async function(){
assert.equal('Login failed', await login.getWarning())
})
it('should get popup warning dialog', async function(){
assert.ok(login.isPopupWarningDisplayed())
assert.equal('Not_Authorized', await login.getPopupWarning())
})
describe("After clicking on popup warning dialog button", function() {
before(async function () {
await login.closePopupWarning()
})
it('should close popup warning', async function(){
await delay(1000)
const visible = await login.isPopupWarningDisplayed()
assert.ok(!visible)
})
})
after(async function () {
await teardown(driver, this, captureScreen)
})
})

View File

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

View File

@ -44,6 +44,13 @@
"monitoring"
],
"limits": {}
},
{
"name": "rabbit_no_management",
"password_hash": "Joz9zzUBOrX10lB3GisWN5oTXK+wj0gxS/nyrfTYmBOuhps5",
"hashing_algorithm": "rabbit_password_hashing_sha256",
"tags": [ ],
"limits": {}
}
],
"vhosts": [
@ -65,6 +72,13 @@
"configure": ".*",
"write": ".*",
"read": ".*"
},
{
"user": "rabbit_no_management",
"vhost": "/",
"configure": ".*",
"write": ".*",
"read": ".*"
}
]

View File

@ -0,0 +1,59 @@
const { By, Key, until, Builder } = require('selenium-webdriver')
require('chromedriver')
const assert = require('assert')
const { buildDriver, goToHome, captureScreensFor, teardown, idpLoginPage } = require('../../utils')
const SSOHomePage = require('../../pageobjects/SSOHomePage')
const OverviewPage = require('../../pageobjects/OverviewPage')
describe('An user without management tag', function () {
let homePage
let idpLogin
let overview
let captureScreen
before(async function () {
driver = buildDriver()
await goToHome(driver)
homePage = new SSOHomePage(driver)
idpLogin = idpLoginPage(driver)
overview = new OverviewPage(driver)
captureScreen = captureScreensFor(driver, __filename)
await homePage.clickToLogin()
await idpLogin.login('rabbit_no_management', 'rabbit_no_management')
if (!await homePage.isLoaded()) {
throw new Error('Failed to login')
}
})
it('cannot log in into the management ui', async function () {
const visible = await homePage.isWarningVisible()
assert.ok(visible)
})
it('should get "Not authorized" warning message', async function(){
assert.equal('Not authorized', await homePage.getWarning())
assert.equal('Click here to logout', await homePage.getLogoutButton())
assert.ok(!await homePage.isBasicAuthSectionVisible())
assert.ok(!await homePage.isOAuth2SectionVisible())
})
describe("After clicking on logout button", function() {
before(async function () {
await homePage.clickToLogout()
})
it('should get redirected to home page again without error message', async function(){
const visible = await homePage.isWarningVisible()
assert.ok(!visible)
})
})
after(async function () {
await teardown(driver, this, captureScreen)
})
})

View File

@ -6,7 +6,7 @@ const { buildDriver, goToHome, captureScreensFor, teardown, idpLoginPage } = req
const SSOHomePage = require('../../pageobjects/SSOHomePage')
const OverviewPage = require('../../pageobjects/OverviewPage')
describe('An user without administrator tag', function () {
describe('An user without management tag', function () {
let homePage
let idpLogin
let overview

View File

@ -13,6 +13,9 @@ const EXCHANGES_TAB = By.css('div#menu ul#tabs li#exchanges')
const ADMIN_TAB = By.css('div#menu ul#tabs li#admin')
const STREAM_CONNECTIONS_TAB = By.css('div#menu ul#tabs li#stream-connections')
const FORM_POPUP = By.css('div.form-popup-warn')
const FORM_POPUP_CLOSE_BUTTON = By.css('div.form-popup-warn span')
module.exports = class BasePage {
driver
timeout
@ -24,7 +27,6 @@ module.exports = class BasePage {
this.timeout = parseInt(process.env.SELENIUM_TIMEOUT) || 1000 // max time waiting to locate an element. Should be less that test timeout
this.polling = parseInt(process.env.SELENIUM_POLLING) || 500 // how frequent selenium searches for an element
this.interactionDelay = parseInt(process.env.SELENIUM_INTERACTION_DELAY) || 0 // slow down interactions (when rabbit is behind a http proxy)
console.log("Interaction Delay : " + this.interactionDelay)
}
@ -138,7 +140,14 @@ module.exports = class BasePage {
return table_model
}
async isPopupWarningDisplayed() {
const element = "form-popup-warn"
try {
let element = await driver.findElement(FORM_POPUP)
return element.isDisplayed()
} catch(e) {
return Promise.resolve(false)
}
/*
let element = await driver.findElement(FORM_POPUP)
return this.driver.wait(until.elementIsVisible(element), this.timeout / 2,
'Timed out after [timeout=' + this.timeout + ';polling=' + this.polling + '] awaiting till visible ' + element,
this.polling / 2).then(function onWarningVisible(e) {
@ -146,17 +155,20 @@ module.exports = class BasePage {
}, function onError(e) {
return Promise.resolve(false)
})
*/
}
async getPopupWarning() {
const element = "form-popup-warn"
let element = await driver.findElement(FORM_POPUP)
return this.driver.wait(until.elementIsVisible(element), this.timeout,
'Timed out after [timeout=' + this.timeout + ';polling=' + this.polling + '] awaiting till visible ' + element,
this.polling)
this.polling).getText().then((value) => value.substring(0, value.search('\n\nClose')))
}
async closePopupWarning() {
return this.click(FORM_POPUP_CLOSE_BUTTON)
}
async isDisplayed(locator) {
try {
element = await driver.findElement(locator)
let element = await driver.findElement(locator)
return this.driver.wait(until.elementIsVisible(element), this.timeout,
'Timed out after [timeout=' + this.timeout + ';polling=' + this.polling + '] awaiting till visible ' + element,

View File

@ -6,6 +6,7 @@ const FORM = By.css('div#login form')
const USERNAME = By.css('input[name="username"]')
const PASSWORD = By.css('input[name="password"]')
const LOGIN_BUTTON = By.css('div#outer div#login form input[type=submit]')
const WARNING = By.css('div#outer div#login div#login-status p')
module.exports = class LoginPage extends BasePage {
async isLoaded () {
@ -22,4 +23,26 @@ module.exports = class LoginPage extends BasePage {
async getLoginButton () {
return this.getValue(LOGIN_BUTTON)
}
async isWarningVisible () {
try {
await this.waitForDisplayed(WARNING)
return Promise.resolve(true)
} catch (e) {
return Promise.resolve(false)
}
}
async getWarnings() {
try
{
return driver.findElements(WARNING)
} catch (NoSuchElement) {
return Promise.resolve([])
}
}
async getWarning () {
return this.getText(WARNING)
}
}

View File

@ -9,8 +9,6 @@
-export([init/2]).
-include_lib("amqp_client/include/amqp_client.hrl").
%%--------------------------------------------------------------------
init(Req0, State) ->
@ -18,19 +16,42 @@ init(Req0, State) ->
rabbit_mgmt_headers:set_common_permission_headers(Req0, ?MODULE), ?MODULE), State).
bootstrap_oauth(Req0, State) ->
JSContent = oauth_initialize_if_required() ++ set_token_auth(Req0),
{ok, cowboy_req:reply(200, #{<<"content-type">> => <<"text/javascript; charset=utf-8">>}, JSContent, Req0), State}.
AuthSettings = rabbit_mgmt_wm_auth:authSettings(),
Dependencies = oauth_dependencies(),
JSContent = import_dependencies(Dependencies) ++
set_oauth_settings(AuthSettings) ++
set_token_auth(AuthSettings, Req0) ++
export_dependencies(Dependencies),
{ok, cowboy_req:reply(200, #{<<"content-type">> => <<"text/javascript; charset=utf-8">>},
JSContent, Req0), State}.
oauth_initialize_if_required() ->
["function oauth_initialize_if_required() { return oauth_initialize(" ,
rabbit_json:encode(rabbit_mgmt_format:format_nulls(rabbit_mgmt_wm_auth:authSettings())) , ") }" ].
set_oauth_settings(AuthSettings) ->
JsonAuthSettings = rabbit_json:encode(rabbit_mgmt_format:format_nulls(AuthSettings)),
["set_oauth_settings(", JsonAuthSettings, ");"].
set_token_auth(Req0) ->
case application:get_env(rabbitmq_management, oauth_enabled, false) of
set_token_auth(AuthSettings, Req0) ->
case proplists:get_value(oauth_enabled, AuthSettings, false) of
true ->
case cowboy_req:parse_header(<<"authorization">>, Req0) of
{bearer, Token} -> ["set_token_auth('", Token, "');"];
_ -> []
end;
false -> []
false ->
[]
end.
import_dependencies(Dependencies) ->
["import {", string:join(Dependencies, ","), "} from './helper.js';"].
oauth_dependencies() ->
["oauth_initialize_if_required",
"hasAnyResourceServerReady",
"oauth_initialize", "oauth_initiate",
"oauth_initiateLogin",
"oauth_initiateLogout",
"oauth_completeLogin",
"oauth_completeLogout",
"set_oauth_settings"].
export_dependencies(Dependencies) ->
[ io_lib:format("window.~s = ~s;", [Dep, Dep]) || Dep <- Dependencies ].

View File

@ -444,7 +444,7 @@ auth_test(Config) ->
%% because user/password are ok, tags are not
test_auth(Config, ?NOT_AUTHORISED, [auth_header("user", "user")]),
WrongAuthResponseHeaders = test_auth(Config, ?NOT_AUTHORISED, [auth_header("guest", "gust")]),
?assertEqual(true, lists:keymember("www-authenticate", 1, WrongAuthResponseHeaders)),
%?assertEqual(true, lists:keymember("www-authenticate", 1, WrongAuthResponseHeaders)),
test_auth(Config, ?OK, [auth_header("guest", "guest")]),
http_delete(Config, "/users/user", {group, '2xx'}),
passed.

View File

@ -265,7 +265,9 @@ internal_user(User) ->
user(User) ->
[{name, User#user.username},
{tags, tags_as_binaries(User#user.tags)}].
{tags, tags_as_binaries(User#user.tags)},
{is_internal_user, lists:any(fun({Module,_}) -> Module == rabbit_auth_backend_internal end,
User#user.authz_backends)}].
tags_as_binaries(Tags) ->
[to_binary(T) || T <- Tags].

View File

@ -277,15 +277,8 @@ halt_response(Code, Type, Reason, ReqData, Context) ->
rabbit_json:encode(Json), ReqData),
{stop, ReqData1, Context}.
not_authenticated(Reason, ReqData, Context,
#auth_settings{auth_realm = AuthRealm} = AuthConfig) ->
case is_oauth2_enabled(AuthConfig) of
false ->
ReqData1 = cowboy_req:set_resp_header(<<"www-authenticate">>, AuthRealm, ReqData),
halt_response(401, not_authorized, Reason, ReqData1, Context);
true ->
halt_response(401, not_authorized, Reason, ReqData, Context)
end.
not_authenticated(Reason, ReqData, Context, _AuthConfig) ->
halt_response(401, not_authorized, Reason, ReqData, Context).
format_reason(Tuple) when is_tuple(Tuple) ->
tuple(Tuple);
@ -366,6 +359,3 @@ log_access_control_result(NotOK) ->
is_basic_auth_disabled(#auth_settings{basic_auth_enabled = Enabled}) ->
not Enabled.
is_oauth2_enabled(#auth_settings{oauth2_enabled = Enabled}) ->
Enabled.