GitLab login (#552)

This commit is contained in:
enahum 2017-05-18 12:51:40 -04:00 committed by GitHub
parent 04d63a3e93
commit 009a07e83a
12 changed files with 101 additions and 28 deletions

View file

@ -35,7 +35,10 @@ const ViewTypes = keyMirror({
SET_CHANNEL_LOADER: null,
SET_LAST_CHANNEL_FOR_TEAM: null
SET_LAST_CHANNEL_FOR_TEAM: null,
GITLAB: null,
SAML: null
});
export default ViewTypes;

View file

@ -15,7 +15,7 @@ import {
import DeviceInfo from 'react-native-device-info';
import semver from 'semver';
import {setAppState, setDeviceToken} from 'mattermost-redux/actions/general';
import {setAppState, setDeviceToken, setServerVersion} from 'mattermost-redux/actions/general';
import {logout} from 'mattermost-redux/actions/users';
import {Client4} from 'mattermost-redux/client';
import {General} from 'mattermost-redux/constants';
@ -64,14 +64,17 @@ export default class Mattermost {
}]
);
} else {
setServerVersion('')(dispatch, getState);
loadConfigAndLicense(serverVersion)(dispatch, getState);
}
}
};
handleReset = () => {
const {dispatch, getState} = store;
Client4.serverVersion = '';
PushNotification.cancelAllLocalNotifications();
setServerVersion('')(dispatch, getState);
this.startApp('fade');
};

View file

@ -8,7 +8,9 @@ import EventEmitter from 'mattermost-redux/utils/event_emitter';
export default function(state = '', action) {
switch (action.type) {
case UserTypes.LOGOUT_SUCCESS:
EventEmitter.emit(NavigationTypes.NAVIGATION_RESET);
setTimeout(() => {
EventEmitter.emit(NavigationTypes.NAVIGATION_RESET);
});
break;
}

View file

@ -23,7 +23,7 @@ import MoreDirectMessages from 'app/screens/more_dms';
import Notification from 'app/screens/notification';
import OptionsModal from 'app/screens/options_modal';
import Root from 'app/screens/root';
import Saml from 'app/screens/saml';
import SSO from 'app/screens/sso';
import SelectServer from 'app/screens/select_server';
import SelectTeam from 'app/screens/select_team';
import Settings from 'app/screens/settings';
@ -63,10 +63,10 @@ export function registerScreens(store, Provider) {
Navigation.registerComponent('OptionsModal', () => wrapWithContextProvider(OptionsModal), store, Provider);
Navigation.registerComponent('Notification', () => Notification, store, Provider);
Navigation.registerComponent('Root', () => Root, store, Provider);
Navigation.registerComponent('SAML', () => wrapWithContextProvider(Saml), store, Provider);
Navigation.registerComponent('SelectServer', () => wrapWithContextProvider(SelectServer), store, Provider);
Navigation.registerComponent('SelectTeam', () => wrapWithContextProvider(SelectTeam), store, Provider);
Navigation.registerComponent('Settings', () => wrapWithContextProvider(Settings), store, Provider);
Navigation.registerComponent('SSO', () => wrapWithContextProvider(SSO), store, Provider);
Navigation.registerComponent('Thread', () => wrapWithContextProvider(Thread), store, Provider);
Navigation.registerComponent('UserProfile', () => wrapWithContextProvider(UserProfile), store, Provider);
}

View file

@ -8,11 +8,12 @@ import {getTheme} from 'app/selectors/preferences';
import LoginOptions from './login_options';
function mapStateToProps(state, ownProps) {
const {config, license} = state.entities.general;
const {config, license, serverVersion} = state.entities.general;
return {
...ownProps,
config,
license,
serverVersion,
theme: getTheme(state)
};
}

View file

@ -10,12 +10,15 @@ import {
Text,
View
} from 'react-native';
import Button from 'react-native-button';
import semver from 'semver';
import {ViewTypes} from 'app/constants';
import FormattedText from 'app/components/formatted_text';
import {GlobalStyles} from 'app/styles';
import {preventDoubleTap} from 'app/utils/tap';
import gitlab from 'assets/images/gitlab.png';
import logo from 'assets/images/logo.png';
class LoginOptions extends PureComponent {
@ -24,6 +27,7 @@ class LoginOptions extends PureComponent {
navigator: PropTypes.object,
config: PropTypes.object.isRequired,
license: PropTypes.object.isRequired,
serverVersion: PropTypes.string.isRequired,
theme: PropTypes.object
};
@ -43,11 +47,11 @@ class LoginOptions extends PureComponent {
});
};
goToSaml = () => {
goToSSO = (ssoType) => {
const {intl, navigator, theme} = this.props;
navigator.push({
screen: 'SAML',
title: intl.formatMessage({id: 'mobile.routes.saml', defaultMessage: 'Single SignOn'}),
screen: 'SSO',
title: intl.formatMessage({id: 'mobile.routes.sso', defaultMessage: 'Single Sign-On'}),
animated: true,
backButtonTitle: '',
navigatorStyle: {
@ -55,6 +59,9 @@ class LoginOptions extends PureComponent {
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg
},
passProps: {
ssoType
}
});
};
@ -80,13 +87,39 @@ class LoginOptions extends PureComponent {
return null;
};
renderGitlabOption = () => {
const {config, serverVersion} = this.props;
const version = serverVersion.match(/^[0-9]*.[0-9]*.[0-9]*(-[a-zA-Z0-9.-]*)?/g)[0];
if (config.EnableSignUpWithGitLab === 'true' && semver.valid(version) && semver.gte(version, 'v3.10.0')) {
return (
<Button
key='saml'
onPress={() => preventDoubleTap(this.goToSSO, this, ViewTypes.GITLAB)}
containerStyle={[GlobalStyles.signupButton, {backgroundColor: '#548'}]}
>
<Image
source={gitlab}
style={{height: 18, marginRight: 5, width: 18}}
/>
<Text
style={[GlobalStyles.signupButtonText, {color: 'white'}]}
>
{'GitLab'}
</Text>
</Button>
);
}
return null;
};
renderSamlOption = () => {
const {config, license} = this.props;
if (config.EnableSaml === 'true' && license.IsLicensed === 'true' && license.SAML === 'true') {
return (
<Button
key='saml'
onPress={() => preventDoubleTap(this.goToSaml, this)}
onPress={() => preventDoubleTap(this.goToSSO, this, ViewTypes.SAML)}
containerStyle={[GlobalStyles.signupButton, {backgroundColor: '#34a28b'}]}
>
<Text
@ -122,6 +155,7 @@ class LoginOptions extends PureComponent {
defaultMessage='Choose your login method'
/>
{this.renderEmailOption()}
{this.renderGitlabOption()}
{this.renderSamlOption()}
</View>
);

View file

@ -14,7 +14,7 @@ import SelectServer from './select_server';
function mapStateToProps(state) {
const {config: configRequest, license: licenseRequest, server: pingRequest} = state.requests.general;
const {config, license} = state.entities.general;
const {config, license, serverVersion} = state.entities.general;
const success = RequestStatus.SUCCESS;
const transition = (pingRequest.status === success && configRequest.status === success && licenseRequest.status === success);
@ -26,6 +26,7 @@ function mapStateToProps(state) {
licenseRequest,
config,
license,
serverVersion,
transition,
theme: getTheme(state)
};

View file

@ -14,8 +14,8 @@ import {
TouchableWithoutFeedback,
View
} from 'react-native';
import Button from 'react-native-button';
import semver from 'semver';
import {RequestStatus} from 'mattermost-redux/constants';
import {Client, Client4} from 'mattermost-redux/client';
@ -43,6 +43,7 @@ class SelectServer extends PureComponent {
pingRequest: PropTypes.object.isRequired,
configRequest: PropTypes.object.isRequired,
licenseRequest: PropTypes.object.isRequired,
serverVersion: PropTypes.string.isRequired,
actions: PropTypes.shape({
getPing: PropTypes.func.isRequired,
resetPing: PropTypes.func.isRequired,
@ -77,11 +78,13 @@ class SelectServer extends PureComponent {
}
handleLoginOptions = () => {
const {config, intl, license, theme} = this.props;
const {config, intl, license, serverVersion, theme} = this.props;
const version = serverVersion.match(/^[0-9]*.[0-9]*.[0-9]*(-[a-zA-Z0-9.-]*)?/g)[0];
const samlEnabled = config.EnableSaml === 'true' && license.IsLicensed === 'true' && license.SAML === 'true';
const gitlabEnabled = config.EnableSignUpWithGitLab === 'true' && semver.valid(version) && semver.gte(version, 'v3.10.0');
let options = 0;
if (samlEnabled) {
if (samlEnabled || gitlabEnabled) {
options += 1;
}

View file

@ -9,7 +9,7 @@ import {getTheme} from 'app/selectors/preferences';
import {setStoreFromLocalData} from 'mattermost-redux/actions/general';
import Saml from './saml';
import SSO from './sso';
function mapStateToProps(state, ownProps) {
return {
@ -28,4 +28,4 @@ function mapDispatchToProps(dispatch) {
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Saml);
export default connect(mapStateToProps, mapDispatchToProps)(SSO);

View file

@ -15,18 +15,37 @@ import PushNotification from 'react-native-push-notification';
import {Client4} from 'mattermost-redux/client';
class Saml extends PureComponent {
import {ViewTypes} from 'app/constants';
import Loading from 'app/components/loading';
class SSO extends PureComponent {
static propTypes = {
intl: intlShape.isRequired,
navigator: PropTypes.object,
theme: PropTypes.object,
serverUrl: PropTypes.string.isRequired,
ssoType: PropTypes.string.isRequired,
actions: PropTypes.shape({
handleSuccessfulLogin: PropTypes.func.isRequired,
setStoreFromLocalData: PropTypes.func.isRequired
}).isRequired
};
constructor(props) {
super(props);
switch (props.ssoType) {
case ViewTypes.GITLAB:
this.loginUrl = `${props.serverUrl}/oauth/gitlab/mobile_login`;
this.completedUrl = `${props.serverUrl}/signup/gitlab/complete`;
break;
case ViewTypes.SAML:
this.loginUrl = `${props.serverUrl}/login/sso/saml?action=mobile`;
this.completedUrl = '/login/sso/saml';
break;
}
}
componentDidMount() {
InteractionManager.runAfterInteractions(() => {
this.setState({renderWebview: true});
@ -69,22 +88,21 @@ class Saml extends PureComponent {
onNavigationStateChange = (navState) => {
const {url} = navState;
if (url.includes('/login/sso/saml')) {
if (url.includes(this.completedUrl)) {
CookieManager.get(this.props.serverUrl, (err, res) => {
const token = res.MMAUTHTOKEN;
if (token) {
this.setState({renderWebview: false});
const {
handleSuccessfulLogin,
setStoreFromLocalData
} = this.props.actions;
Client4.setToken(token);
handleSuccessfulLogin().
then(async (expiresAt) => {
await setStoreFromLocalData({url: this.props.serverUrl, token});
this.goToLoadTeam(expiresAt);
});
setStoreFromLocalData({url: this.props.serverUrl, token}).
then(handleSuccessfulLogin).
then(this.goToLoadTeam);
}
});
}
@ -92,22 +110,30 @@ class Saml extends PureComponent {
render() {
if (!this.state || !this.state.renderWebview) {
return null;
return (
<View style={{flex: 1}}>
<StatusBar barStyle='light-content'/>
<Loading/>
</View>
);
}
return (
<View style={{flex: 1}}>
<StatusBar barStyle='light-content'/>
<WebView
source={{uri: `${this.props.serverUrl}/login/sso/saml?action=mobile`}}
source={{uri: this.loginUrl}}
javaScriptEnabledAndroid={true}
automaticallyAdjustContentInsets={false}
scalesPageToFit={true}
startInLoadingState={true}
onNavigationStateChange={this.onNavigationStateChange}
onShouldStartLoadWithRequest={() => true}
renderLoading={() => (<Loading/>)}
/>
</View>
);
}
}
export default injectIntl(Saml);
export default injectIntl(SSO);

View file

@ -1736,7 +1736,7 @@
"mobile.routes.loginOptions": "Login Chooser",
"mobile.routes.mfa": "Multi-factor Authentication",
"mobile.routes.postsList": "Posts List",
"mobile.routes.saml": "Single SignOn",
"mobile.routes.sso": "Single Sign-On",
"mobile.routes.selectTeam": "Select Team",
"mobile.routes.settings": "Settings",
"mobile.routes.thread": "{channelName} Thread",

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B