[v1] Skip LoginOptions if only one SSO method is enabled (#6097)

* Skip LoginOptions if only one SSO method is enabled

* feedback review
This commit is contained in:
Elias Nahum 2022-03-30 12:13:37 -03:00 committed by GitHub
parent 734675978d
commit 30a16d0bc1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 89 additions and 96 deletions

View file

@ -9,6 +9,7 @@ import DeepLinkTypes from './deep_linking';
import DeviceTypes from './device';
import ListTypes from './list';
import NavigationTypes from './navigation';
import Sso from './sso';
import Types from './types';
import ViewTypes from './view';
import WebsocketEvents from './websocket';
@ -21,6 +22,7 @@ export {
DateTypes,
ListTypes,
NavigationTypes,
Sso,
Types,
ViewTypes,
WebsocketEvents,

21
app/constants/sso.ts Normal file
View file

@ -0,0 +1,21 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import keyMirror from '@mm-redux/utils/key_mirror';
export const REDIRECT_URL_SCHEME = 'mmauth://';
export const REDIRECT_URL_SCHEME_DEV = 'mmauthbeta://';
const constants = keyMirror({
SAML: null,
GITLAB: null,
GOOGLE: null,
OFFICE365: null,
OPENID: null,
});
export default {
...constants,
REDIRECT_URL_SCHEME,
REDIRECT_URL_SCHEME_DEV,
};

View file

@ -20,9 +20,8 @@ import gitlab from '@assets/images/gitlab.png';
import google from '@assets/images/google.png';
import FormattedText from '@components/formatted_text';
import StatusBar from '@components/status_bar';
import {ViewTypes} from '@constants';
import {Sso} from '@constants';
import globalEventHandler from '@init/global_event_handler';
import {isMinimumServerVersion} from '@mm-redux/utils/helpers';
import {preventDoubleTap} from '@utils/tap';
import {GlobalStyles} from 'app/styles';
@ -172,7 +171,7 @@ export default class LoginOptions extends PureComponent {
return (
<Button
key='gitlab'
onPress={preventDoubleTap(() => this.goToSSO(ViewTypes.GITLAB))}
onPress={preventDoubleTap(() => this.goToSSO(Sso.GITLAB))}
containerStyle={[GlobalStyles.signupButton, additionalButtonStyle]}
>
<Image
@ -211,7 +210,7 @@ export default class LoginOptions extends PureComponent {
return (
<Button
key='google'
onPress={preventDoubleTap(() => this.goToSSO(ViewTypes.GOOGLE))}
onPress={preventDoubleTap(() => this.goToSSO(Sso.GOOGLE))}
containerStyle={[GlobalStyles.signupButton, additionalButtonStyle]}
>
<Image
@ -247,7 +246,7 @@ export default class LoginOptions extends PureComponent {
return (
<Button
key='o365'
onPress={preventDoubleTap(() => this.goToSSO(ViewTypes.OFFICE365))}
onPress={preventDoubleTap(() => this.goToSSO(Sso.OFFICE365))}
containerStyle={[GlobalStyles.signupButton, additionalButtonStyle]}
>
<FormattedText
@ -264,7 +263,7 @@ export default class LoginOptions extends PureComponent {
renderOpenIdOption = () => {
const {config, license} = this.props;
const openIdEnabled = config.EnableSignUpWithOpenId === 'true' && license.IsLicensed === 'true' && isMinimumServerVersion(config.Version, 5, 33, 0);
const openIdEnabled = config.EnableSignUpWithOpenId === 'true' && license.IsLicensed === 'true';
if (openIdEnabled) {
const additionalButtonStyle = {
@ -278,7 +277,7 @@ export default class LoginOptions extends PureComponent {
return (
<Button
key='openId'
onPress={preventDoubleTap(() => this.goToSSO(ViewTypes.OPENID))}
onPress={preventDoubleTap(() => this.goToSSO(Sso.OPENID))}
containerStyle={[GlobalStyles.signupButton, additionalButtonStyle]}
>
<FormattedText
@ -315,7 +314,7 @@ export default class LoginOptions extends PureComponent {
return (
<Button
key='saml'
onPress={preventDoubleTap(() => this.goToSSO(ViewTypes.SAML))}
onPress={preventDoubleTap(() => this.goToSSO(Sso.SAML))}
containerStyle={[GlobalStyles.signupButton, additionalStyle]}
>
<Text

View file

@ -33,7 +33,7 @@ describe('Login options', () => {
expect(configuredWrapper.find(FormattedText).find({id: 'signup.google'}).exists()).toBe(true);
});
test('should show open id button only when enabled and from version 5.33', () => {
test('should show open id button only when enabled', () => {
const basicWrapper = shallowWithIntl(<LoginOptions {...baseProps}/>);
expect(basicWrapper.find(FormattedText).find({id: 'signup.openid'}).exists()).toBe(false);
@ -42,21 +42,9 @@ describe('Login options', () => {
config: {
...baseProps.config,
EnableSignUpWithOpenId: 'true',
Version: '5.33.0',
},
};
const newVersionWrapper = shallowWithIntl(<LoginOptions {...newVersionProps}/>);
expect(newVersionWrapper.find(FormattedText).find({id: 'signup.openid'}).exists()).toBe(true);
const oldVersionProps = {
...baseProps,
config: {
...baseProps.config,
EnableSignUpWithOpenId: 'true',
Version: '5.30.0',
},
};
const oldVersionWrapper = shallowWithIntl(<LoginOptions {...oldVersionProps}/>);
expect(oldVersionWrapper.find(FormattedText).find({id: 'signup.openid'}).exists()).toBe(false);
});
});

View file

@ -32,9 +32,9 @@ import {Client4} from '@client/rest';
import AppVersion from '@components/app_version';
import ErrorText from '@components/error_text';
import FormattedText from '@components/formatted_text';
import {Sso} from '@constants';
import fetchConfig from '@init/fetch';
import globalEventHandler from '@init/global_event_handler';
import {isMinimumServerVersion} from '@mm-redux/utils/helpers';
import {t} from '@utils/i18n';
import {preventDoubleTap} from '@utils/tap';
import {changeOpacity} from '@utils/theme';
@ -233,20 +233,33 @@ export default class SelectServer extends PureComponent {
handleLoginOptions = async () => {
const {formatMessage} = this.context.intl;
const {config, license} = this.props;
const samlEnabled = config.EnableSaml === 'true' && license.IsLicensed === 'true' && license.SAML === 'true';
const isLicensed = license.IsLicensed === 'true';
const samlEnabled = isLicensed && config.EnableSaml === 'true' && license.SAML === 'true';
const googleEnabled = isLicensed && config.EnableSignUpWithGoogle === 'true';
const o365Enabled = isLicensed && config.EnableSignUpWithOffice365 === 'true' && license.Office365OAuth === 'true';
const openIdEnabled = isLicensed && config.EnableSignUpWithOpenId === 'true';
const gitlabEnabled = config.EnableSignUpWithGitLab === 'true';
const googleEnabled = config.EnableSignUpWithGoogle === 'true' && license.IsLicensed === 'true';
const o365Enabled = config.EnableSignUpWithOffice365 === 'true' && license.IsLicensed === 'true' && license.Office365OAuth === 'true';
const openIdEnabled = config.EnableSignUpWithOpenId === 'true' && license.IsLicensed === 'true' && isMinimumServerVersion(config.Version, 5, 33, 0);
let options = 0;
if (samlEnabled || gitlabEnabled || googleEnabled || o365Enabled || openIdEnabled) {
options += 1;
}
const ldapEnabled = isLicensed && config.EnableLdap === 'true' && license.LDAP === 'true';
const hasLoginForm = config.EnableSignInWithEmail === 'true' || config.EnableSignInWithUsername === 'true' || ldapEnabled;
const ssoOptions = {
[Sso.SAML]: samlEnabled,
[Sso.GITLAB]: gitlabEnabled,
[Sso.GOOGLE]: googleEnabled,
[Sso.OFFICE365]: o365Enabled,
[Sso.OPENID]: openIdEnabled,
};
const enabledSSOs = Object.keys(ssoOptions).filter((key) => ssoOptions[key]);
const numberSSOs = enabledSSOs.length;
const redirectSSO = !hasLoginForm && numberSSOs === 1;
let screen;
let title;
if (options) {
let props;
if (redirectSSO) {
screen = 'SSO';
title = formatMessage({id: 'mobile.routes.sso', defaultMessage: 'Single Sign-On'});
props = {ssoType: enabledSSOs[0]};
} else if (hasLoginForm && numberSSOs > 0) {
screen = 'LoginOptions';
title = formatMessage({id: 'mobile.routes.loginOptions', defaultMessage: 'Login Chooser'});
} else {
@ -256,6 +269,7 @@ export default class SelectServer extends PureComponent {
this.props.actions.resetPing();
await globalEventHandler.configureAnalytics();
globalEventHandler.clearCookiesAndWebData();
if (Platform.OS === 'ios') {
if (config.ExperimentalClientSideCertEnable === 'true' && config.ExperimentalClientSideCertCheck === 'primary') {
@ -265,10 +279,10 @@ export default class SelectServer extends PureComponent {
}
setTimeout(() => {
this.goToNextScreen(screen, title);
this.goToNextScreen(screen, title, props);
}, 350);
} else {
this.goToNextScreen(screen, title);
this.goToNextScreen(screen, title, props);
}
};

View file

@ -9,13 +9,11 @@ import {resetToChannel} from '@actions/navigation';
import {scheduleExpiredNotification} from '@actions/views/session';
import {ssoLogin} from '@actions/views/user';
import {Client4} from '@client/rest';
import {ViewTypes} from '@constants';
import {Sso} from '@constants';
import emmProvider from '@init/emm_provider';
import {getConfig} from '@mm-redux/selectors/entities/general';
import {getTheme} from '@mm-redux/selectors/entities/preferences';
import {DispatchFunc} from '@mm-redux/types/actions';
import {ErrorApi} from '@mm-redux/types/client4';
import {isMinimumServerVersion} from '@mm-redux/utils/helpers';
import SSOWithRedirectURL from './sso_with_redirect_url';
import SSOWithWebView from './sso_with_webview';
@ -28,8 +26,7 @@ interface SSOProps {
}
function SSO({intl, ssoType}: SSOProps) {
const [config, serverUrl, theme] = useSelector((state: GlobalState) => ([
getConfig(state),
const [serverUrl, theme] = useSelector((state: GlobalState) => ([
state.views.selectServer.serverUrl,
getTheme(state),
]), shallowEqual);
@ -42,27 +39,27 @@ function SSO({intl, ssoType}: SSOProps) {
let completeUrlPath = '';
let loginUrl = '';
switch (ssoType) {
case ViewTypes.GOOGLE: {
case Sso.GOOGLE: {
completeUrlPath = '/signup/google/complete';
loginUrl = `${serverUrl}/oauth/google/mobile_login`;
break;
}
case ViewTypes.GITLAB: {
case Sso.GITLAB: {
completeUrlPath = '/signup/gitlab/complete';
loginUrl = `${serverUrl}/oauth/gitlab/mobile_login`;
break;
}
case ViewTypes.SAML: {
case Sso.SAML: {
completeUrlPath = '/login/sso/saml';
loginUrl = `${serverUrl}/login/sso/saml?action=mobile`;
break;
}
case ViewTypes.OFFICE365: {
case Sso.OFFICE365: {
completeUrlPath = '/signup/office365/complete';
loginUrl = `${serverUrl}/oauth/office365/mobile_login`;
break;
}
case ViewTypes.OPENID: {
case Sso.OPENID: {
completeUrlPath = '/signup/openid/complete';
loginUrl = `${serverUrl}/oauth/openid/mobile_login`;
break;
@ -100,8 +97,6 @@ function SSO({intl, ssoType}: SSOProps) {
dispatch(scheduleExpiredNotification(intl));
};
const isSSOWithRedirectURLAvailable = isMinimumServerVersion(config.Version, 5, 33, 0);
const props = {
intl,
loginError,
@ -112,7 +107,7 @@ function SSO({intl, ssoType}: SSOProps) {
theme,
};
if (!isSSOWithRedirectURLAvailable || emmProvider.inAppSessionAuth === true) {
if (emmProvider.inAppSessionAuth === true) {
return (
<SSOWithWebView
{...props}

View file

@ -1,7 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import merge from 'deepmerge';
import React from 'react';
import {Linking} from 'react-native';
import configureMockStore from 'redux-mock-store';
@ -19,38 +18,10 @@ describe('SSO', () => {
},
};
test('implement with webview when version is less than 5.32 version', async () => {
const mockStore = configureMockStore();
const store = mockStore(
merge(initialState, {
entities: {
general: {
config: {
Version: '5.30.0',
},
},
},
}),
);
const basicWrapper = renderWithReduxIntl(<SSOComponent {...baseProps}/>, store);
expect(basicWrapper.queryByTestId('sso.webview')).toBeTruthy();
expect(basicWrapper.queryByTestId('sso.redirect_url')).toBeFalsy();
});
test('implement with OS browser & redirect url from version 5.33', async () => {
test('implement with OS browser & redirect url', async () => {
(Linking.openURL as jest.Mock).mockResolvedValueOnce('');
const mockStore = configureMockStore();
const store = mockStore(
merge(initialState, {
entities: {
general: {
config: {
Version: '5.33.0',
},
},
},
}),
);
const store = mockStore(initialState);
const basicWrapper = renderWithReduxIntl(<SSOComponent {...baseProps}/>, store);
expect(basicWrapper.queryByTestId('sso.webview')).toBeFalsy();
expect(basicWrapper.queryByTestId('sso.redirect_url')).toBeTruthy();

View file

@ -12,6 +12,7 @@ import urlParse from 'url-parse';
import {setDeepLinkURL} from '@actions/views/root';
import FormattedText from '@components/formatted_text';
import Loading from '@components/loading';
import {Sso} from '@constants';
import {Theme} from '@mm-redux/types/theme';
import Store from '@store/store';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
@ -39,9 +40,9 @@ function SSOWithRedirectURL({
const [error, setError] = React.useState<string>('');
const style = getStyleSheet(theme);
let customUrlScheme = 'mmauth://';
let customUrlScheme = Sso.REDIRECT_URL_SCHEME;
if (DeviceInfo.getBundleId && DeviceInfo.getBundleId().includes('rnbeta')) {
customUrlScheme = 'mmauthbeta://';
customUrlScheme = Sso.REDIRECT_URL_SCHEME_DEV;
}
const redirectUrl = customUrlScheme + 'callback';
@ -97,10 +98,10 @@ function SSOWithRedirectURL({
};
React.useEffect(() => {
Linking.addEventListener('url', onURLChange);
const listener = Linking.addEventListener('url', onURLChange);
init(false);
return () => {
Linking.removeEventListener('url', onURLChange);
listener.remove();
};
}, []);

26
package-lock.json generated
View file

@ -6,7 +6,7 @@
"packages": {
"": {
"name": "mattermost-mobile",
"version": "1.50.0",
"version": "1.50.1",
"hasInstallScript": true,
"license": "Apache 2.0",
"dependencies": {
@ -16934,9 +16934,9 @@
}
},
"node_modules/minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
},
"node_modules/mixin-deep": {
"version": "1.3.2",
@ -18781,9 +18781,9 @@
}
},
"node_modules/plist": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/plist/-/plist-3.0.4.tgz",
"integrity": "sha512-ksrr8y9+nXOxQB2osVNqrgvX/XQPOXaU4BQMKjYq8PvaY1U18mo+fKgBSwzK+luSyinOuPae956lSVcBwxlAMg==",
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/plist/-/plist-3.0.5.tgz",
"integrity": "sha512-83vX4eYdQp3vP9SxuYgEM/G/pJQqLUz/V/xzPrzruLs7fz7jxGQ1msZ/mg1nwZxUSuOp4sb+/bEIbRrbzZRxDA==",
"dependencies": {
"base64-js": "^1.5.1",
"xmlbuilder": "^9.0.7"
@ -36821,9 +36821,9 @@
}
},
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
},
"mixin-deep": {
"version": "1.3.2",
@ -38218,9 +38218,9 @@
}
},
"plist": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/plist/-/plist-3.0.4.tgz",
"integrity": "sha512-ksrr8y9+nXOxQB2osVNqrgvX/XQPOXaU4BQMKjYq8PvaY1U18mo+fKgBSwzK+luSyinOuPae956lSVcBwxlAMg==",
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/plist/-/plist-3.0.5.tgz",
"integrity": "sha512-83vX4eYdQp3vP9SxuYgEM/G/pJQqLUz/V/xzPrzruLs7fz7jxGQ1msZ/mg1nwZxUSuOp4sb+/bEIbRrbzZRxDA==",
"requires": {
"base64-js": "^1.5.1",
"xmlbuilder": "^9.0.7"

View file

@ -101,7 +101,9 @@ jest.doMock('react-native', () => {
const Linking = {
...RNLinking,
openURL: jest.fn(),
openURL: jest.fn().mockImplementation(
() => Promise.resolve(''),
),
};
return Object.setPrototypeOf({