[MM 17255] Fix OneLogin and other OAUTH/SAML custom login attempts (#1025)

* allow custom login activity (oath, saml)

* fix electron missing cursor bug

* change variable name

* tweaks to custom login process
This commit is contained in:
Dean Whillier
2019-09-16 16:02:37 -04:00
committed by GitHub
parent 99fae82514
commit 171e4a03a7
2 changed files with 115 additions and 11 deletions

View File

@@ -120,7 +120,10 @@ export default class MattermostView extends React.Component {
// So this would be emitted again when reloading a webview
webview.addEventListener('dom-ready', () => {
// webview.openDevTools();
// Remove this once https://github.com/electron/electron/issues/14474 is fixed
// - fixes missing cursor bug in electron
webview.blur();
webview.focus();
if (!this.state.isContextMenuAdded) {
contextMenu.setup(webview, {
useSpellChecker: this.props.useSpellChecker,

View File

@@ -67,6 +67,23 @@ let config = null;
let trayIcon = null;
let trayImages = null;
// supported custom login paths (oath, saml)
const customLoginRegexPaths = [
/^\/oauth\/authorize$/i,
/^\/oauth\/deauthorize$/i,
/^\/oauth\/access_token$/i,
/^\/oauth\/[A-Za-z0-9]+\/complete$/i,
/^\/oauth\/[A-Za-z0-9]+\/login$/i,
/^\/oauth\/[A-Za-z0-9]+\/signup$/i,
/^\/api\/v3\/oauth\/[A-Za-z0-9]+\/complete$/i,
/^\/signup\/[A-Za-z0-9]+\/complete$/i,
/^\/login\/[A-Za-z0-9]+\/complete$/i,
/^\/login\/sso\/saml$/i,
];
// tracking in progress custom logins
const customLogins = {};
/**
* Main entry point for the application, ensures that everything initializes in the proper order
*/
@@ -361,26 +378,77 @@ function handleAppWillFinishLaunching() {
}
function handleAppWebContentsCreated(dc, contents) {
// initialize custom login tracking
customLogins[contents.id] = {
inProgress: false,
startingURL: null,
externalHostname: null,
};
contents.on('will-attach-webview', (event, webPreferences) => {
webPreferences.nodeIntegration = false;
webPreferences.contextIsolation = true;
});
contents.on('will-navigate', (event, navigationUrl) => {
const parsedUrl = new URL(navigationUrl);
const trustedURLs = config.teams.map((team) => new URL(team.url)); //eslint-disable-line max-nested-callbacks
let trusted = false;
for (const url of trustedURLs) {
if (parsedUrl.origin === url.origin) {
trusted = true;
break;
contents.on('will-navigate', (event, url) => {
const contentID = event.sender.id;
const parsedUrl = new URL(url);
const urlIsTrusted = isTrustedURL(parsedUrl);
const urlIsTrustedExternalLoginPath = parsedUrl.hostname === customLogins[contentID].externalHostname;
// don't prevent custom login attempts (oath, saml)
if (!urlIsTrusted && !urlIsTrustedExternalLoginPath) {
event.preventDefault();
// reset custom login if in progress
if (customLogins[contentID].inProgress) {
customLogins[contentID].inProgress = false;
customLogins[contentID].externalHostname = null;
// redirect to starting url if set
if (customLogins[contentID].startingURL) {
event.sender.loadURL(customLogins[contentID].startingURL);
customLogins[contentID].startingURL = null;
}
}
}
});
if (!trusted) {
event.preventDefault();
// handle custom login requests (oath, saml):
// 1. are we navigating to a supported local custom login path from the `/login` page? (did-start-navigation listener)
// - indicate custom login is in progress and store starting point for possible reset
// 2. is a custom login in progress but we don't have the 3rd party hostname yet? (will-redirect listener)
// - store 3rd party hostname to trust subsequent navigation changes within that hostname
// 3. are we finished with the custom login process? (did-start-navigation listener)
// - indicate custom login is NOT in progress and clear any stored 3rd party hostname's
contents.on('did-start-navigation', (event, url) => {
const contentID = event.sender.id;
const parsedUrl = new URL(url);
const urlIsTrusted = isTrustedURL(parsedUrl);
const urlIsCustomLoginPath = isCustomLoginURL(parsedUrl);
const previousPage = event.sender.history[event.sender.history.length - 1];
if (urlIsTrusted && urlIsCustomLoginPath && !customLogins[contentID].inProgress && previousPage.endsWith('/login')) {
customLogins[contentID].inProgress = true;
customLogins[contentID].startingURL = event.sender.getURL();
} else if (urlIsTrusted && customLogins[contentID].inProgress && customLogins[contentID].externalHostname) {
customLogins[contentID].inProgress = false;
customLogins[contentID].externalHostname = null;
}
});
contents.on('will-redirect', (event, url) => {
const contentID = event.sender.id;
const parsedUrl = new URL(url);
const urlIsTrusted = isTrustedURL(parsedUrl);
const previousPage = event.sender.history[event.sender.history.length - 1];
const previousPageIsTrusted = isTrustedURL(previousPage);
if (!urlIsTrusted && previousPageIsTrusted && customLogins[contentID].inProgress && !customLogins[contentID].externalHostname) {
customLogins[contentID].externalHostname = parsedUrl.hostname;
}
});
contents.on('new-window', (event) => {
event.preventDefault();
});
@@ -674,6 +742,39 @@ function handleMainWindowWebContentsCrashed() {
// helper functions
//
function isTrustedURL(url) {
let parsedUrl = url;
if (typeof url === 'string') {
parsedUrl = new URL(url);
}
const trustedURLs = config.teams.map((team) => new URL(team.url));
for (const trustedURL of trustedURLs) {
if (parsedUrl.origin === trustedURL.origin) {
return true;
}
}
return false;
}
function isCustomLoginURL(url) {
if (!isTrustedURL(url)) {
return false;
}
let parsedUrl = url;
if (typeof url === 'string') {
parsedUrl = new URL(url);
}
const urlPath = parsedUrl.pathname;
for (const regexPath of customLoginRegexPaths) {
if (urlPath.match(regexPath)) {
return true;
}
}
return false;
}
function getTrayImages() {
switch (process.platform) {
case 'win32':