ui: Improve dev-time SSO/OIDC visibility (#11248)

This commit tries to make the development experience of working on our OIDC support a little more realistic, essentially by creating our own OIDC provider in our application (only during development builds). You can still provide a real OIDC provider to work with via our dev time environment/cookie variables as before, just now we default to the behaviour in this commit. Overall this makes it much easier to verify our OIDC support in the UI, and also opens up avenues for us to be able to test more scenarios that we couldn't before (for example not only successful logins, but also erroneous, potentially with multiple error reasons).
This commit is contained in:
John Cowen 2021-10-11 16:03:59 +01:00 committed by GitHub
parent c059e57f33
commit 569c92c10c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 107 additions and 14 deletions

View File

@ -19,7 +19,7 @@
{{~/if~}} {{~/if~}}
{{~else~}} {{~else~}}
<button <button
type="button" type={{or @type 'button'}}
{{on 'click' (optional @onclick)}} {{on 'click' (optional @onclick)}}
tabindex={{@tabindex}} tabindex={{@tabindex}}
...attributes ...attributes

View File

@ -27,7 +27,7 @@ export default class Outlet extends Component {
} }
setAppRoute(name) { setAppRoute(name) {
if (name !== 'loading') { if (name !== 'loading' || name === 'oidc-provider-debug') {
const doc = this.element.ownerDocument.documentElement; const doc = this.element.ownerDocument.documentElement;
if (doc.classList.contains('ember-loading')) { if (doc.classList.contains('ember-loading')) {
doc.classList.remove('ember-loading'); doc.classList.remove('ember-loading');

View File

@ -26,7 +26,7 @@ export default class BaseRoute extends Route {
this.templateName = template; this.templateName = template;
} }
const queryParams = get(routes, `${routeName}._options.queryParams`); const queryParams = get(routes, `${routeName}._options.queryParams`);
if(queryParams && this.routeName === 'dc.partitions.index') { if(queryParams && (this.routeName === 'dc.partitions.index' || this.routeName === 'oauth-provider-debug')) {
this.queryParams = queryParams; this.queryParams = queryParams;
} }
} }

View File

@ -1,4 +1,6 @@
import OAuth2CodeProvider from 'torii/providers/oauth2-code'; import OAuth2CodeProvider from 'torii/providers/oauth2-code';
import { runInDebug } from '@ember/debug';
export default class OAuth2CodeWithURLProvider extends OAuth2CodeProvider { export default class OAuth2CodeWithURLProvider extends OAuth2CodeProvider {
name = 'oidc-with-url'; name = 'oidc-with-url';
@ -16,11 +18,13 @@ export default class OAuth2CodeWithURLProvider extends OAuth2CodeProvider {
.open(url, responseParams, options) .open(url, responseParams, options)
.then(function(authData) { .then(function(authData) {
// the same as the parent class but with an authorizationState added // the same as the parent class but with an authorizationState added
return { const creds = {
authorizationState: authData.state, authorizationState: authData.state,
authorizationCode: decodeURIComponent(authData[responseType]), authorizationCode: decodeURIComponent(authData[responseType]),
provider: name, provider: name,
}; };
runInDebug(_ => console.log('Retrieved the following creds from the OAuth Provider', creds))
return creds;
}); });
} }

View File

@ -36,6 +36,8 @@ as |source|>
/> />
{{/if}} {{/if}}
{{#if (not-eq route.currentName 'oauth-provider-debug')}}
{{! redirect if we aren't on a URL with dc information }} {{! redirect if we aren't on a URL with dc information }}
{{#if (eq route.currentName 'index')}} {{#if (eq route.currentName 'index')}}
{{did-insert (route-action 'replaceWith' 'dc.services.index' {{did-insert (route-action 'replaceWith' 'dc.services.index'
@ -156,4 +158,17 @@ as |dc dcs|}}
</DataSource> </DataSource>
{{/let}} {{/let}}
{{/if}} {{/if}}
{{else}}
{{! Routes with no main navigation }}
<Outlet
@name="application"
@model={{hash
user=(hash
token=token
)
}}
as |o|>
{{outlet}}
</Outlet>
{{/if}}
</Route> </Route>

View File

@ -0,0 +1,38 @@
<Route
@name={{routeName}}
as |route|>
<div
style="width: 50%;margin: 0 auto;"
>
<h1><route.Title @title="Mock OAuth Provider" /></h1>
<main>
<form
method="GET"
action={{redirect_uri}}
>
{{#let (hash
state="state-123456789/abcdefghijklmnopqrstuvwxyz"
code="code-abcdefghijklmnopqrstuvwxyz/123456789"
) as |item|}}
<TextInput
@name="state"
@label="State"
@item={{item}}
@help="The OIDC state value that will get passed through to Consul"
/>
<TextInput
@name="code"
@label="Code"
@item={{item}}
@help="The OIDC code value that will get passed through to Consul"
/>
{{/let}}
<Action
@type="submit"
>
Login
</Action>
</form>
</main>
</div>
</Route>

View File

@ -13,6 +13,8 @@ const repositorySHA = utils.repositorySHA;
const binaryVersion = utils.binaryVersion(repositoryRoot); const binaryVersion = utils.binaryVersion(repositoryRoot);
module.exports = function(environment, $ = process.env) { module.exports = function(environment, $ = process.env) {
// available environments
// ['production', 'development', 'staging', 'test'];
const env = utils.env($); const env = utils.env($);
// basic 'get env var with fallback' accessor // basic 'get env var with fallback' accessor
@ -23,10 +25,9 @@ module.exports = function(environment, $ = process.env) {
locationType: 'fsm-with-optional', locationType: 'fsm-with-optional',
historySupportMiddleware: true, historySupportMiddleware: true,
// We use a complete dynamically (from Consul) configured torii provider. torii: {
// We provide this object here to prevent ember from giving a log message disableRedirectInitializer: false
// when starting ember up },
torii: {},
EmberENV: { EmberENV: {
FEATURES: { FEATURES: {
@ -128,8 +129,18 @@ module.exports = function(environment, $ = process.env) {
}), }),
}); });
break; break;
case environment === 'development':
ENV = Object.assign({}, ENV, {
torii: {
disableRedirectInitializer: true
},
});
break;
case environment === 'staging': case environment === 'staging':
ENV = Object.assign({}, ENV, { ENV = Object.assign({}, ENV, {
torii: {
disableRedirectInitializer: true
},
// On staging sites everything defaults to being turned on by // On staging sites everything defaults to being turned on by
// different staging sites can be built with certain features disabled // different staging sites can be built with certain features disabled
// by setting an environment variable to 0 during building (e.g. // by setting an environment variable to 0 during building (e.g.

View File

@ -149,7 +149,12 @@ module.exports = function(defaults, $ = process.env) {
outputFile: `assets/${item.name}/routes.js`, outputFile: `assets/${item.name}/routes.js`,
}); });
}); });
['consul-ui/services'].concat(devlike ? ['consul-ui/services-debug'] : []).forEach(item => { [
'consul-ui/services'
].concat(devlike ? [
'consul-ui/services-debug',
'consul-ui/routes-debug'
] : []).forEach(item => {
app.import(`vendor/${item}.js`, { app.import(`vendor/${item}.js`, {
outputFile: `assets/${item}.js`, outputFile: `assets/${item}.js`,
}); });

View File

@ -46,6 +46,7 @@ ${
environment === 'development' || environment === 'staging' environment === 'development' || environment === 'staging'
? ` ? `
<script data-app-name="${appName}" data-${appName}-services src="${rootURL}assets/consul-ui/services-debug.js"></script> <script data-app-name="${appName}" data-${appName}-services src="${rootURL}assets/consul-ui/services-debug.js"></script>
<script data-app-name="${appName}" data-${appName}-routing src="${rootURL}assets/consul-ui/routes-debug.js"></script>
` : ``} ` : ``}
${ ${
environment === 'production' environment === 'production'

View File

@ -1,3 +1,3 @@
{ {
"AuthURL": "${env('CONSUL_OIDC_PROVIDER_URL')}&redirect_uri=${encodeURIComponent(http.body.RedirectURI)}&response_type=code&scope=openid" "AuthURL": "${env('CONSUL_OIDC_PROVIDER_URL', 'http://localhost:4200/ui/oauth-provider-debug?client_id=oauth-double&nonce=1&state=123456789abc')}&redirect_uri=${encodeURIComponent(http.body.RedirectURI)}&response_type=code&scope=openid"
} }

View File

@ -6,7 +6,7 @@
typeof http.body.Namespace !== 'undefined' ? http.body.Namespace : 'default' typeof http.body.Namespace !== 'undefined' ? http.body.Namespace : 'default'
}", }",
"Local": false, "Local": false,
"Description": "${fake.lorem.sentence()}", "Description": "AuthMethod: ${http.body.AuthMethod}; Code: ${http.body.Code}; State: ${http.body.State}; - ${fake.lorem.sentence()}",
"Policies": [ "Policies": [
${ ${
range(env('CONSUL_POLICY_COUNT', 3)).map( range(env('CONSUL_POLICY_COUNT', 3)).map(

View File

@ -0,0 +1,19 @@
(routes => routes({
['oauth-provider-debug']: {
_options: {
path: '/oauth-provider-debug',
queryParams: {
redirect_uri: 'redirect_uri',
response_type: 'response_type',
scope: 'scope',
},
}
},
}))(
(json, data = document.currentScript.dataset) => {
const appNameJS = data.appName.split('-')
.map((item, i) => i ? `${item.substr(0, 1).toUpperCase()}${item.substr(1)}` : item)
.join('');
data[`${appNameJS}Routes`] = JSON.stringify(json);
}
);