Skip to content

Commit

Permalink
[sso] OIDC Updates for the UI (#15804)
Browse files Browse the repository at this point in the history
* Updated UI to handle OIDC method changes

* Remove redundant store unload call
  • Loading branch information
philrenaud authored Jan 17, 2023
1 parent ebc76d2 commit d57b805
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 43 deletions.
8 changes: 6 additions & 2 deletions command/acl_auth_method_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 0 additions & 8 deletions nomad/acl_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions ui/app/adapters/auth-method.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down
39 changes: 31 additions & 8 deletions ui/app/controllers/settings/tokens.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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 {
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion ui/app/templates/settings/tokens.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
</button>
{{/each}}
</div>
Expand Down
57 changes: 36 additions & 21 deletions ui/mirage/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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());
});

Expand Down Expand Up @@ -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 '';
});
Expand All @@ -566,7 +571,6 @@ export default function () {
description: Description,
rules: Rules,
});

});

this.get('/regions', function ({ regions }) {
Expand Down Expand Up @@ -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
}
Expand Down

0 comments on commit d57b805

Please sign in to comment.