diff options
author | Joram Wilander <jwawilander@gmail.com> | 2017-04-25 11:46:02 -0400 |
---|---|---|
committer | Christopher Speller <crspeller@gmail.com> | 2017-04-25 11:46:02 -0400 |
commit | 6c4c706313eb765eb00c639f381646be74f27b69 (patch) | |
tree | 6068feaa9668dcd74601730ac1a5abfb366402b1 /webapp/components/user_settings/user_settings_security | |
parent | cc07c005074348de87854f1c953a549e8772fa03 (diff) | |
download | chat-6c4c706313eb765eb00c639f381646be74f27b69.tar.gz chat-6c4c706313eb765eb00c639f381646be74f27b69.tar.bz2 chat-6c4c706313eb765eb00c639f381646be74f27b69.zip |
Start moving webapp to Redux (#6140)
* Start moving webapp to Redux
* Fix localforage import
* Updates per feedback
* Feedback udpates and a few fixes
* Minor updates
* Fix statuses, config not loading properly, getMe sanitizing too much
* Fix preferences
* Fix user autocomplete
* Fix sessions and audits
* Fix error handling for all redux actions
* Use new directory structure for components and containers
* Refresh immediately on logout instead of after timeout
* Add fetch polyfill
Diffstat (limited to 'webapp/components/user_settings/user_settings_security')
-rw-r--r-- | webapp/components/user_settings/user_settings_security/index.js | 24 | ||||
-rw-r--r-- | webapp/components/user_settings/user_settings_security/user_settings_security.jsx | 1036 |
2 files changed, 1060 insertions, 0 deletions
diff --git a/webapp/components/user_settings/user_settings_security/index.js b/webapp/components/user_settings/user_settings_security/index.js new file mode 100644 index 000000000..cdbabd055 --- /dev/null +++ b/webapp/components/user_settings/user_settings_security/index.js @@ -0,0 +1,24 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import {getMe} from 'mattermost-redux/actions/users'; + +import SecurityTab from './user_settings_security.jsx'; + +function mapStateToProps(state, ownProps) { + return { + ...ownProps + }; +} + +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators({ + getMe + }, dispatch) + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(SecurityTab); diff --git a/webapp/components/user_settings/user_settings_security/user_settings_security.jsx b/webapp/components/user_settings/user_settings_security/user_settings_security.jsx new file mode 100644 index 000000000..d4a372454 --- /dev/null +++ b/webapp/components/user_settings/user_settings_security/user_settings_security.jsx @@ -0,0 +1,1036 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import SettingItemMin from 'components/setting_item_min.jsx'; +import SettingItemMax from 'components/setting_item_max.jsx'; +import AccessHistoryModal from 'components/access_history_modal'; +import ActivityLogModal from 'components/activity_log_modal'; +import ToggleModalButton from 'components/toggle_modal_button.jsx'; + +import PreferenceStore from 'stores/preference_store.jsx'; + +import * as Utils from 'utils/utils.jsx'; +import Constants from 'utils/constants.jsx'; + +import {updatePassword, getAuthorizedApps, deactivateMfa, deauthorizeOAuthApp} from 'actions/user_actions.jsx'; + +import $ from 'jquery'; +import React from 'react'; +import {FormattedMessage, FormattedTime, FormattedDate} from 'react-intl'; +import {browserHistory, Link} from 'react-router/es6'; + +import icon50 from 'images/icon50x50.png'; + +export default class SecurityTab extends React.Component { + static propTypes = { + user: React.PropTypes.object, + activeSection: React.PropTypes.string, + updateSection: React.PropTypes.func, + updateTab: React.PropTypes.func, + closeModal: React.PropTypes.func.isRequired, + collapseModal: React.PropTypes.func.isRequired, + setEnforceFocus: React.PropTypes.func.isRequired, + actions: React.PropTypes.shape({ + getMe: React.PropTypes.func.isRequired + }).isRequired + } + + constructor(props) { + super(props); + + this.submitPassword = this.submitPassword.bind(this); + this.setupMfa = this.setupMfa.bind(this); + this.removeMfa = this.removeMfa.bind(this); + this.updateCurrentPassword = this.updateCurrentPassword.bind(this); + this.updateNewPassword = this.updateNewPassword.bind(this); + this.updateConfirmPassword = this.updateConfirmPassword.bind(this); + this.getDefaultState = this.getDefaultState.bind(this); + this.createPasswordSection = this.createPasswordSection.bind(this); + this.createSignInSection = this.createSignInSection.bind(this); + this.createOAuthAppsSection = this.createOAuthAppsSection.bind(this); + this.deauthorizeApp = this.deauthorizeApp.bind(this); + + this.state = this.getDefaultState(); + } + + getDefaultState() { + return { + currentPassword: '', + newPassword: '', + confirmPassword: '', + passwordError: '', + serverError: '', + authService: this.props.user.auth_service + }; + } + + componentDidMount() { + if (global.mm_config.EnableOAuthServiceProvider === 'true') { + getAuthorizedApps( + (authorizedApps) => { + this.setState({authorizedApps, serverError: null}); //eslint-disable-line react/no-did-mount-set-state + }, + (err) => { + this.setState({serverError: err.message}); //eslint-disable-line react/no-did-mount-set-state + }); + } + } + + submitPassword(e) { + e.preventDefault(); + + var user = this.props.user; + var currentPassword = this.state.currentPassword; + var newPassword = this.state.newPassword; + var confirmPassword = this.state.confirmPassword; + + if (currentPassword === '') { + this.setState({passwordError: Utils.localizeMessage('user.settings.security.currentPasswordError', 'Please enter your current password.'), serverError: ''}); + return; + } + + const passwordErr = Utils.isValidPassword(newPassword); + if (passwordErr !== '') { + this.setState({ + passwordError: passwordErr, + serverError: '' + }); + return; + } + + if (newPassword !== confirmPassword) { + var defaultState = Object.assign(this.getDefaultState(), {passwordError: Utils.localizeMessage('user.settings.security.passwordMatchError', 'The new passwords you entered do not match.'), serverError: ''}); + this.setState(defaultState); + return; + } + + updatePassword( + user.id, + currentPassword, + newPassword, + () => { + this.props.updateSection(''); + this.props.actions.getMe(); + this.setState(this.getDefaultState()); + }, + (err) => { + var state = this.getDefaultState(); + if (err.message) { + state.serverError = err.message; + } else { + state.serverError = err; + } + state.passwordError = ''; + this.setState(state); + } + ); + } + + setupMfa(e) { + e.preventDefault(); + browserHistory.push('/mfa/setup'); + } + + removeMfa() { + deactivateMfa( + () => { + if (global.window.mm_license.MFA === 'true' && + global.window.mm_config.EnableMultifactorAuthentication === 'true' && + global.window.mm_config.EnforceMultifactorAuthentication === 'true') { + window.location.href = '/mfa/setup'; + return; + } + + this.props.updateSection(''); + this.setState(this.getDefaultState()); + }, + (err) => { + const state = this.getDefaultState(); + if (err.message) { + state.serverError = err.message; + } else { + state.serverError = err; + } + this.setState(state); + } + ); + } + + updateCurrentPassword(e) { + this.setState({currentPassword: e.target.value}); + } + + updateNewPassword(e) { + this.setState({newPassword: e.target.value}); + } + + updateConfirmPassword(e) { + this.setState({confirmPassword: e.target.value}); + } + + deauthorizeApp(e) { + e.preventDefault(); + const appId = e.currentTarget.getAttribute('data-app'); + deauthorizeOAuthApp( + appId, + () => { + const authorizedApps = this.state.authorizedApps.filter((app) => { + return app.id !== appId; + }); + + this.setState({authorizedApps, serverError: null}); + }, + (err) => { + this.setState({serverError: err.message}); + }); + } + + createMfaSection() { + let updateSectionStatus; + let submit; + + if (this.props.activeSection === 'mfa') { + let content; + let extraInfo; + if (this.props.user.mfa_active) { + let mfaRemoveHelp; + let mfaButtonText; + + if (global.window.mm_config.EnforceMultifactorAuthentication === 'true') { + mfaRemoveHelp = ( + <FormattedMessage + id='user.settings.mfa.requiredHelp' + defaultMessage='Multi-factor authentication is required on this server. Resetting is only recommended when you need to switch code generation to a new mobile device. You will be required to set it up again immediately.' + /> + ); + + mfaButtonText = ( + <FormattedMessage + id='user.settings.mfa.reset' + defaultMessage='Reset MFA on your account' + /> + ); + } else { + mfaRemoveHelp = ( + <FormattedMessage + id='user.settings.mfa.removeHelp' + defaultMessage='Removing multi-factor authentication means you will no longer require a phone-based passcode to sign-in to your account.' + /> + ); + + mfaButtonText = ( + <FormattedMessage + id='user.settings.mfa.remove' + defaultMessage='Remove MFA from your account' + /> + ); + } + + content = ( + <div key='mfaQrCode'> + <a + className='btn btn-primary' + href='#' + onClick={this.removeMfa} + > + {mfaButtonText} + </a> + <br/> + </div> + ); + + extraInfo = ( + <span> + {mfaRemoveHelp} + </span> + ); + } else { + content = ( + <div key='mfaQrCode'> + <a + className='btn btn-primary' + href='#' + onClick={this.setupMfa} + > + <FormattedMessage + id='user.settings.mfa.add' + defaultMessage='Add MFA to your account' + /> + </a> + <br/> + </div> + ); + + extraInfo = ( + <span> + <FormattedMessage + id='user.settings.mfa.addHelp' + defaultMessage='Adding multi-factor authentication will make your account more secure by requiring a code from your mobile phone each time you sign in.' + /> + </span> + ); + } + + const inputs = []; + inputs.push( + <div + key='mfaSetting' + className='padding-top' + > + {content} + </div> + ); + + updateSectionStatus = function resetSection(e) { + this.props.updateSection(''); + this.setState({serverError: null}); + e.preventDefault(); + }.bind(this); + + return ( + <SettingItemMax + title={Utils.localizeMessage('user.settings.mfa.title', 'Multi-factor Authentication')} + inputs={inputs} + extraInfo={extraInfo} + submit={submit} + server_error={this.state.serverError} + updateSection={updateSectionStatus} + width='medium' + /> + ); + } + + let describe; + if (this.props.user.mfa_active) { + describe = Utils.localizeMessage('user.settings.security.active', 'Active'); + } else { + describe = Utils.localizeMessage('user.settings.security.inactive', 'Inactive'); + } + + updateSectionStatus = function updateSection() { + this.props.updateSection('mfa'); + }.bind(this); + + return ( + <SettingItemMin + title={Utils.localizeMessage('user.settings.mfa.title', 'Multi-factor Authentication')} + describe={describe} + updateSection={updateSectionStatus} + /> + ); + } + + createPasswordSection() { + let updateSectionStatus; + + if (this.props.activeSection === 'password') { + const inputs = []; + let submit; + + if (this.props.user.auth_service === '') { + submit = this.submitPassword; + + inputs.push( + <div + key='currentPasswordUpdateForm' + className='form-group' + > + <label className='col-sm-5 control-label'> + <FormattedMessage + id='user.settings.security.currentPassword' + defaultMessage='Current Password' + /> + </label> + <div className='col-sm-7'> + <input + id='currentPassword' + className='form-control' + type='password' + onChange={this.updateCurrentPassword} + value={this.state.currentPassword} + /> + </div> + </div> + ); + inputs.push( + <div + key='newPasswordUpdateForm' + className='form-group' + > + <label className='col-sm-5 control-label'> + <FormattedMessage + id='user.settings.security.newPassword' + defaultMessage='New Password' + /> + </label> + <div className='col-sm-7'> + <input + id='newPassword' + className='form-control' + type='password' + onChange={this.updateNewPassword} + value={this.state.newPassword} + /> + </div> + </div> + ); + inputs.push( + <div + key='retypeNewPasswordUpdateForm' + className='form-group' + > + <label className='col-sm-5 control-label'> + <FormattedMessage + id='user.settings.security.retypePassword' + defaultMessage='Retype New Password' + /> + </label> + <div className='col-sm-7'> + <input + id='confirmPassword' + className='form-control' + type='password' + onChange={this.updateConfirmPassword} + value={this.state.confirmPassword} + /> + </div> + </div> + ); + } else if (this.props.user.auth_service === Constants.GITLAB_SERVICE) { + inputs.push( + <div + key='oauthEmailInfo' + className='form-group' + > + <div className='setting-list__hint col-sm-12'> + <FormattedMessage + id='user.settings.security.passwordGitlabCantUpdate' + defaultMessage='Login occurs through GitLab. Password cannot be updated.' + /> + </div> + </div> + ); + } else if (this.props.user.auth_service === Constants.LDAP_SERVICE) { + inputs.push( + <div + key='oauthEmailInfo' + className='form-group' + > + <div className='setting-list__hint col-sm-12'> + <FormattedMessage + id='user.settings.security.passwordLdapCantUpdate' + defaultMessage='Login occurs through AD/LDAP. Password cannot be updated.' + /> + </div> + </div> + ); + } else if (this.props.user.auth_service === Constants.SAML_SERVICE) { + inputs.push( + <div + key='oauthEmailInfo' + className='form-group' + > + <div className='setting-list__hint col-sm-12'> + <FormattedMessage + id='user.settings.security.passwordSamlCantUpdate' + defaultMessage='This field is handled through your login provider. If you want to change it, you need to do so through your login provider.' + /> + </div> + </div> + ); + } else if (this.props.user.auth_service === Constants.GOOGLE_SERVICE) { + inputs.push( + <div + key='oauthEmailInfo' + className='form-group' + > + <div className='setting-list__hint col-sm-12'> + <FormattedMessage + id='user.settings.security.passwordGoogleCantUpdate' + defaultMessage='Login occurs through Google Apps. Password cannot be updated.' + /> + </div> + </div> + ); + } else if (this.props.user.auth_service === Constants.OFFICE365_SERVICE) { + inputs.push( + <div + key='oauthEmailInfo' + className='form-group' + > + <div className='setting-list__hint col-sm-12'> + <FormattedMessage + id='user.settings.security.passwordOffice365CantUpdate' + defaultMessage='Login occurs through Office 365. Password cannot be updated.' + /> + </div> + </div> + ); + } + + updateSectionStatus = function resetSection(e) { + this.props.updateSection(''); + this.setState({currentPassword: '', newPassword: '', confirmPassword: '', serverError: null, passwordError: null}); + e.preventDefault(); + $('.settings-modal .modal-body').scrollTop(0).perfectScrollbar('update'); + }.bind(this); + + return ( + <SettingItemMax + title={ + <FormattedMessage + id='user.settings.security.password' + defaultMessage='Password' + /> + } + inputs={inputs} + submit={submit} + server_error={this.state.serverError} + client_error={this.state.passwordError} + updateSection={updateSectionStatus} + /> + ); + } + + let describe; + + if (this.props.user.auth_service === '') { + const d = new Date(this.props.user.last_password_update); + const hours12 = !PreferenceStore.getBool(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, Constants.Preferences.USE_MILITARY_TIME, false); + + describe = ( + <FormattedMessage + id='user.settings.security.lastUpdated' + defaultMessage='Last updated {date} at {time}' + values={{ + date: ( + <FormattedDate + value={d} + day='2-digit' + month='short' + year='numeric' + /> + ), + time: ( + <FormattedTime + value={d} + hour12={hours12} + hour='2-digit' + minute='2-digit' + /> + ) + }} + /> + ); + } else if (this.props.user.auth_service === Constants.GITLAB_SERVICE) { + describe = ( + <FormattedMessage + id='user.settings.security.loginGitlab' + defaultMessage='Login done through GitLab' + /> + ); + } else if (this.props.user.auth_service === Constants.LDAP_SERVICE) { + describe = ( + <FormattedMessage + id='user.settings.security.loginLdap' + defaultMessage='Login done through AD/LDAP' + /> + ); + } else if (this.props.user.auth_service === Constants.SAML_SERVICE) { + describe = ( + <FormattedMessage + id='user.settings.security.loginSaml' + defaultMessage='Login done through SAML' + /> + ); + } else if (this.props.user.auth_service === Constants.GOOGLE_SERVICE) { + describe = ( + <FormattedMessage + id='user.settings.security.loginGoogle' + defaultMessage='Login done through Google Apps' + /> + ); + } else if (this.props.user.auth_service === Constants.OFFICE365_SERVICE) { + describe = ( + <FormattedMessage + id='user.settings.security.loginOffice365' + defaultMessage='Login done through Office 365' + /> + ); + } + + updateSectionStatus = function updateSection() { + this.props.updateSection('password'); + }.bind(this); + + return ( + <SettingItemMin + title={ + <FormattedMessage + id='user.settings.security.password' + defaultMessage='Password' + /> + } + describe={describe} + updateSection={updateSectionStatus} + /> + ); + } + + createSignInSection() { + let updateSectionStatus; + const user = this.props.user; + + if (this.props.activeSection === 'signin') { + let emailOption; + let gitlabOption; + let googleOption; + let office365Option; + let ldapOption; + let samlOption; + + if (user.auth_service === '') { + if (global.window.mm_config.EnableSignUpWithGitLab === 'true') { + gitlabOption = ( + <div className='padding-bottom x2'> + <Link + className='btn btn-primary' + to={'/claim/email_to_oauth?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service + '&new_type=' + Constants.GITLAB_SERVICE} + > + <FormattedMessage + id='user.settings.security.switchGitlab' + defaultMessage='Switch to using GitLab SSO' + /> + </Link> + <br/> + </div> + ); + } + + if (global.window.mm_config.EnableSignUpWithGoogle === 'true') { + googleOption = ( + <div className='padding-bottom x2'> + <Link + className='btn btn-primary' + to={'/claim/email_to_oauth?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service + '&new_type=' + Constants.GOOGLE_SERVICE} + > + <FormattedMessage + id='user.settings.security.switchGoogle' + defaultMessage='Switch to using Google SSO' + /> + </Link> + <br/> + </div> + ); + } + + if (global.window.mm_config.EnableSignUpWithOffice365 === 'true') { + office365Option = ( + <div className='padding-bottom x2'> + <Link + className='btn btn-primary' + to={'/claim/email_to_oauth?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service + '&new_type=' + Constants.OFFICE365_SERVICE} + > + <FormattedMessage + id='user.settings.security.switchOffice365' + defaultMessage='Switch to using Office 365 SSO' + /> + </Link> + <br/> + </div> + ); + } + + if (global.window.mm_config.EnableLdap === 'true') { + ldapOption = ( + <div className='padding-bottom x2'> + <Link + className='btn btn-primary' + to={'/claim/email_to_ldap?email=' + encodeURIComponent(user.email)} + > + <FormattedMessage + id='user.settings.security.switchLdap' + defaultMessage='Switch to using AD/LDAP' + /> + </Link> + <br/> + </div> + ); + } + + if (global.window.mm_config.EnableSaml === 'true') { + samlOption = ( + <div className='padding-bottom x2'> + <Link + className='btn btn-primary' + to={'/claim/email_to_oauth?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service + '&new_type=' + Constants.SAML_SERVICE} + > + <FormattedMessage + id='user.settings.security.switchSaml' + defaultMessage='Switch to using SAML SSO' + /> + </Link> + <br/> + </div> + ); + } + } else if (global.window.mm_config.EnableSignUpWithEmail === 'true') { + let link; + if (user.auth_service === Constants.LDAP_SERVICE) { + link = '/claim/ldap_to_email?email=' + encodeURIComponent(user.email); + } else { + link = '/claim/oauth_to_email?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service; + } + + emailOption = ( + <div className='padding-bottom x2'> + <Link + className='btn btn-primary' + to={link} + > + <FormattedMessage + id='user.settings.security.switchEmail' + defaultMessage='Switch to using email and password' + /> + </Link> + <br/> + </div> + ); + } + + const inputs = []; + inputs.push( + <div key='userSignInOption'> + {emailOption} + {gitlabOption} + {googleOption} + {office365Option} + {ldapOption} + {samlOption} + </div> + ); + + updateSectionStatus = function updateSection(e) { + this.props.updateSection(''); + this.setState({serverError: null}); + e.preventDefault(); + }.bind(this); + + const extraInfo = ( + <span> + <FormattedMessage + id='user.settings.security.oneSignin' + defaultMessage='You may only have one sign-in method at a time. Switching sign-in method will send an email notifying you if the change was successful.' + /> + </span> + ); + + return ( + <SettingItemMax + title={Utils.localizeMessage('user.settings.security.method', 'Sign-in Method')} + extraInfo={extraInfo} + inputs={inputs} + server_error={this.state.serverError} + updateSection={updateSectionStatus} + /> + ); + } + + updateSectionStatus = function updateSection() { + this.props.updateSection('signin'); + }.bind(this); + + let describe = ( + <FormattedMessage + id='user.settings.security.emailPwd' + defaultMessage='Email and Password' + /> + ); + if (this.props.user.auth_service === Constants.GITLAB_SERVICE) { + describe = ( + <FormattedMessage + id='user.settings.security.gitlab' + defaultMessage='GitLab' + /> + ); + } else if (this.props.user.auth_service === Constants.GOOGLE_SERVICE) { + describe = ( + <FormattedMessage + id='user.settings.security.google' + defaultMessage='Google' + /> + ); + } else if (this.props.user.auth_service === Constants.OFFICE365_SERVICE) { + describe = ( + <FormattedMessage + id='user.settings.security.office365' + defaultMessage='Office 365' + /> + ); + } else if (this.props.user.auth_service === Constants.LDAP_SERVICE) { + describe = ( + <FormattedMessage + id='user.settings.security.ldap' + defaultMessage='AD/LDAP' + /> + ); + } else if (this.props.user.auth_service === Constants.SAML_SERVICE) { + describe = ( + <FormattedMessage + id='user.settings.security.saml' + defaultMessage='SAML' + /> + ); + } + + return ( + <SettingItemMin + title={Utils.localizeMessage('user.settings.security.method', 'Sign-in Method')} + describe={describe} + updateSection={updateSectionStatus} + /> + ); + } + + createOAuthAppsSection() { + let updateSectionStatus; + + if (this.props.activeSection === 'apps') { + let apps; + if (this.state.authorizedApps && this.state.authorizedApps.length > 0) { + apps = this.state.authorizedApps.map((app) => { + const homepage = ( + <a + href={app.homepage} + target='_blank' + rel='noopener noreferrer' + > + {app.homepage} + </a> + ); + + return ( + <div + key={app.id} + className='padding-bottom x2 authorized-app' + > + <div className='col-sm-10'> + <div className='authorized-app__name'> + {app.name} + <span className='authorized-app__url'> + {' -'} {homepage} + </span> + </div> + <div className='authorized-app__description'>{app.description}</div> + <div className='authorized-app__deauthorize'> + <a + href='#' + data-app={app.id} + onClick={this.deauthorizeApp} + > + <FormattedMessage + id='user.settings.security.deauthorize' + defaultMessage='Deauthorize' + /> + </a> + </div> + </div> + <div className='col-sm-2 pull-right'> + <img + alt={app.name} + src={app.icon_url || icon50} + /> + </div> + <br/> + </div> + ); + }); + } else { + apps = ( + <div className='padding-bottom x2 authorized-app'> + <div className='setting-list__hint'> + <FormattedMessage + id='user.settings.security.noApps' + defaultMessage='No OAuth 2.0 Applications are authorized.' + /> + </div> + </div> + ); + } + + const inputs = []; + let wrapperClass; + let helpText; + if (Array.isArray(apps)) { + wrapperClass = 'authorized-apps__wrapper'; + + helpText = ( + <div className='authorized-apps__help'> + <FormattedMessage + id='user.settings.security.oauthAppsHelp' + defaultMessage='Applications act on your behalf to access your data based on the permissions you grant them.' + /> + </div> + ); + } + + inputs.push( + <div + className={wrapperClass} + key='authorizedApps' + > + {apps} + </div> + ); + + updateSectionStatus = function updateSection(e) { + this.props.updateSection(''); + this.setState({serverError: null}); + e.preventDefault(); + }.bind(this); + + const title = ( + <div> + <FormattedMessage + id='user.settings.security.oauthApps' + defaultMessage='OAuth 2.0 Applications' + /> + {helpText} + </div> + ); + + return ( + <SettingItemMax + title={title} + inputs={inputs} + server_error={this.state.serverError} + updateSection={updateSectionStatus} + width='full' + /> + ); + } + + updateSectionStatus = function updateSection() { + this.props.updateSection('apps'); + }.bind(this); + + return ( + <SettingItemMin + title={Utils.localizeMessage('user.settings.security.oauthApps', 'OAuth 2.0 Applications')} + describe={ + <FormattedMessage + id='user.settings.security.oauthAppsDescription' + defaultMessage="Click 'Edit' to manage your OAuth 2.0 Applications" + /> + } + updateSection={updateSectionStatus} + /> + ); + } + + render() { + const user = this.props.user; + const config = window.mm_config; + + const passwordSection = this.createPasswordSection(); + + let numMethods = 0; + numMethods = config.EnableSignUpWithGitLab === 'true' ? numMethods + 1 : numMethods; + numMethods = config.EnableSignUpWithGoogle === 'true' ? numMethods + 1 : numMethods; + numMethods = config.EnableLdap === 'true' ? numMethods + 1 : numMethods; + numMethods = config.EnableSaml === 'true' ? numMethods + 1 : numMethods; + + // If there are other sign-in methods and either email is enabled or the user's account is email, then allow switching + let signInSection; + if ((config.EnableSignUpWithEmail === 'true' || user.auth_service === '') && numMethods > 0) { + signInSection = this.createSignInSection(); + } + + let mfaSection; + if (config.EnableMultifactorAuthentication === 'true' && + global.window.mm_license.IsLicensed === 'true' && + (user.auth_service === '' || user.auth_service === Constants.LDAP_SERVICE)) { + mfaSection = this.createMfaSection(); + } + + let oauthSection; + if (config.EnableOAuthServiceProvider === 'true') { + oauthSection = this.createOAuthAppsSection(); + } + + return ( + <div> + <div className='modal-header'> + <button + type='button' + className='close' + data-dismiss='modal' + aria-label={Utils.localizeMessage('user.settings.security.close', 'Close')} + onClick={this.props.closeModal} + > + <span aria-hidden='true'>{'×'}</span> + </button> + <h4 + className='modal-title' + ref='title' + > + <div className='modal-back'> + <i + className='fa fa-angle-left' + onClick={this.props.collapseModal} + /> + </div> + <FormattedMessage + id='user.settings.security.title' + defaultMessage='Security Settings' + /> + </h4> + </div> + <div className='user-settings'> + <h3 className='tab-header'> + <FormattedMessage + id='user.settings.security.title' + defaultMessage='Security Settings' + /> + </h3> + <div className='divider-dark first'/> + {passwordSection} + <div className='divider-light'/> + {mfaSection} + <div className='divider-light'/> + {oauthSection} + <div className='divider-light'/> + {signInSection} + <div className='divider-dark'/> + <br/> + <ToggleModalButton + className='security-links theme' + dialogType={AccessHistoryModal} + > + <i className='fa fa-clock-o'/> + <FormattedMessage + id='user.settings.security.viewHistory' + defaultMessage='View Access History' + /> + </ToggleModalButton> + <b/> + <ToggleModalButton + className='security-links theme' + dialogType={ActivityLogModal} + > + <i className='fa fa-clock-o'/> + <FormattedMessage + id='user.settings.security.logoutActiveSessions' + defaultMessage='View and Logout of Active Sessions' + /> + </ToggleModalButton> + </div> + </div> + ); + } +} + +SecurityTab.defaultProps = { + user: {}, + activeSection: '' +}; |