diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_login.erl b/deps/rabbitmq_management/src/rabbit_mgmt_login.erl index 4423565c12e5..e91839dd7bc0 100644 --- a/deps/rabbitmq_management/src/rabbit_mgmt_login.erl +++ b/deps/rabbitmq_management/src/rabbit_mgmt_login.erl @@ -52,7 +52,7 @@ login(<<"POST">>, Req0, State) -> AccessToken -> handleAccessToken(Req0, AccessToken, State) end; -login(<<"GET">>, Req, State) -> +login(<<"GET">>, Req, State) -> Auth = case rabbit_mgmt_util:qs_val(?MANAGEMENT_LOGIN_STRICT_AUTH_MECHANISM, Req) of undefined -> case rabbit_mgmt_util:qs_val(?MANAGEMENT_LOGIN_PREFERRED_AUTH_MECHANISM, Req) of diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_oauth_bootstrap.erl b/deps/rabbitmq_management/src/rabbit_mgmt_oauth_bootstrap.erl index d9c51a93b33d..c00614287f7b 100644 --- a/deps/rabbitmq_management/src/rabbit_mgmt_oauth_bootstrap.erl +++ b/deps/rabbitmq_management/src/rabbit_mgmt_oauth_bootstrap.erl @@ -82,6 +82,7 @@ get_auth_mechanism(Req) -> get_auth_mechanism_from_cookies(Req) -> Cookies = cowboy_req:parse_cookies(Req), + ?LOG_DEBUG("get_auth_mechanism_from_cookies: ~p", [Cookies]), case proplists:get_value(?MANAGEMENT_LOGIN_STRICT_AUTH_MECHANISM, Cookies) of undefined -> case proplists:get_value(?MANAGEMENT_LOGIN_PREFERRED_AUTH_MECHANISM, Cookies) of @@ -114,9 +115,14 @@ set_token_auth(AuthSettings, Req0) -> ["set_token_auth('", Token, "');"] }; _ -> - Cookies = cowboy_req:parse_cookies(Req0), - case lists:keyfind(?OAUTH2_ACCESS_TOKEN, 1, Cookies) of - {_, Token} -> + Cookies = cowboy_req:parse_cookies(Req0), + ?LOG_DEBUG("set_token_auth: ~p", [Cookies]), + case proplists:get_value(?OAUTH2_ACCESS_TOKEN, Cookies) of + undefined -> { + Req0, + [] + }; + Token -> { cowboy_req:set_resp_cookie( ?OAUTH2_ACCESS_TOKEN, <<"">>, Req0, #{ @@ -126,11 +132,7 @@ set_token_auth(AuthSettings, Req0) -> same_site => strict }), ["set_token_auth('", Token, "');"] - }; - false -> { - Req0, - [] - } + } end end; false -> { diff --git a/selenium/short-suite-management-ui b/selenium/short-suite-management-ui index 03828544c99e..2d8209f3e321 100644 --- a/selenium/short-suite-management-ui +++ b/selenium/short-suite-management-ui @@ -9,4 +9,5 @@ mgt/queuesAndStreams.sh mgt/limits.sh mgt/amqp10-connections.sh mgt/mqtt-connections.sh -mgt/feature-flags.sh \ No newline at end of file +mgt/feature-flags.sh +authnz-mgt/multi-oauth-with-basic-auth.sh \ No newline at end of file diff --git a/selenium/test/multi-oauth/with-basic-auth/landing-with-login-preferences.js b/selenium/test/multi-oauth/with-basic-auth/landing-with-login-preferences.js new file mode 100644 index 000000000000..06d4caa8d5a0 --- /dev/null +++ b/selenium/test/multi-oauth/with-basic-auth/landing-with-login-preferences.js @@ -0,0 +1,84 @@ +const { By, Key, until, Builder } = require('selenium-webdriver') +require('chromedriver') +const assert = require('assert') +const { buildDriver, goToHome, goToLogin, captureScreensFor, teardown, findOption } = require('../../utils') + +const SSOHomePage = require('../../pageobjects/SSOHomePage') + +describe('Given two oauth resources and basic auth enabled, an unauthenticated user', function () { + let driver; + let captureScreen; + let login; + + before(async function () { + this.driver = buildDriver(); + this.captureScreen = captureScreensFor(this.driver, __filename); + + login = async (key, value) => { + await goToLogin(this.driver, key, value); + const homePage = new SSOHomePage(this.driver); + await homePage.isLoaded(); + return homePage; + } + }) + + it('can preselect rabbit_dev oauth2 resource', async function () { + const homePage = await login("preferred_auth_mechanism", "oauth2:rabbit_dev"); + + const oauth2Section = await homePage.isOAuth2SectionVisible(); + assert.ok((await oauth2Section.getAttribute("class")).includes("section-visible")) + const basicSection = await homePage.isBasicAuthSectionVisible(); + assert.ok((await basicSection.getAttribute("class")).includes("section-invisible")) + + resources = await homePage.getOAuthResourceOptions(); + const option = findOption("rabbit_dev", resources); + assert.ok(option); + assert.ok(option.selected); + + }) + it('can preselect rabbit_prod oauth2 resource', async function () { + const homePage = await login("preferred_auth_mechanism", "oauth2:rabbit_prod"); + + const oauth2Section = await homePage.isOAuth2SectionVisible(); + assert.ok((await oauth2Section.getAttribute("class")).includes("section-visible")) + const basicSection = await homePage.isBasicAuthSectionVisible(); + assert.ok((await basicSection.getAttribute("class")).includes("section-invisible")) + + resources = await homePage.getOAuthResourceOptions(); + const option = findOption("rabbit_prod", resources); + assert.ok(option); + assert.ok(option.selected); + + }) + + it('can preselect basic auth', async function () { + const homePage = await login("preferred_auth_mechanism", "basic"); + + const oauth2Section = await homePage.isOAuth2SectionVisible(); + assert.ok((await oauth2Section.getAttribute("class")).includes("section-invisible")) + const basicSection = await homePage.isBasicAuthSectionVisible(); + assert.ok((await basicSection.getAttribute("class")).includes("section-visible")) + }) + + it('can force only to authenticate only with rabbit_dev oauth2 resource', async function () { + const homePage = await login("strict_auth_mechanism", "oauth2:rabbit_dev"); + const value = await homePage.getLoginButtonOnClick(); + assert.ok(value.includes("rabbit_dev")); + }) + it('can force only to authenticate only with rabbit_prod oauth2 resource', async function () { + const homePage = await login("strict_auth_mechanism", "oauth2:rabbit_prod"); + const value = await homePage.getLoginButtonOnClick(); + assert.ok(value.includes("rabbit_prod")); + }) + it('can force only to authenticate only with basic auth', async function () { + const homePage = await login("strict_auth_mechanism", "basic"); + await homePage.isOAuth2SectionNotVisible(); + const basicSection = await homePage.isBasicAuthSectionVisible(); + assert.ok((await basicSection.getAttribute("class")).includes("section-visible")) + + }) + + after(async function () { + await teardown(this.driver, this, this.captureScreen); + }) +}) diff --git a/selenium/test/pageobjects/BasePage.js b/selenium/test/pageobjects/BasePage.js index 63106a1fcbe8..3b4a318aee4b 100644 --- a/selenium/test/pageobjects/BasePage.js +++ b/selenium/test/pageobjects/BasePage.js @@ -131,7 +131,8 @@ module.exports = class BasePage { for (const index in optionList) { const t = await optionList[index].getText() const v = await optionList[index].getAttribute('value') - table_model.push({"text":t, "value": v}) + const s = await optionList[index].getAttribute('selected') + table_model.push({"text": t, "value": v, "selected" : s !== undefined}) } return table_model @@ -300,13 +301,14 @@ module.exports = class BasePage { async isDisplayed(locator) { try { - let element = await driver.findElement(locator) + let element = await this.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, this.polling / 2) }catch(error) { - return Promise.resolve(false) + console.log("isDisplayed failed due to " + error); + return Promise.resolve(false); } } diff --git a/selenium/test/pageobjects/SSOHomePage.js b/selenium/test/pageobjects/SSOHomePage.js index 44f771bc54e2..995b7e7246eb 100644 --- a/selenium/test/pageobjects/SSOHomePage.js +++ b/selenium/test/pageobjects/SSOHomePage.js @@ -37,6 +37,10 @@ module.exports = class SSOHomePage extends BasePage { async getLoginButton () { return this.getText(OAUTH2_LOGIN_BUTTON) } + async getLoginButtonOnClick () { + const element = await this.waitForDisplayed(OAUTH2_LOGIN_BUTTON); + return element.getAttribute('onClick'); + } async getLogoutButton () { return this.getText(LOGOUT_BUTTON) } @@ -74,6 +78,10 @@ module.exports = class SSOHomePage extends BasePage { async isOAuth2SectionVisible() { return this.isDisplayed(SECTION_LOGIN_WITH_OAUTH) } + async isOAuth2SectionNotVisible() { + return this.isElementNotVisible(SECTION_LOGIN_WITH_OAUTH) + } + async getOAuth2Section() { return this.waitForDisplayed(SECTION_LOGIN_WITH_OAUTH) } diff --git a/selenium/test/utils.js b/selenium/test/utils.js index c862b290cc04..db1c94a88a8b 100644 --- a/selenium/test/utils.js +++ b/selenium/test/utils.js @@ -39,8 +39,9 @@ class CaptureScreenshot { const screenshotsSubDir = path.join(screenshotsDir, this.test) if (!fs.existsSync(screenshotsSubDir)) { await fsp.mkdir(screenshotsSubDir) - } + } const dest = path.join(screenshotsSubDir, name + '.png') + console.log("screenshot saved to " + dest) await fsp.writeFile(dest, image, 'base64') } } @@ -122,8 +123,31 @@ module.exports = { return d.driver.get(d.baseUrl) }, - goToLogin: (d, token) => { - return d.driver.get(d.baseUrl + '#/login?access_token=' + token) + /** + * For instance, + * goToLogin(d, access_token, myAccessToken) + * or + * goToLogin(d, preferred_auth_mechanism, "oauth2:my-resource") + */ + goToLogin: (d, ...keyValuePairs) => { + const params = []; + for (let i = 0; i < keyValuePairs.length; i += 2) { + const key = keyValuePairs[i]; + const value = keyValuePairs[i + 1]; + + if (key !== undefined) { + // URL-encode both key and value + const encodedKey = encodeURIComponent(key); + const encodedValue = encodeURIComponent(value || ''); + params.push(`${encodedKey}=${encodedValue}`); + } + } + // Build query string: "key1=value1&key2=value2" + const queryString = params.join('&'); + + const url = d.baseUrl + '/login?' + queryString; + console.log("Navigating to " + url); + return d.driver.get(url); }, goToConnections: (d) => { @@ -263,8 +287,15 @@ module.exports = { && actualOption.text == expectedOptions[i].text)) } }, + findOption: (value, options) => { + for (let i = 0; i < options.length; i++) { + if (options[i].value === value) return options[i]; + } + return undefined; + }, teardown: async (d, test, captureScreen = null) => { + driver = d.driver driver.manage().logs().get(logging.Type.BROWSER).then(function(entries) { entries.forEach(function(entry) { @@ -274,8 +305,11 @@ module.exports = { if (test.currentTest) { if (test.currentTest.isPassed()) { driver.executeScript('lambda-status=passed') - } else { - if (captureScreen != null) await captureScreen.shot('after-failed') + } else { + if (captureScreen != null) { + console.log("Teardown failed . capture..."); + await captureScreen.shot('after-failed'); + } driver.executeScript('lambda-status=failed') } }