[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
|
// So this would be emitted again when reloading a webview
|
||||||
webview.addEventListener('dom-ready', () => {
|
webview.addEventListener('dom-ready', () => {
|
||||||
// webview.openDevTools();
|
// 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) {
|
if (!this.state.isContextMenuAdded) {
|
||||||
contextMenu.setup(webview, {
|
contextMenu.setup(webview, {
|
||||||
useSpellChecker: this.props.useSpellChecker,
|
useSpellChecker: this.props.useSpellChecker,
|
||||||
|
123
src/main.js
123
src/main.js
@@ -67,6 +67,23 @@ let config = null;
|
|||||||
let trayIcon = null;
|
let trayIcon = null;
|
||||||
let trayImages = 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
|
* Main entry point for the application, ensures that everything initializes in the proper order
|
||||||
*/
|
*/
|
||||||
@@ -361,26 +378,77 @@ function handleAppWillFinishLaunching() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleAppWebContentsCreated(dc, contents) {
|
function handleAppWebContentsCreated(dc, contents) {
|
||||||
|
// initialize custom login tracking
|
||||||
|
customLogins[contents.id] = {
|
||||||
|
inProgress: false,
|
||||||
|
startingURL: null,
|
||||||
|
externalHostname: null,
|
||||||
|
};
|
||||||
|
|
||||||
contents.on('will-attach-webview', (event, webPreferences) => {
|
contents.on('will-attach-webview', (event, webPreferences) => {
|
||||||
webPreferences.nodeIntegration = false;
|
webPreferences.nodeIntegration = false;
|
||||||
webPreferences.contextIsolation = true;
|
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;
|
contents.on('will-navigate', (event, url) => {
|
||||||
for (const url of trustedURLs) {
|
const contentID = event.sender.id;
|
||||||
if (parsedUrl.origin === url.origin) {
|
const parsedUrl = new URL(url);
|
||||||
trusted = true;
|
const urlIsTrusted = isTrustedURL(parsedUrl);
|
||||||
break;
|
const urlIsTrustedExternalLoginPath = parsedUrl.hostname === customLogins[contentID].externalHostname;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!trusted) {
|
// don't prevent custom login attempts (oath, saml)
|
||||||
|
if (!urlIsTrusted && !urlIsTrustedExternalLoginPath) {
|
||||||
event.preventDefault();
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 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) => {
|
contents.on('new-window', (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
@@ -674,6 +742,39 @@ function handleMainWindowWebContentsCrashed() {
|
|||||||
// helper functions
|
// 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() {
|
function getTrayImages() {
|
||||||
switch (process.platform) {
|
switch (process.platform) {
|
||||||
case 'win32':
|
case 'win32':
|
||||||
|
Reference in New Issue
Block a user