[sso] OIDC Updates for the UI (#15804)

* Updated UI to handle OIDC method changes

* Remove redundant store unload call
This commit is contained in:
Phil Renaud
2023-01-17 17:01:47 -05:00
committed by GitHub
parent ebc76d23da
commit d57b805780
6 changed files with 77 additions and 43 deletions

View File

@@ -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)

View File

@@ -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,

View File

@@ -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,

View File

@@ -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;

View File

@@ -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>

View File

@@ -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
}