mattermost-mobile/app/screens/sso/sso.js
Miguel Alatzar 2d81b497cf [MM-23520] Port mattermost-redux (#4088)
* Remove mattermost-redux

* Move mm-redux files into app/redux

* Add @redux path to tsconfig.json

* Fix imports

* Install missing dependencies

* Fix tsc errors

* Fix i18n_utils test

* Fix more imports

* Remove redux websocket

* Fix tests

* Rename @redux

* Apply changes from mattermost-redux PR 1103

* Remove mattermost-redux mention in template

* Add missing imports

* Rename app/redux/ to app/mm-redux/

* Remove test file

* Fix fetching Sidebar GM profiles

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2020-04-17 21:12:09 -07:00

259 lines
7.8 KiB
JavaScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {injectIntl, intlShape} from 'react-intl';
import {
Text,
View,
Platform,
} from 'react-native';
import {WebView} from 'react-native-webview';
import CookieManager from 'react-native-cookies';
import urlParse from 'url-parse';
import {Client4} from '@mm-redux/client';
import {ViewTypes} from 'app/constants';
import Loading from 'app/components/loading';
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
import StatusBar from 'app/components/status_bar';
import {resetToChannel} from 'app/actions/navigation';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import tracker from 'app/utils/time_tracker';
const HEADERS = {
'X-Mobile-App': 'mattermost',
};
const postMessageJS = "window.postMessage(document.body.innerText, '*');";
// Used to make sure that OneLogin forms scale appropriately on both platforms.
const oneLoginFormScalingJS = `
(function() {
var loginPage = document.getElementById('login-page');
var submitButton = document.getElementById('user_submit');
if (loginPage) {
loginPage.setAttribute('style', 'background-repeat: repeat-y;');
}
function resetPadding() {
var mainBody = document.getElementById('body-main');
if (mainBody) {
mainBody.setAttribute('style', 'height: auto; padding: 10px 0;');
}
if (submitButton) {
submitButton.removeEventListener('click', resetPadding);
}
}
resetPadding();
if (submitButton) {
submitButton.addEventListener('click', resetPadding);
}
})();
`;
class SSO extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
scheduleExpiredNotification: PropTypes.func.isRequired,
ssoLogin: PropTypes.func.isRequired,
}).isRequired,
intl: intlShape.isRequired,
isLandscape: PropTypes.bool.isRequired,
serverUrl: PropTypes.string.isRequired,
ssoType: PropTypes.string.isRequired,
theme: PropTypes.object,
};
useWebkit = true;
constructor(props) {
super(props);
this.state = {
error: null,
renderWebView: true,
jsCode: '',
messagingEnabled: false,
};
switch (props.ssoType) {
case ViewTypes.GITLAB:
this.loginUrl = `${props.serverUrl}/oauth/gitlab/mobile_login`;
this.completedUrl = '/signup/gitlab/complete';
break;
case ViewTypes.SAML:
this.loginUrl = `${props.serverUrl}/login/sso/saml?action=mobile`;
this.completedUrl = '/login/sso/saml';
break;
case ViewTypes.OFFICE365:
this.loginUrl = `${props.serverUrl}/oauth/office365/mobile_login`;
this.completedUrl = '/signup/office365/complete';
break;
}
if (Platform.OS === 'ios') {
this.useWebkit = parseInt(Platform.Version, 10) >= 11;
}
}
goToChannel = () => {
tracker.initialLoad = Date.now();
this.scheduleSessionExpiredNotification();
resetToChannel();
};
onMessage = (event) => {
try {
const response = JSON.parse(event.nativeEvent.data);
if (response) {
const {
id,
message,
status_code: statusCode,
} = response;
if (id && message && statusCode !== 200) {
this.setState({error: message});
}
}
} catch (e) {
// do nothing
}
};
onNavigationStateChange = (navState) => {
const {url} = navState;
const nextState = {
messagingEnabled: false,
};
const parsed = urlParse(url);
if (parsed.host.includes('.onelogin.com')) {
nextState.jsCode = oneLoginFormScalingJS;
} else if (parsed.pathname === this.completedUrl) {
// 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;
}
this.setState(nextState);
};
onLoadEnd = (event) => {
const url = event.nativeEvent.url;
if (url.includes(this.completedUrl)) {
CookieManager.get(this.props.serverUrl, this.useWebkit).then((res) => {
const token = res.MMAUTHTOKEN;
if (token) {
this.setState({renderWebView: false});
const {
ssoLogin,
} = this.props.actions;
Client4.setToken(token);
ssoLogin(token).then((result) => {
if (result.error) {
this.onLoadEndError(result.error);
return;
}
this.goToChannel();
});
} else if (this.webView && !this.state.error) {
this.webView.injectJavaScript(postMessageJS);
}
});
}
};
onLoadEndError = (e) => {
console.warn('Failed to set store from local data', e); // eslint-disable-line no-console
this.setState({error: e.message});
};
scheduleSessionExpiredNotification = () => {
const {actions, intl} = this.props;
actions.scheduleExpiredNotification(intl);
};
renderLoading = () => {
return <Loading/>;
};
webViewRef = (ref) => {
this.webView = ref;
};
render() {
const {theme, isLandscape} = this.props;
const {error, messagingEnabled, renderWebView, jsCode} = this.state;
const style = getStyleSheet(theme);
let content;
if (!renderWebView) {
content = this.renderLoading();
} else if (error) {
content = (
<View style={style.errorContainer}>
<Text style={style.errorText}>{error}</Text>
</View>
);
} else {
content = (
<WebView
ref={this.webViewRef}
source={{uri: this.loginUrl, headers: HEADERS}}
javaScriptEnabledAndroid={true}
automaticallyAdjustContentInsets={false}
startInLoadingState={true}
onNavigationStateChange={this.onNavigationStateChange}
onShouldStartLoadWithRequest={() => true}
injectedJavaScript={jsCode}
onLoadEnd={this.onLoadEnd}
onMessage={messagingEnabled ? this.onMessage : null}
useSharedProcessPool={true}
cacheEnabled={false}
/>
);
}
return (
<View style={[style.container, padding(isLandscape)]}>
<StatusBar/>
{content}
</View>
);
}
}
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
container: {
flex: 1,
},
errorContainer: {
alignItems: 'center',
flex: 1,
marginTop: 40,
},
errorText: {
color: changeOpacity(theme.centerChannelColor, 0.4),
fontSize: 16,
fontWeight: '400',
lineHeight: 23,
paddingHorizontal: 30,
},
};
});
export default injectIntl(SSO);