diff --git a/app/index.js b/app/index.js
index 7b30aa8be..7a3d8effe 100644
--- a/app/index.js
+++ b/app/index.js
@@ -62,9 +62,9 @@ const OutsideStack = createStackNavigator({
defaultNavigationOptions: defaultHeader
});
-const OAuthStack = createStackNavigator({
- OAuthView: {
- getScreen: () => require('./views/OAuthView').default
+const AuthenticationWebViewStack = createStackNavigator({
+ AuthenticationWebView: {
+ getScreen: () => require('./views/AuthenticationWebView').default
}
}, {
defaultNavigationOptions: defaultHeader
@@ -72,7 +72,7 @@ const OAuthStack = createStackNavigator({
const OutsideStackModal = createStackNavigator({
OutsideStack,
- OAuthStack
+ AuthenticationWebViewStack
},
{
mode: 'modal',
diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js
index 325daa987..d3efaa4b3 100644
--- a/app/lib/rocketchat.js
+++ b/app/lib/rocketchat.js
@@ -297,7 +297,7 @@ const RocketChat = {
}
},
- async loginOAuth(params) {
+ async loginOAuthOrSso(params) {
try {
const result = await this.login(params);
reduxStore.dispatch(loginRequest({ resume: result.token }));
@@ -776,15 +776,15 @@ const RocketChat = {
},
async getLoginServices(server) {
try {
- let loginServicesFilter = [];
+ let loginServices = [];
const loginServicesResult = await fetch(`${ server }/api/v1/settings.oauth`).then(response => response.json());
if (loginServicesResult.success && loginServicesResult.services.length > 0) {
const { services } = loginServicesResult;
- loginServicesFilter = services.filter(item => item.custom !== undefined); // TODO: remove this after SAML and CAS
+ loginServices = services;
- const loginServicesReducer = loginServicesFilter.reduce((ret, item) => {
- const name = item.name ? item.name : item.service;
+ const loginServicesReducer = loginServices.reduce((ret, item) => {
+ const name = item.name || item.buttonLabelText || item.service;
const authType = this._determineAuthType(item);
if (authType !== 'not_supported') {
@@ -795,21 +795,25 @@ const RocketChat = {
}, {});
reduxStore.dispatch(setLoginServices(loginServicesReducer));
}
- return Promise.resolve(loginServicesFilter.length);
+ return Promise.resolve(loginServices.length);
} catch (error) {
console.warn(error);
return Promise.reject();
}
},
- _determineAuthType(service) {
- // TODO: remove this after other oauth providers are implemented. e.g. Drupal, github_enterprise
- const availableOAuth = ['facebook', 'github', 'gitlab', 'google', 'linkedin', 'meteor-developer', 'twitter'];
- const { name, custom } = service;
+ _determineAuthType(services) {
+ const { name, custom, service } = services;
if (custom) {
return 'oauth_custom';
}
+ if (service === 'saml') {
+ return 'saml';
+ }
+
+ // TODO: remove this after other oauth providers are implemented. e.g. Drupal, github_enterprise
+ const availableOAuth = ['facebook', 'github', 'gitlab', 'google', 'linkedin', 'meteor-developer', 'twitter'];
return availableOAuth.includes(name) ? 'oauth' : 'not_supported';
},
getUsernameSuggestion() {
diff --git a/app/views/OAuthView.js b/app/views/AuthenticationWebView.js
similarity index 58%
rename from app/views/OAuthView.js
rename to app/views/AuthenticationWebView.js
index eee46c158..2895f3cee 100644
--- a/app/views/OAuthView.js
+++ b/app/views/AuthenticationWebView.js
@@ -24,11 +24,14 @@ const styles = StyleSheet.create({
}
});
-class OAuthView extends React.PureComponent {
- static navigationOptions = ({ navigation }) => ({
- headerLeft: ,
- title: 'OAuth'
- })
+class AuthenticationWebView extends React.PureComponent {
+ static navigationOptions = ({ navigation }) => {
+ const authType = navigation.getParam('authType', 'oauth');
+ return {
+ headerLeft: ,
+ title: authType === 'saml' ? 'SSO' : 'OAuth'
+ };
+ }
static propTypes = {
navigation: PropTypes.object,
@@ -41,6 +44,7 @@ class OAuthView extends React.PureComponent {
logging: false,
loading: false
};
+ this.authType = props.navigation.getParam('authType', 'oauth');
this.redirectRegex = new RegExp(`(?=.*(${ props.server }))(?=.*(credentialToken))(?=.*(credentialSecret))`, 'g');
}
@@ -58,7 +62,7 @@ class OAuthView extends React.PureComponent {
this.setState({ logging: true });
try {
- await RocketChat.loginOAuth(params);
+ await RocketChat.loginOAuthOrSso(params);
} catch (e) {
console.warn(e);
}
@@ -66,29 +70,45 @@ class OAuthView extends React.PureComponent {
this.dismiss();
}
+ onNavigationStateChange = (webViewState) => {
+ const url = decodeURIComponent(webViewState.url);
+ if (this.authType === 'saml') {
+ const { navigation } = this.props;
+ const ssoToken = navigation.getParam('ssoToken');
+ if (url.includes('ticket') || url.includes('validate')) {
+ const payload = `{ "saml": true, "credentialToken": "${ ssoToken }" }`;
+ // We need to set a timeout when the login is done with SSO in order to make it work on our side.
+ // It is actually due to the SSO server processing the response.
+ setTimeout(() => {
+ this.login(JSON.parse(payload));
+ }, 3000);
+ }
+ }
+
+ if (this.authType === 'oauth') {
+ if (this.redirectRegex.test(url)) {
+ const parts = url.split('#');
+ const credentials = JSON.parse(parts[1]);
+ this.login({ oauth: { ...credentials } });
+ }
+ }
+ }
+
render() {
const { navigation } = this.props;
const { loading } = this.state;
- const oAuthUrl = navigation.getParam('oAuthUrl');
+ const uri = navigation.getParam('url');
return (
{
- const url = decodeURIComponent(webViewState.url);
- if (this.redirectRegex.test(url)) {
- const parts = url.split('#');
- const credentials = JSON.parse(parts[1]);
- this.login({ oauth: { ...credentials } });
- }
- }}
+ onNavigationStateChange={this.onNavigationStateChange}
onLoadStart={() => {
this.setState({ loading: true });
}}
-
onLoadEnd={() => {
this.setState({ loading: false });
}}
@@ -103,4 +123,4 @@ const mapStateToProps = state => ({
server: state.server.server
});
-export default connect(mapStateToProps)(OAuthView);
+export default connect(mapStateToProps)(AuthenticationWebView);
diff --git a/app/views/LoginSignupView.js b/app/views/LoginSignupView.js
index 8184c9a8a..36e530e5a 100644
--- a/app/views/LoginSignupView.js
+++ b/app/views/LoginSignupView.js
@@ -156,7 +156,7 @@ class LoginSignupView extends React.Component {
const scope = 'email';
const state = this.getOAuthState();
const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }&display=touch`;
- this.openOAuth(`${ endpoint }${ params }`);
+ this.openOAuth({ url: `${ endpoint }${ params }` });
}
onPressGithub = () => {
@@ -167,7 +167,7 @@ class LoginSignupView extends React.Component {
const scope = 'user:email';
const state = this.getOAuthState();
const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }`;
- this.openOAuth(`${ endpoint }${ encodeURIComponent(params) }`);
+ this.openOAuth({ url: `${ endpoint }${ encodeURIComponent(params) }` });
}
onPressGitlab = () => {
@@ -179,7 +179,7 @@ class LoginSignupView extends React.Component {
const scope = 'read_user';
const state = this.getOAuthState();
const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }&response_type=code`;
- this.openOAuth(`${ endpoint }${ params }`);
+ this.openOAuth({ url: `${ endpoint }${ params }` });
}
onPressGoogle = () => {
@@ -190,7 +190,7 @@ class LoginSignupView extends React.Component {
const scope = 'email';
const state = this.getOAuthState();
const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }&response_type=code`;
- this.openOAuth(`${ endpoint }${ params }`);
+ this.openOAuth({ url: `${ endpoint }${ params }` });
}
onPressLinkedin = () => {
@@ -201,7 +201,7 @@ class LoginSignupView extends React.Component {
const scope = 'r_emailaddress';
const state = this.getOAuthState();
const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }&response_type=code`;
- this.openOAuth(`${ endpoint }${ params }`);
+ this.openOAuth({ url: `${ endpoint }${ params }` });
}
onPressMeteor = () => {
@@ -211,14 +211,14 @@ class LoginSignupView extends React.Component {
const redirect_uri = `${ server }/_oauth/meteor-developer`;
const state = this.getOAuthState();
const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&state=${ state }&response_type=code`;
- this.openOAuth(`${ endpoint }${ params }`);
+ this.openOAuth({ url: `${ endpoint }${ params }` });
}
onPressTwitter = () => {
const { server } = this.props;
const state = this.getOAuthState();
const url = `${ server }/_oauth/twitter/?requestTokenAndRedirect=true&state=${ state }`;
- this.openOAuth(url);
+ this.openOAuth({ url });
}
onPressCustomOAuth = (loginService) => {
@@ -230,7 +230,16 @@ class LoginSignupView extends React.Component {
const state = this.getOAuthState();
const params = `?client_id=${ clientId }&redirect_uri=${ redirectUri }&response_type=code&state=${ state }&scope=${ scope }`;
const url = `${ serverURL }${ authorizePath }${ params }`;
- this.openOAuth(url);
+ this.openOAuth({ url });
+ }
+
+ onPressSaml = (loginService) => {
+ const { server } = this.props;
+ const { clientConfig } = loginService;
+ const { provider } = clientConfig;
+ const ssoToken = random(17);
+ const url = `${ server }/_saml/authorize/${ provider }/${ ssoToken }`;
+ this.openOAuth({ url, ssoToken, authType: 'saml' });
}
getOAuthState = () => {
@@ -238,9 +247,9 @@ class LoginSignupView extends React.Component {
return Base64.encodeURI(JSON.stringify({ loginStyle: 'popup', credentialToken, isCordova: true }));
}
- openOAuth = (oAuthUrl) => {
+ openOAuth = ({ url, ssoToken, authType = 'oauth' }) => {
const { navigation } = this.props;
- navigation.navigate('OAuthView', { oAuthUrl });
+ navigation.navigate('AuthenticationWebView', { url, authType, ssoToken });
}
login = () => {
@@ -317,7 +326,6 @@ class LoginSignupView extends React.Component {
let { name } = service;
name = name === 'meteor-developer' ? 'meteor' : name;
const icon = `icon_${ name }`;
- name = name.charAt(0).toUpperCase() + name.slice(1);
let onPress = () => {};
switch (service.authType) {
@@ -329,16 +337,29 @@ class LoginSignupView extends React.Component {
onPress = () => this.onPressCustomOAuth(service);
break;
}
+ case 'saml': {
+ onPress = () => this.onPressSaml(service);
+ break;
+ }
default:
break;
}
+ name = name.charAt(0).toUpperCase() + name.slice(1);
+ let buttonText;
+ if (service.service === 'saml') {
+ buttonText = {name};
+ } else {
+ buttonText = (
+ <>
+ {I18n.t('Continue_with')} {name}
+ >
+ );
+ }
return (
{service.authType === 'oauth' ? : null}
-
- {I18n.t('Continue_with')} {name}
-
+ {buttonText}
);