From d57b805780e96d8838fd61ce0fa7b0c3bc697889 Mon Sep 17 00:00:00 2001 From: Phil Renaud Date: Tue, 17 Jan 2023 17:01:47 -0500 Subject: [PATCH] [sso] OIDC Updates for the UI (#15804) * Updated UI to handle OIDC method changes * Remove redundant store unload call --- command/acl_auth_method_list_test.go | 8 +++- nomad/acl_endpoint.go | 8 ---- ui/app/adapters/auth-method.js | 6 +-- ui/app/controllers/settings/tokens.js | 39 ++++++++++++++---- ui/app/templates/settings/tokens.hbs | 2 +- ui/mirage/config.js | 57 +++++++++++++++++---------- 6 files changed, 77 insertions(+), 43 deletions(-) diff --git a/command/acl_auth_method_list_test.go b/command/acl_auth_method_list_test.go index a4cdd2d1c67..9c2966152b6 100644 --- a/command/acl_auth_method_list_test.go +++ b/command/acl_auth_method_list_test.go @@ -39,15 +39,19 @@ func TestACLAuthMethodListCommand(t *testing.T) { ui := cli.NewMockUi() cmd := &ACLAuthMethodListCommand{Meta: Meta{Ui: ui, flagAddress: url}} - // Attempt to list auth methods without a valid management token + // List with an invalid token works fine invalidToken := mock.ACLToken() code := cmd.Run([]string{"-address=" + url, "-token=" + invalidToken.SecretID}) - must.One(t, code) + must.Zero(t, code) // List with a valid management token code = cmd.Run([]string{"-address=" + url, "-token=" + token.SecretID}) must.Zero(t, code) + // List with no token at all + code = cmd.Run([]string{"-address=" + url}) + must.Zero(t, code) + // Check the output out := ui.OutputWriter.String() must.StrContains(t, out, method.Name) diff --git a/nomad/acl_endpoint.go b/nomad/acl_endpoint.go index da5e336bfca..482039c60a2 100644 --- a/nomad/acl_endpoint.go +++ b/nomad/acl_endpoint.go @@ -1866,14 +1866,6 @@ func (a *ACL) ListAuthMethods( } defer metrics.MeasureSince([]string{"nomad", "acl", "list_auth_methods"}, time.Now()) - // Resolve the token and ensure it has some form of permissions. - acl, err := a.srv.ResolveToken(args.AuthToken) - if err != nil { - return err - } else if acl == nil { - return structs.ErrPermissionDenied - } - // Set up and return the blocking query. return a.srv.blockingRPC(&blockingOptions{ queryOpts: &args.QueryOptions, diff --git a/ui/app/adapters/auth-method.js b/ui/app/adapters/auth-method.js index 42926c52c4e..586eb51ed78 100644 --- a/ui/app/adapters/auth-method.js +++ b/ui/app/adapters/auth-method.js @@ -17,7 +17,7 @@ export default class AuthMethodAdapter extends ApplicationAdapter { /** * @typedef {Object} ACLOIDCAuthURLParams - * @property {string} AuthMethod + * @property {string} AuthMethodName * @property {string} RedirectUri * @property {string} ClientNonce * @property {Object[]} Meta // NOTE: unsure if array of objects or kv pairs @@ -27,11 +27,11 @@ export default class AuthMethodAdapter extends ApplicationAdapter { * @param {ACLOIDCAuthURLParams} params * @returns */ - getAuthURL({ AuthMethod, RedirectUri, ClientNonce, Meta }) { + getAuthURL({ AuthMethodName, RedirectUri, ClientNonce, Meta }) { const url = `/${this.namespace}/oidc/auth-url`; return this.ajax(url, 'POST', { data: { - AuthMethod, + AuthMethodName, RedirectUri, ClientNonce, Meta, diff --git a/ui/app/controllers/settings/tokens.js b/ui/app/controllers/settings/tokens.js index 1a5548bb1b6..c019087f898 100644 --- a/ui/app/controllers/settings/tokens.js +++ b/ui/app/controllers/settings/tokens.js @@ -90,13 +90,20 @@ export default class Tokens extends Controller { window.localStorage.setItem('nomadOIDCNonce', nonce); window.localStorage.setItem('nomadOIDCAuthMethod', provider); + let redirectURL; + if (Ember.testing) { + redirectURL = this.router.currentURL; + } else { + redirectURL = new URL(window.location.toString()); + redirectURL.search = ''; + redirectURL = redirectURL.href; + } + method .getAuthURL({ - AuthMethod: provider, + AuthMethodName: provider, ClientNonce: nonce, - RedirectUri: Ember.testing - ? this.router.currentURL - : window.location.toString(), + RedirectUri: redirectURL, }) .then(({ AuthURL }) => { if (Ember.testing) { @@ -111,7 +118,7 @@ export default class Tokens extends Controller { @tracked state = null; get isValidatingToken() { - if (this.code && this.state === 'success') { + if (this.code && this.state) { this.validateSSO(); return true; } else { @@ -120,25 +127,41 @@ export default class Tokens extends Controller { } async validateSSO() { + let redirectURL; + if (Ember.testing) { + redirectURL = this.router.currentURL; + } else { + redirectURL = new URL(window.location.toString()); + redirectURL.search = ''; + redirectURL = redirectURL.href; + } + const res = await this.token.authorizedRequest( '/v1/acl/oidc/complete-auth', { method: 'POST', body: JSON.stringify({ - AuthMethod: window.localStorage.getItem('nomadOIDCAuthMethod'), + AuthMethodName: window.localStorage.getItem('nomadOIDCAuthMethod'), ClientNonce: window.localStorage.getItem('nomadOIDCNonce'), Code: this.code, State: this.state, + RedirectURI: redirectURL, }), } ); if (res.ok) { const data = await res.json(); - this.token.set('secret', data.ACLToken); - this.verifyToken(); + this.clearTokenProperties(); + this.token.set('secret', data.SecretID); this.state = null; this.code = null; + + // Refetch the token and associated policies + this.get('token.fetchSelfTokenAndPolicies').perform().catch(); + + this.signInStatus = 'success'; + this.token.set('tokenNotFound', false); } else { this.state = 'failure'; this.code = null; diff --git a/ui/app/templates/settings/tokens.hbs b/ui/app/templates/settings/tokens.hbs index 33918cf829f..dd8dc64c05a 100644 --- a/ui/app/templates/settings/tokens.hbs +++ b/ui/app/templates/settings/tokens.hbs @@ -82,7 +82,7 @@ class="button is-primary" onclick={{action "redirectToSSO" method}} type="button" - >Sign in with with {{method.name}} + >Sign in with {{method.name}} {{/each}} diff --git a/ui/mirage/config.js b/ui/mirage/config.js index e8196724913..86b7820f97b 100644 --- a/ui/mirage/config.js +++ b/ui/mirage/config.js @@ -443,7 +443,7 @@ export default function () { return JSON.stringify(findLeader(schema)); }); - this.get('/acl/tokens', function ({tokens}, req) { + this.get('/acl/tokens', function ({ tokens }, req) { return this.serialize(tokens.all()); }); @@ -548,9 +548,14 @@ export default function () { this.delete('/acl/policy/:id', function (schema, request) { const { id } = request.params; - schema.tokens.all().models.filter(token => token.policyIds.includes(id)).forEach(token => { - token.update({ policyIds: token.policyIds.filter(pid => pid !== id) }); - }); + schema.tokens + .all() + .models.filter((token) => token.policyIds.includes(id)) + .forEach((token) => { + token.update({ + policyIds: token.policyIds.filter((pid) => pid !== id), + }); + }); server.db.policies.remove(id); return ''; }); @@ -566,7 +571,6 @@ export default function () { description: Description, rules: Rules, }); - }); this.get('/regions', function ({ regions }) { @@ -979,26 +983,37 @@ export default function () { return schema.authMethods.all(); }); this.post('/acl/oidc/auth-url', (schema, req) => { - const {AuthMethod, ClientNonce, RedirectUri, Meta} = JSON.parse(req.requestBody); - return new Response(200, {}, { - AuthURL: `/ui/oidc-mock?auth_method=${AuthMethod}&client_nonce=${ClientNonce}&redirect_uri=${RedirectUri}&meta=${Meta}` - }); + const { AuthMethodName, ClientNonce, RedirectUri, Meta } = JSON.parse( + req.requestBody + ); + return new Response( + 200, + {}, + { + AuthURL: `/ui/oidc-mock?auth_method=${AuthMethodName}&client_nonce=${ClientNonce}&redirect_uri=${RedirectUri}&meta=${Meta}`, + } + ); }); // Simulate an OIDC callback by assuming the code passed is the secret of an existing token, and return that token. - this.post('/acl/oidc/complete-auth', function (schema, req) { - const code = JSON.parse(req.requestBody).Code; - const token = schema.tokens.findBy({ - id: code - }); - - return new Response(200, {}, { - ACLToken: token.secretId - }); - }, {timing: 1000}); - - + this.post( + '/acl/oidc/complete-auth', + function (schema, req) { + const code = JSON.parse(req.requestBody).Code; + const token = schema.tokens.findBy({ + id: code, + }); + return new Response( + 200, + {}, + { + SecretID: token.secretId, + } + ); + }, + { timing: 1000 } + ); //#endregion SSO }