From 293470ff9471eafbafa88ee98d0a42626914d8b0 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Mon, 1 Jun 2020 14:06:37 -0400 Subject: [PATCH] Handle SSO redirecting to a different URL than specified by the user (#4372) * Handle SSO redirecting to a different URL than specified by the user * Set Server URL based on the last redirect * Improve last redirect condition --- app/screens/select_server/select_server.js | 60 ++++++++++++++-------- app/screens/sso/sso.js | 26 +++++++--- 2 files changed, 58 insertions(+), 28 deletions(-) diff --git a/app/screens/select_server/select_server.js b/app/screens/select_server/select_server.js index fde4a382a..3613cc11c 100644 --- a/app/screens/select_server/select_server.js +++ b/app/screens/select_server/select_server.js @@ -147,19 +147,19 @@ export default class SelectServer extends PureComponent { } }; - getUrl = () => { - const urlParse = require('url-parse'); - let preUrl = urlParse(this.state.url, true); + getUrl = async (serverUrl, useHttp = false) => { + let url = this.sanitizeUrl(serverUrl, useHttp); - if (!preUrl.host || preUrl.protocol === 'file:') { - preUrl = urlParse('https://' + stripTrailingSlashes(this.state.url), true); + try { + const resp = await fetch(url, {method: 'HEAD'}); + if (resp?.rnfbRespInfo?.redirects?.length) { + url = resp.rnfbRespInfo.redirects[resp.rnfbRespInfo.redirects.length - 1]; + } + } catch { + // do nothing } - if (preUrl.protocol === 'http:') { - preUrl.protocol = 'https:'; - } - - return stripTrailingSlashes(preUrl.protocol + '//' + preUrl.host + preUrl.pathname); + return this.sanitizeUrl(url, useHttp); }; goToNextScreen = (screen, title, passProps = {}, navOptions = {}) => { @@ -187,8 +187,6 @@ export default class SelectServer extends PureComponent { }; handleConnect = preventDoubleTap(async () => { - const url = this.getUrl(); - Keyboard.dismiss(); if (this.state.connecting || this.state.connected) { @@ -197,7 +195,7 @@ export default class SelectServer extends PureComponent { return; } - if (!isValidUrl(url)) { + if (!isValidUrl(this.sanitizeUrl(this.state.url))) { this.setState({ error: { intl: { @@ -219,11 +217,11 @@ export default class SelectServer extends PureComponent { auto: true, certificate, }).build(); - this.pingServer(url); + this.pingServer(this.state.url); } }); } else { - this.pingServer(url); + this.pingServer(this.state.url); } }); @@ -301,7 +299,7 @@ export default class SelectServer extends PureComponent { resetToChannel(); }; - pingServer = (url, retryWithHttp = true) => { + pingServer = async (url, retryWithHttp = true) => { const { getPing, handleServerUrlChanged, @@ -315,8 +313,9 @@ export default class SelectServer extends PureComponent { error: null, }); - Client4.setUrl(url); - handleServerUrlChanged(url); + const serverUrl = await this.getUrl(url, !retryWithHttp); + Client4.setUrl(serverUrl); + handleServerUrlChanged(serverUrl); let cancel = false; this.cancelPing = () => { @@ -330,13 +329,16 @@ export default class SelectServer extends PureComponent { this.cancelPing = null; }; - getPing().then((result) => { + try { + const result = await getPing(); + if (cancel) { return; } if (result.error && retryWithHttp) { - this.pingServer(url.replace('https:', 'http:'), false); + const nurl = serverUrl.replace('https:', 'http:'); + this.pingServer(nurl, false); return; } @@ -350,7 +352,7 @@ export default class SelectServer extends PureComponent { connecting: false, error: result.error, }); - }).catch(() => { + } catch { if (cancel) { return; } @@ -358,9 +360,23 @@ export default class SelectServer extends PureComponent { this.setState({ connecting: false, }); - }); + } }; + sanitizeUrl = (url, useHttp = false) => { + const urlParse = require('url-parse'); + let preUrl = urlParse(url, true); + + if (!preUrl.host || preUrl.protocol === 'file:') { + preUrl = urlParse('https://' + stripTrailingSlashes(url), true); + } + + if (preUrl.protocol === 'http:' && !useHttp) { + preUrl.protocol = 'https:'; + } + return stripTrailingSlashes(preUrl.protocol + '//' + preUrl.host + preUrl.pathname); + } + scheduleSessionExpiredNotification = () => { const {intl} = this.context; const {actions} = this.props; diff --git a/app/screens/sso/sso.js b/app/screens/sso/sso.js index 8612610e3..9acef9918 100644 --- a/app/screens/sso/sso.js +++ b/app/screens/sso/sso.js @@ -87,15 +87,15 @@ class SSO extends PureComponent { switch (props.ssoType) { case ViewTypes.GITLAB: this.loginUrl = `${props.serverUrl}/oauth/gitlab/mobile_login`; - this.completedUrl = '/signup/gitlab/complete'; + this.completeUrlPath = '/signup/gitlab/complete'; break; case ViewTypes.SAML: this.loginUrl = `${props.serverUrl}/login/sso/saml?action=mobile`; - this.completedUrl = '/login/sso/saml'; + this.completeUrlPath = '/login/sso/saml'; break; case ViewTypes.OFFICE365: this.loginUrl = `${props.serverUrl}/oauth/office365/mobile_login`; - this.completedUrl = '/signup/office365/complete'; + this.completeUrlPath = '/signup/office365/complete'; break; } @@ -139,7 +139,7 @@ class SSO extends PureComponent { if (parsed.host.includes('.onelogin.com')) { nextState.jsCode = oneLoginFormScalingJS; - } else if (parsed.pathname === this.completedUrl) { + } else if (parsed.pathname === this.completeUrlPath) { // To avoid `window.postMessage` conflicts in any of the SSO flows // we enable the onMessage handler only When the webView navigates to the final SSO URL. nextState.messagingEnabled = true; @@ -150,8 +150,15 @@ class SSO extends PureComponent { onLoadEnd = (event) => { const url = event.nativeEvent.url; - if (url.includes(this.completedUrl)) { - CookieManager.get(this.props.serverUrl, this.useWebkit).then((res) => { + const parsed = urlParse(url); + + let isLastRedirect = url.includes(this.completeUrlPath); + if (this.props.ssoType === ViewTypes.SAML) { + isLastRedirect = isLastRedirect && !parsed.query; + } + + if (isLastRedirect) { + CookieManager.get(parsed.origin, this.useWebkit).then((res) => { const mmtoken = res.MMAUTHTOKEN; const token = typeof mmtoken === 'object' ? mmtoken.value : mmtoken; @@ -162,6 +169,13 @@ class SSO extends PureComponent { } = this.props.actions; Client4.setToken(token); + if (this.props.serverUrl !== parsed.origin) { + const original = urlParse(this.props.serverUrl); + + // Check whether we need to set a sub-path + parsed.set('pathname', original.pathname || ''); + Client4.setUrl(parsed.href); + } ssoLogin(token).then((result) => { if (result.error) { this.onLoadEndError(result.error);