mirror of https://github.com/rancher/ui.git
Merge pull request #4494 from mantis-toboggan-md/auth-redirect
github/azuread nonce with ui type
This commit is contained in:
commit
0a4eaaf732
|
|
@ -26,7 +26,7 @@ Router.map(function() {
|
|||
this.route('not-found', { path: '*path' });
|
||||
|
||||
this.route('signup' );
|
||||
this.route('verify', { path: '/verify/:verify_token' });
|
||||
this.route('verify');
|
||||
this.route('verify-reset-password', { path: '/verify-reset-password/:verify_token' });
|
||||
|
||||
this.route('logout');
|
||||
|
|
|
|||
|
|
@ -1,50 +1,7 @@
|
|||
import Route from '@ember/routing/route';
|
||||
import { get } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import C from 'shared/utils/constants';
|
||||
import VerifyAuth from 'ui/mixins/verify-auth';
|
||||
|
||||
export default Route.extend(VerifyAuth, {
|
||||
azureAd: service(),
|
||||
|
||||
|
||||
model(params/* , transition */) {
|
||||
if (window.opener) {
|
||||
const stateMsg = 'Authorization state did not match, please try again.';
|
||||
|
||||
if (get(params, 'code') && window.opener.window.onAzureTest) {
|
||||
reply(null, get(params, 'code'));
|
||||
} else {
|
||||
reply(stateMsg);
|
||||
}
|
||||
}
|
||||
|
||||
if (get(params, 'code') && !window.opener) {
|
||||
let azureProvider = get(this, 'access.providers').findBy('id', 'azuread');
|
||||
|
||||
return azureProvider.doAction('login', {
|
||||
code: get(params, 'code'),
|
||||
description: C.SESSION.DESCRIPTION,
|
||||
responseType: 'cookie',
|
||||
ttl: C.SESSION.TTL,
|
||||
}).then(() => {
|
||||
return get(this, 'access').detect()
|
||||
.then(() => this.transitionTo('authenticated'));
|
||||
});
|
||||
}
|
||||
|
||||
function reply(err, code) {
|
||||
const opener = window.opener.window;
|
||||
|
||||
if (opener.onAzureTest) {
|
||||
opener.onAzureTest(err, code);
|
||||
|
||||
setTimeout(() => {
|
||||
window.close();
|
||||
}, 250);
|
||||
} else {
|
||||
window.close();
|
||||
}
|
||||
}
|
||||
export default Route.extend( {
|
||||
beforeModel() {
|
||||
this.transitionTo('verify');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -1,135 +1,7 @@
|
|||
import Route from '@ember/routing/route';
|
||||
import RSVP from 'rsvp';
|
||||
import { get } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import C from 'shared/utils/constants';
|
||||
import { addQueryParams, parseUrl } from 'shared/utils/util';
|
||||
import { reject } from 'rsvp';
|
||||
import VerifyAuth from 'ui/mixins/verify-auth';
|
||||
|
||||
const samlProviders = ['ping', 'adfs', 'keycloak', 'okta', 'shibboleth'];
|
||||
const allowedForwards = ['localhost'];
|
||||
|
||||
export default Route.extend(VerifyAuth, {
|
||||
oauth: service(),
|
||||
intl: service(),
|
||||
language: service('user-language'),
|
||||
|
||||
export default Route.extend( {
|
||||
beforeModel() {
|
||||
if (!this.intl.locale) {
|
||||
return get(this, 'language').initUnauthed();
|
||||
}
|
||||
},
|
||||
|
||||
model(params/* , transition */) {
|
||||
const oauth = get(this, 'oauth');
|
||||
const code = get(params, 'code');
|
||||
const forward = get(params, 'forward');
|
||||
|
||||
// Allow another redirect if the hostname is in the whitelist above.
|
||||
// This allows things like sharing github auth between rancher at localhost:8000
|
||||
// and rio dev at localhost:8004
|
||||
if ( forward ) {
|
||||
const parsed = parseUrl(forward);
|
||||
|
||||
if ( allowedForwards.includes(parsed.hostname.toLowerCase()) ) {
|
||||
if ( get(params, 'login') ) {
|
||||
window.location.href = addQueryParams(forward, {
|
||||
forwarded: 'true',
|
||||
code
|
||||
});
|
||||
} else {
|
||||
oauth.login(forward);
|
||||
}
|
||||
} else {
|
||||
return reject(new Error('Invalid forward url'));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ( window.opener && !get(params, 'login') && !get(params, 'errorCode') ) {
|
||||
let openersOauth = window.opener.ls('oauth');
|
||||
let openerStore = window.opener.ls('globalStore');
|
||||
let qp = get(params, 'config') || get(params, 'authProvider');
|
||||
let type = `${ qp }Config`;
|
||||
let config = openerStore.getById(type, qp);
|
||||
let stateMsg = 'Authorization state did not match, please try again.';
|
||||
let isGithub = get(params, 'config') === 'github'
|
||||
let isGoogle = get(params, 'config') === 'googleoauth'
|
||||
|
||||
if ( isGithub || isGoogle ) {
|
||||
return oauth.testConfig(config).then((resp) => {
|
||||
oauth.authorize(resp, openersOauth.get('state'));
|
||||
}).catch((err) => {
|
||||
reply({ err });
|
||||
});
|
||||
} else if ( samlProviders.includes(get(params, 'config')) ) {
|
||||
if ( window.opener.window.onAuthTest ) {
|
||||
reply(null, config);
|
||||
} else {
|
||||
reply({ err: 'failure' });
|
||||
}
|
||||
}
|
||||
|
||||
if ( get(params, 'code') ) {
|
||||
if ( openersOauth.stateMatches(get(params, 'state')) ) {
|
||||
reply(params.error_description, params.code);
|
||||
} else {
|
||||
reply(stateMsg);
|
||||
}
|
||||
}
|
||||
|
||||
if ( get(params, 'oauth_token') && get(params, 'oauth_verifier') ) {
|
||||
reply(null, {
|
||||
oauthToken: get(params, 'oauth_token'),
|
||||
oauthVerifier: get(params, 'oauth_verifier'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if ( code && get(params, 'state').includes('login') ) {
|
||||
const providerType = get(params, 'state').includes('github') ? 'github' : 'googleoauth'
|
||||
|
||||
if ( oauth.stateMatches(get(params, 'state')) ) {
|
||||
const currentProvider = get(this, 'access.providers').findBy('id', providerType);
|
||||
|
||||
return currentProvider.doAction('login', {
|
||||
code,
|
||||
responseType: 'cookie',
|
||||
description: C.SESSION.DESCRIPTION,
|
||||
ttl: C.SESSION.TTL,
|
||||
}).then(() => {
|
||||
return get(this, 'access').detect()
|
||||
.then(() => this.transitionTo('authenticated'));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (get(params, 'errorCode')) {
|
||||
let errorMessageKey = get(params, 'errorMsg') || null;
|
||||
let errorMessageTranslation = this.intl.t('loginPage.error.unknown');
|
||||
let locale = this.intl.locale || ['en-us'];
|
||||
|
||||
if (errorMessageKey && this.intl.exists(`loginPage.error.${ errorMessageKey }`, locale)) {
|
||||
errorMessageTranslation = this.intl.t(`loginPage.error.${ errorMessageKey }`);
|
||||
}
|
||||
|
||||
reply(errorMessageTranslation, get(params, 'errorCode'));
|
||||
}
|
||||
|
||||
function reply(err, code) {
|
||||
try {
|
||||
window.opener.window.onAuthTest(err, code);
|
||||
setTimeout(() => {
|
||||
window.close();
|
||||
}, 250);
|
||||
|
||||
return new RSVP.promise();
|
||||
} catch (e) {
|
||||
window.close();
|
||||
}
|
||||
}
|
||||
this.transitionTo('verify');
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,38 +1,181 @@
|
|||
import { reject } from 'rsvp';
|
||||
import Route from '@ember/routing/route';
|
||||
import fetch from '@rancher/ember-api-store/utils/fetch';
|
||||
import RSVP from 'rsvp';
|
||||
import { get } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import C from 'shared/utils/constants';
|
||||
import { addQueryParams, parseUrl } from 'shared/utils/util';
|
||||
import { reject } from 'rsvp';
|
||||
import VerifyAuth from 'ui/mixins/verify-auth';
|
||||
|
||||
export default Route.extend({
|
||||
const samlProviders = ['ping', 'adfs', 'keycloak', 'okta', 'shibboleth'];
|
||||
const allowedForwards = ['localhost'];
|
||||
|
||||
model(params) {
|
||||
if (params.verify_token) {
|
||||
this.set('params', params);
|
||||
export default Route.extend(VerifyAuth, {
|
||||
oauth: service(),
|
||||
intl: service(),
|
||||
azureAD: service(),
|
||||
language: service('user-language'),
|
||||
|
||||
return fetch('/verify-token', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ token: params.verify_token })
|
||||
}).then((resp) => {
|
||||
if (resp.status >= 200 && resp.status < 300) {
|
||||
return resp.body;
|
||||
} else {
|
||||
return reject();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.transitionTo('/');
|
||||
beforeModel() {
|
||||
if (!this.intl.locale) {
|
||||
return get(this, 'language').initUnauthed();
|
||||
}
|
||||
},
|
||||
setupController(controller, model) {
|
||||
this._super(controller, model);
|
||||
controller.set('token', this.get('params.verify_token'));
|
||||
},
|
||||
activate() {
|
||||
$('BODY').addClass('container-farm'); // eslint-disable-line
|
||||
},
|
||||
|
||||
deactivate() {
|
||||
$('BODY').removeClass('container-farm'); // eslint-disable-line
|
||||
},
|
||||
model(params/* , transition */) {
|
||||
const oauth = get(this, 'oauth');
|
||||
const azure = get(this, 'azureAD');
|
||||
|
||||
const forward = get(params, 'forward');
|
||||
|
||||
// Allow another redirect if the hostname is in the whitelist above.
|
||||
// This allows things like sharing github auth between rancher at localhost:8000
|
||||
// and rio dev at localhost:8004
|
||||
if ( forward ) {
|
||||
const parsed = parseUrl(forward);
|
||||
|
||||
if ( allowedForwards.includes(parsed.hostname.toLowerCase()) ) {
|
||||
if ( get(params, 'login') ) {
|
||||
window.location.href = addQueryParams(forward, {
|
||||
forwarded: 'true',
|
||||
code
|
||||
});
|
||||
} else {
|
||||
oauth.login(forward);
|
||||
}
|
||||
} else {
|
||||
return reject(new Error('Invalid forward url'));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (get(params, 'errorCode')) {
|
||||
let errorMessageKey = get(params, 'errorMsg') || null;
|
||||
let errorMessageTranslation = this.intl.t('loginPage.error.unknown');
|
||||
let locale = this.intl.locale || ['en-us'];
|
||||
|
||||
if (errorMessageKey && this.intl.exists(`loginPage.error.${ errorMessageKey }`, locale)) {
|
||||
errorMessageTranslation = this.intl.t(`loginPage.error.${ errorMessageKey }`);
|
||||
}
|
||||
|
||||
reply(errorMessageTranslation, get(params, 'errorCode'));
|
||||
}
|
||||
|
||||
|
||||
|
||||
const code = get(params, 'code');
|
||||
const state = get(params, 'state')
|
||||
|
||||
/*
|
||||
presence of window.opener indicates this is inital setup popup, not login
|
||||
handle auth enable
|
||||
*/
|
||||
if (window.opener){
|
||||
let openersOauth = window.opener.ls('oauth');
|
||||
let openerStore = window.opener.ls('globalStore');
|
||||
let openersAzure = window.opener.ls('azure-ad')
|
||||
|
||||
if (!state){
|
||||
let provider = get(params, 'config') || get(params, 'authProvider')
|
||||
let type = `${ provider }Config`;
|
||||
let config = openerStore.getById(type, provider);
|
||||
|
||||
// do nothing & close popup for saml
|
||||
if ( samlProviders.includes(provider) ) {
|
||||
if ( window.opener.window.onAuthTest ) {
|
||||
reply(null, config);
|
||||
} else {
|
||||
reply({ err: 'failure' });
|
||||
}
|
||||
} else if (provider === 'azuread'){
|
||||
return azure.testConfig(config).then((resp) => {
|
||||
azure.test(resp, openersAzure.get('state'));
|
||||
}).catch((err) => {
|
||||
reply({ err });
|
||||
});
|
||||
} else {
|
||||
// redirect to 3rd party login for oauth
|
||||
return oauth.testConfig(config).then((resp) => {
|
||||
oauth.authorize(resp, openersOauth.get('state'));
|
||||
}).catch((err) => {
|
||||
reply({ err });
|
||||
});
|
||||
}
|
||||
// if state is defined, this route was hit via redirect from 3rd party auth;
|
||||
// validate nonce and close window
|
||||
} else {
|
||||
let stateMsg = 'Authorization state did not match, please try again.';
|
||||
let parsedState
|
||||
|
||||
try {
|
||||
parsedState = JSON.parse(oauth.decodeState(state))
|
||||
} catch {
|
||||
reply({ err: 'nonce' })
|
||||
}
|
||||
// handle github/google/azuread
|
||||
if ( get(params, 'code') ) {
|
||||
let openers = openersOauth
|
||||
|
||||
if ( openers.stateMatches(get(parsedState, 'nonce')) ) {
|
||||
reply(params.error_description, params.code);
|
||||
} else {
|
||||
reply(stateMsg);
|
||||
}
|
||||
|
||||
// handle bitbucket
|
||||
} else if ( get(params, 'oauth_token') && get(params, 'oauth_verifier') ) {
|
||||
reply(null, {
|
||||
oauthToken: get(params, 'oauth_token'),
|
||||
oauthVerifier: get(params, 'oauth_verifier'),
|
||||
});
|
||||
}
|
||||
}
|
||||
// handle login verification
|
||||
} else {
|
||||
let parsedState
|
||||
|
||||
try {
|
||||
parsedState = JSON.parse(oauth.decodeState(state))
|
||||
} catch {
|
||||
reply({ err: 'nonce' })
|
||||
}
|
||||
if (oauth.stateMatches(parsedState.nonce)){
|
||||
const providerType = parsedState.provider
|
||||
|
||||
const currentProvider = get(this, 'access.providers').findBy('id', providerType);
|
||||
|
||||
return currentProvider.doAction('login', {
|
||||
code,
|
||||
responseType: 'cookie',
|
||||
description: C.SESSION.DESCRIPTION,
|
||||
ttl: C.SESSION.TTL,
|
||||
}).then(() => {
|
||||
return get(this, 'access').detect()
|
||||
.then(() => this.transitionTo('authenticated'));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function reply(err, code) {
|
||||
try {
|
||||
let cb = window.opener.window.onAuthTest
|
||||
|
||||
if (window.opener.window.onAzureTest){
|
||||
cb = window.opener.window.onAzureTest
|
||||
}
|
||||
cb(err, code);
|
||||
setTimeout(() => {
|
||||
window.close();
|
||||
}, 250);
|
||||
|
||||
return new RSVP.promise();
|
||||
} catch (e) {
|
||||
window.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ export default Controller.extend(AuthMixin, {
|
|||
|
||||
setProperties(model, {
|
||||
accessMode: am,
|
||||
rancherUrl: `${ window.location.origin }/verify-auth-azure`
|
||||
rancherUrl: `${ window.location.origin }/verify-auth`
|
||||
});
|
||||
|
||||
var errors = model.validationErrors();
|
||||
|
|
@ -73,7 +73,7 @@ export default Controller.extend(AuthMixin, {
|
|||
}),
|
||||
|
||||
replyUrl: computed(() => {
|
||||
return `${ window.location.origin }/verify-auth-azure`;
|
||||
return `${ window.location.origin }/verify-auth`;
|
||||
}),
|
||||
|
||||
authenticationApplied(err) {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export default Service.extend({
|
|||
globalStore: service(),
|
||||
app: service(),
|
||||
intl: service(),
|
||||
oauth: service(),
|
||||
|
||||
testConfig(config) {
|
||||
return config.doAction('configureTest', config);
|
||||
|
|
@ -27,7 +28,10 @@ export default Service.extend({
|
|||
login() {
|
||||
const provider = get(this, 'access.providers').findBy('id', 'azuread');
|
||||
const authRedirect = get(provider, 'redirectUrl');
|
||||
const redirect = Util.addQueryParams(authRedirect, additionalRedirectParams);
|
||||
let redirect = Util.addQueryParams(authRedirect, additionalRedirectParams);
|
||||
|
||||
redirect = Util.addQueryParams(redirect, { state: this.oauth.encodeState(this.oauth.generateState('azuread')) });
|
||||
|
||||
|
||||
window.location.href = redirect;
|
||||
},
|
||||
|
|
@ -46,6 +50,10 @@ export default Service.extend({
|
|||
};
|
||||
|
||||
url = Util.addQueryParams(url, additionalRedirectParams);
|
||||
const state = this.oauth.encodeState(this.oauth.generateState('azuread'))
|
||||
|
||||
url = Util.addQueryParams(url, { state });
|
||||
|
||||
|
||||
const popup = window.open(url, 'rancherAuth', Util.popupWindowOptions());
|
||||
const intl = get(this, 'intl');
|
||||
|
|
|
|||
|
|
@ -15,16 +15,53 @@ export default Service.extend({
|
|||
intl: service(),
|
||||
authType: '',
|
||||
|
||||
generateState() {
|
||||
return set(this, 'session.oauthState', `${ Math.random() }`);
|
||||
generateState(provider = '') {
|
||||
const state = JSON.stringify({
|
||||
to: 'ember',
|
||||
provider,
|
||||
nonce: Math.random(),
|
||||
test: true
|
||||
})
|
||||
|
||||
|
||||
return set(this, 'session.oauthState', state);
|
||||
},
|
||||
|
||||
generateLoginStateKey(authType) {
|
||||
return set(this, 'session.oauthState', `${ Math.random() }login${ authType }`)
|
||||
generateLoginStateKey(provider) {
|
||||
const state = JSON.stringify({
|
||||
to: 'ember',
|
||||
provider,
|
||||
nonce: Math.random(),
|
||||
test: false
|
||||
})
|
||||
|
||||
return set(this, 'session.oauthState', state);
|
||||
},
|
||||
|
||||
encodeState(state){
|
||||
const m = {
|
||||
'+': '-',
|
||||
'/': '_',
|
||||
'=': ''
|
||||
}
|
||||
|
||||
return AWS.util.base64.encode(state).replace(/[+/]|=$/, (char) => m[char])
|
||||
},
|
||||
|
||||
decodeState(state){
|
||||
return AWS.util.base64.decode(`${ state.replace(/[-_]/, (char) => char === '-' ? '+' : '/') }=`).toString()
|
||||
},
|
||||
|
||||
stateMatches(actual) {
|
||||
return actual && get(this, 'session.oauthState') === actual;
|
||||
const state = get(this, 'session.oauthState')
|
||||
|
||||
try {
|
||||
const parsedState = JSON.parse(state)
|
||||
|
||||
return actual && actual === parsedState.nonce
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
},
|
||||
|
||||
testConfig(config) {
|
||||
|
|
@ -69,7 +106,7 @@ export default Service.extend({
|
|||
|
||||
let url = addQueryParams(authRedirect, {
|
||||
scope: authType === 'github' ? githubOauthScope : googleOauthScope,
|
||||
state: this.generateLoginStateKey(authType),
|
||||
state: this.encodeState(this.generateLoginStateKey(authType)),
|
||||
redirect_uri: redirect,
|
||||
});
|
||||
|
||||
|
|
@ -90,8 +127,8 @@ export default Service.extend({
|
|||
}
|
||||
};
|
||||
|
||||
set(this, 'state', this.generateState());
|
||||
let url = addQueryParams(`${ window.location.origin }/verify-auth`, { config: configName, });
|
||||
set(this, 'state', this.encodeState(this.generateState(config.name)));
|
||||
let url = addQueryParams(`${ window.location.origin }/verify`, { config: configName, });
|
||||
|
||||
const popup = window.open(url, 'rancherAuth', popupWindowOptions());
|
||||
const intl = get(this, 'intl');
|
||||
|
|
|
|||
|
|
@ -10,10 +10,9 @@ export default Service.extend({
|
|||
hostname: null,
|
||||
scheme: null,
|
||||
clientId: null,
|
||||
oauth: service(),
|
||||
|
||||
|
||||
generateState() {
|
||||
return set(this, 'session.oauthState', `${ Math.random() }`);
|
||||
},
|
||||
|
||||
redirectURL: computed(() => {
|
||||
return `${ window.location.origin }/verify-auth`;
|
||||
|
|
@ -24,7 +23,7 @@ export default Service.extend({
|
|||
|
||||
redirect = redirect.split('#')[0];
|
||||
var url = Util.addQueryParams(githubAuthUrl, {
|
||||
state: this.generateState(),
|
||||
state: this.oauth.encodeState(this.oauth.generateState('github')),
|
||||
redirect_uri: redirect
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue