open-consul/ui/packages/consul-ui/app/routing/route.js
John Cowen 569c92c10c
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).
2021-10-11 16:03:59 +01:00

185 lines
6.1 KiB
JavaScript

import Route from '@ember/routing/route';
import { get, setProperties, action } from '@ember/object';
import { inject as service } from '@ember/service';
import HTTPError from 'consul-ui/utils/http/error';
// paramsFor
import { routes } from 'consul-ui/router';
import wildcard from 'consul-ui/utils/routing/wildcard';
const isWildcard = wildcard(routes);
export default class BaseRoute extends Route {
@service('container') container;
@service('env') env;
@service('repository/permission') permissions;
@service('router') router;
_setRouteName() {
super._setRouteName(...arguments);
const routeName = this.routeName
.split('.')
.filter(item => item !== 'index')
.join('.');
const template = get(routes, `${routeName}._options.template`);
if(template) {
this.templateName = template;
}
const queryParams = get(routes, `${routeName}._options.queryParams`);
if(queryParams && (this.routeName === 'dc.partitions.index' || this.routeName === 'oauth-provider-debug')) {
this.queryParams = queryParams;
}
}
redirect(model, transition) {
// remove any references to index as it is the same as the root routeName
const routeName = this.routeName
.split('.')
.filter(item => item !== 'index')
.join('.');
const to = get(routes, `${routeName}._options.redirect`);
if (typeof to !== 'undefined') {
// TODO: Does this need to return?
// Almost remember things getting strange if you returned from here
// which is why I didn't do it originally so be sure to look properly if
// you feel like adding a return
this.replaceWith(`${routeName}${to}`, model);
}
}
/**
* Inspects a custom `abilities` array on the router for this route. Every
* abililty needs to 'pass' for the route not to throw a 403 error. Anything
* more complex then this (say ORs) should use a single ability and perform
* the OR logic in the test for the ability. Note, this ability check happens
* before any calls to the backend for this model/route.
*/
async beforeModel() {
// remove any references to index as it is the same as the root routeName
const routeName = this.routeName
.split('.')
.filter(item => item !== 'index')
.join('.');
const abilities = get(routes, `${routeName}._options.abilities`) || [];
if (abilities.length > 0) {
if (!abilities.every(ability => this.permissions.can(ability))) {
throw new HTTPError('403');
}
}
}
/**
* By default any empty string query parameters should remove the query
* parameter from the URL. This is the most common behavior if you don't
* require this behavior overwrite this method in the specific Route for the
* specific queryParam key.
* If the behaviour should be different add an empty: [] parameter to the
* queryParameter configuration to configure what is deemed 'empty'
*/
serializeQueryParam(value, key, type) {
if (typeof value !== 'undefined') {
const empty = get(this, `queryParams.${key}.empty`);
if (typeof empty === 'undefined') {
// by default any queryParams when an empty string mean undefined,
// therefore remove the queryParam from the URL
if (value === '') {
value = undefined;
}
} else {
const possible = empty[0];
let actual = value;
if (Array.isArray(actual)) {
actual = actual.split(',');
}
const diff = possible.filter(item => !actual.includes(item));
if (diff.length === 0) {
value = undefined;
}
}
}
return value;
}
// TODO: this is only required due to intention_id trying to do too much
// therefore we need to change the route parameter intention_id to just
// intention or id or similar then we can revert to only returning a model if
// we have searchProps (or a child route overwrites model)
model() {
const model = {};
if (
typeof this.queryParams !== 'undefined' &&
typeof this.queryParams.searchproperty !== 'undefined'
) {
model.searchProperties = this.queryParams.searchproperty.empty[0];
}
return model;
}
/**
* Set the routeName for the controller so that it is available in the template
* for the route/controller.. This is mainly used to give a route name to the
* Outlet component
*/
setupController(controller, model) {
setProperties(controller, {
...model,
routeName: this.routeName,
});
super.setupController(...arguments);
}
optionalParams() {
return this.container.get(`location:${this.env.var('locationType')}`).optionalParams();
}
/**
* Adds urldecoding to any wildcard route `params` passed into ember `model`
* hooks, plus of course anywhere else where `paramsFor` is used. This means
* the entire ember app is now changed so that all paramsFor calls returns
* urldecoded params instead of raw ones.
* For example we use this largely for URLs for the KV store:
* /kv/*key > /ui/kv/%25-kv-name/%25-here > key = '%-kv-name/%-here'
*/
paramsFor(name) {
const params = super.paramsFor(...arguments);
if (isWildcard(this.routeName)) {
return Object.keys(params).reduce(function(prev, item) {
if (typeof params[item] !== 'undefined') {
prev[item] = decodeURIComponent(params[item]);
} else {
prev[item] = params[item];
}
return prev;
}, {});
} else {
return params;
}
}
@action
async replaceWith(routeName, obj) {
await Promise.resolve();
let params = [];
if (typeof obj === 'string') {
params = [obj];
}
if (typeof obj !== 'undefined' && !Array.isArray(obj) && typeof obj !== 'string') {
params = Object.values(obj);
}
return super.replaceWith(routeName, ...params);
}
@action
async transitionTo(routeName, obj) {
await Promise.resolve();
let params = [];
if (typeof obj === 'string') {
params = [obj];
}
if (typeof obj !== 'undefined' && !Array.isArray(obj) && typeof obj !== 'string') {
params = Object.values(obj);
}
return super.transitionTo(routeName, ...params);
}
}