[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:
@@ -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,
|
||||
|
121
src/main.js
121
src/main.js
@@ -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':
|
||||
|
Reference in New Issue
Block a user