mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-30 18:17:55 +00:00 
			
		
		
		
	UI/update auth form to fetchRoles after a namespace is inputted, prior to OIDC auth (#19541)
* re-fetch roles if there is a namespace * remove redundant conditional * reorder oidc auth operations * add test * test cleanup * add changelog
This commit is contained in:
		
							
								
								
									
										3
									
								
								changelog/19541.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								changelog/19541.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | ```release-note:bug | ||||||
|  | ui: fixes oidc tabs in auth form submitting with the root's default_role value after a namespace has been inputted | ||||||
|  | ``` | ||||||
| @@ -139,7 +139,7 @@ export default Component.extend({ | |||||||
|  |  | ||||||
|     let { namespace, path, state, code } = oidcState; |     let { namespace, path, state, code } = oidcState; | ||||||
|  |  | ||||||
|     // The namespace can be either be passed as a query paramter, or be embedded |     // The namespace can be either be passed as a query parameter, or be embedded | ||||||
|     // in the state param in the format `<state_id>,ns=<namespace>`. So if |     // in the state param in the format `<state_id>,ns=<namespace>`. So if | ||||||
|     // `namespace` is empty, check for namespace in state as well. |     // `namespace` is empty, check for namespace in state as well. | ||||||
|     if (namespace === '' || this.featureFlagService.managedNamespaceRoot) { |     if (namespace === '' || this.featureFlagService.managedNamespaceRoot) { | ||||||
| @@ -176,6 +176,14 @@ export default Component.extend({ | |||||||
|       if (e && e.preventDefault) { |       if (e && e.preventDefault) { | ||||||
|         e.preventDefault(); |         e.preventDefault(); | ||||||
|       } |       } | ||||||
|  |       try { | ||||||
|  |         await this.fetchRole.perform(this.roleName, { debounce: false }); | ||||||
|  |       } catch (error) { | ||||||
|  |         // this task could be cancelled if the instances in didReceiveAttrs resolve after this was started | ||||||
|  |         if (error?.name !== 'TaskCancelation') { | ||||||
|  |           throw error; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|       if (!this.isOIDC || !this.role || !this.role.authUrl) { |       if (!this.isOIDC || !this.role || !this.role.authUrl) { | ||||||
|         let message = this.errorMessage; |         let message = this.errorMessage; | ||||||
|         if (!this.role) { |         if (!this.role) { | ||||||
| @@ -187,14 +195,6 @@ export default Component.extend({ | |||||||
|         this.onError(message); |         this.onError(message); | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|       try { |  | ||||||
|         await this.fetchRole.perform(this.roleName, { debounce: false }); |  | ||||||
|       } catch (error) { |  | ||||||
|         // this task could be cancelled if the instances in didReceiveAttrs resolve after this was started |  | ||||||
|         if (error?.name !== 'TaskCancelation') { |  | ||||||
|           throw error; |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|       const win = this.getWindow(); |       const win = this.getWindow(); | ||||||
|  |  | ||||||
|       const POPUP_WIDTH = 500; |       const POPUP_WIDTH = 500; | ||||||
|   | |||||||
| @@ -19,7 +19,7 @@ | |||||||
|                   "is-active" |                   "is-active" | ||||||
|                   "" |                   "" | ||||||
|                 }} |                 }} | ||||||
|                 data-test-auth-method |                 data-test-auth-method={{method.id}} | ||||||
|               > |               > | ||||||
|                 <LinkTo |                 <LinkTo | ||||||
|                   @route="vault.cluster.auth" |                   @route="vault.cluster.auth" | ||||||
| @@ -32,18 +32,16 @@ | |||||||
|               </li> |               </li> | ||||||
|             {{/let}} |             {{/let}} | ||||||
|           {{/each}} |           {{/each}} | ||||||
|           {{#if this.hasMethodsWithPath}} |           <li class={{unless this.selectedAuthIsPath "is-active" ""}} data-test-auth-method> | ||||||
|             <li class={{unless this.selectedAuthIsPath "is-active" ""}} data-test-auth-method> |             <LinkTo | ||||||
|               <LinkTo |               @route="vault.cluster.auth" | ||||||
|                 @route="vault.cluster.auth" |               @model={{this.cluster.name}} | ||||||
|                 @model={{this.cluster.name}} |               @query={{hash with="token"}} | ||||||
|                 @query={{hash with="token"}} |               data-test-auth-method-link="other" | ||||||
|                 data-test-auth-method-link="other" |             > | ||||||
|               > |               Other | ||||||
|                 Other |             </LinkTo> | ||||||
|               </LinkTo> |           </li> | ||||||
|             </li> |  | ||||||
|           {{/if}} |  | ||||||
|         </ul> |         </ul> | ||||||
|       </nav> |       </nav> | ||||||
|     {{/if}} |     {{/if}} | ||||||
|   | |||||||
| @@ -82,6 +82,7 @@ | |||||||
|               <div class="field"> |               <div class="field"> | ||||||
|                 <div class="control"> |                 <div class="control"> | ||||||
|                   <input |                   <input | ||||||
|  |                     data-test-auth-form-ns-input | ||||||
|                     value={{this.namespaceQueryParam}} |                     value={{this.namespaceQueryParam}} | ||||||
|                     placeholder="/ (Root)" |                     placeholder="/ (Root)" | ||||||
|                     oninput={{perform this.updateNamespace value="target.value"}} |                     oninput={{perform this.updateNamespace value="target.value"}} | ||||||
|   | |||||||
| @@ -7,7 +7,8 @@ import { click, settled, visit, fillIn, currentURL } from '@ember/test-helpers'; | |||||||
| import { module, test } from 'qunit'; | import { module, test } from 'qunit'; | ||||||
| import { setupApplicationTest } from 'ember-qunit'; | import { setupApplicationTest } from 'ember-qunit'; | ||||||
| import { create } from 'ember-cli-page-object'; | import { create } from 'ember-cli-page-object'; | ||||||
|  | import { setupMirage } from 'ember-cli-mirage/test-support'; | ||||||
|  | import parseURL from 'core/utils/parse-url'; | ||||||
| import consoleClass from 'vault/tests/pages/components/console/ui-panel'; | import consoleClass from 'vault/tests/pages/components/console/ui-panel'; | ||||||
| import authPage from 'vault/tests/pages/auth'; | import authPage from 'vault/tests/pages/auth'; | ||||||
| import logout from 'vault/tests/pages/logout'; | import logout from 'vault/tests/pages/logout'; | ||||||
| @@ -93,4 +94,90 @@ module('Acceptance | Enterprise | namespaces', function (hooks) { | |||||||
|       'Does not prepend root to namespace' |       'Does not prepend root to namespace' | ||||||
|     ); |     ); | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|  |   module('auth form', function (hooks) { | ||||||
|  |     setupMirage(hooks); | ||||||
|  |     const SELECTORS = { | ||||||
|  |       authTab: (path) => `[data-test-auth-method="${path}"] a`, | ||||||
|  |       authSubmit: '[data-test-auth-submit]', | ||||||
|  |     }; | ||||||
|  |     hooks.beforeEach(async function () { | ||||||
|  |       this.namespace = 'test-ns'; | ||||||
|  |       this.rootOidc = 'root-oidc'; | ||||||
|  |       this.nsOidc = 'ns-oidc'; | ||||||
|  |  | ||||||
|  |       const enableOidc = async (path, role = '') => { | ||||||
|  |         this.server.post(`/auth/${path}/config`, () => {}); | ||||||
|  |         await shell.runCommands([ | ||||||
|  |           `write sys/auth/${path} type=oidc`, | ||||||
|  |           `write auth/${path}/config default_role="${role}" oidc_discovery_url="https://example.com"`, | ||||||
|  |           // show method as tab | ||||||
|  |           `write sys/auth/${path}/tune listing_visibility="unauth"`, | ||||||
|  |         ]); | ||||||
|  |       }; | ||||||
|  |       await authPage.login(); | ||||||
|  |       // enable oidc in root namespace, without default role | ||||||
|  |       await enableOidc(this.rootOidc); | ||||||
|  |       // create child namespace to enable oidc | ||||||
|  |       await createNS(this.namespace); | ||||||
|  |       await logout.visit(); | ||||||
|  |  | ||||||
|  |       // enable oidc in child namespace with default role | ||||||
|  |       await authPage.loginNs(this.namespace); | ||||||
|  |       await enableOidc(this.nsOidc, `${this.nsOidc}-role`); | ||||||
|  |       await authPage.logout(); | ||||||
|  |  | ||||||
|  |       // end by logging in/out of root so query params are cleared out and don't include namespace | ||||||
|  |       await authPage.login(); | ||||||
|  |       return await authPage.logout(); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     hooks.afterEach(async function () { | ||||||
|  |       const disableOidc = async (path) => { | ||||||
|  |         await shell.runCommands([`delete /sys/auth/${path}`]); | ||||||
|  |       }; | ||||||
|  |  | ||||||
|  |       await authPage.loginNs(this.namespace); | ||||||
|  |       await visit(`/vault/access?namespace=${this.namespace}`); | ||||||
|  |       // disable methods to cleanup test state for re-running | ||||||
|  |       await disableOidc(this.rootOidc); | ||||||
|  |       await disableOidc(this.nsOidc); | ||||||
|  |       await authPage.logout(); | ||||||
|  |  | ||||||
|  |       await authPage.login(); | ||||||
|  |       await shell.runCommands([`delete /sys/auth/${this.namespace}`]); | ||||||
|  |       this.server.shutdown(); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     test('oidc: request is made to auth_url when a namespace is inputted', async function (assert) { | ||||||
|  |       assert.expect(5); | ||||||
|  |       this.server.post(`/auth/${this.rootOidc}/oidc/auth_url`, (schema, req) => { | ||||||
|  |         const { redirect_uri } = JSON.parse(req.requestBody); | ||||||
|  |         const { pathname, search } = parseURL(redirect_uri); | ||||||
|  |         assert.strictEqual( | ||||||
|  |           pathname + search, | ||||||
|  |           `/ui/vault/auth/${this.rootOidc}/oidc/callback`, | ||||||
|  |           'request made to auth_url when the login page is visited' | ||||||
|  |         ); | ||||||
|  |       }); | ||||||
|  |       this.server.post(`/auth/${this.nsOidc}/oidc/auth_url`, (schema, req) => { | ||||||
|  |         const { redirect_uri } = JSON.parse(req.requestBody); | ||||||
|  |         const { pathname, search } = parseURL(redirect_uri); | ||||||
|  |         assert.strictEqual( | ||||||
|  |           pathname + search, | ||||||
|  |           `/ui/vault/auth/${this.nsOidc}/oidc/callback?namespace=${this.namespace}`, | ||||||
|  |           'request made to correct auth_url when namespace is filled in' | ||||||
|  |         ); | ||||||
|  |       }); | ||||||
|  |       await visit('/vault/auth?with=oidc%2F'); | ||||||
|  |       assert.dom(SELECTORS.authTab(this.rootOidc)).exists('renders oidc method tab for root'); | ||||||
|  |       await authPage.namespaceInput(this.namespace); | ||||||
|  |       assert.strictEqual( | ||||||
|  |         currentURL(), | ||||||
|  |         `/vault/auth?namespace=${this.namespace}&with=${this.nsOidc}%2F`, | ||||||
|  |         'url updates with namespace value' | ||||||
|  |       ); | ||||||
|  |       assert.dom(SELECTORS.authTab(this.nsOidc)).exists('renders oidc method tab for child namespace'); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ export default create({ | |||||||
|   tokenInput: fillable('[data-test-token]'), |   tokenInput: fillable('[data-test-token]'), | ||||||
|   usernameInput: fillable('[data-test-username]'), |   usernameInput: fillable('[data-test-username]'), | ||||||
|   passwordInput: fillable('[data-test-password]'), |   passwordInput: fillable('[data-test-password]'), | ||||||
|  |   namespaceInput: fillable('[data-test-auth-form-ns-input]'), | ||||||
|   login: async function (token) { |   login: async function (token) { | ||||||
|     // make sure we're always logged out and logged back in |     // make sure we're always logged out and logged back in | ||||||
|     await this.logout(); |     await this.logout(); | ||||||
| @@ -44,4 +45,17 @@ export default create({ | |||||||
|     await this.passwordInput(password).submit(); |     await this.passwordInput(password).submit(); | ||||||
|     return; |     return; | ||||||
|   }, |   }, | ||||||
|  |   loginNs: async function (ns) { | ||||||
|  |     // make sure we're always logged out and logged back in | ||||||
|  |     await this.logout(); | ||||||
|  |     await settled(); | ||||||
|  |     // clear session storage to ensure we have a clean state | ||||||
|  |     window.localStorage.clear(); | ||||||
|  |     await this.visit({ with: 'token' }); | ||||||
|  |     await settled(); | ||||||
|  |     await this.namespaceInput(ns); | ||||||
|  |     await settled(); | ||||||
|  |     await this.tokenInput(rootToken).submit(); | ||||||
|  |     return; | ||||||
|  |   }, | ||||||
| }); | }); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 claire bontempo
					claire bontempo