// 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 ConfirmModal from 'components/confirm_modal.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 {trackEvent} from 'actions/diagnostics_actions.jsx';
import {isMobile} from 'utils/user_agent.jsx';
import $ from 'jquery';
import PropTypes from 'prop-types';
import React from 'react';
import * as UserUtils from 'mattermost-redux/utils/user_utils';
import {FormattedMessage, FormattedTime, FormattedDate, FormattedHTMLMessage} from 'react-intl';
import {browserHistory, Link} from 'react-router/es6';
import icon50 from 'images/icon50x50.png';
const TOKEN_CREATING = 'creating';
const TOKEN_CREATED = 'created';
const TOKEN_NOT_CREATING = 'not_creating';
export default class SecurityTab extends React.Component {
static propTypes = {
user: PropTypes.object,
activeSection: PropTypes.string,
updateSection: PropTypes.func,
updateTab: PropTypes.func,
closeModal: PropTypes.func.isRequired,
collapseModal: PropTypes.func.isRequired,
setEnforceFocus: PropTypes.func.isRequired,
/*
* The personal access tokens for the user
*/
userAccessTokens: PropTypes.object,
/*
* Set if access tokens are enabled and this user can use them
*/
canUseAccessTokens: PropTypes.bool,
actions: PropTypes.shape({
getMe: PropTypes.func.isRequired,
/*
* Function to get personal access tokens for a user
*/
getUserAccessTokensForUser: PropTypes.func.isRequired,
/*
* Function to create a personal access token
*/
createUserAccessToken: PropTypes.func.isRequired,
/*
* Function to revoke a personal access token
*/
revokeUserAccessToken: PropTypes.func.isRequired,
/*
* Function to clear personal access tokens locally
*/
clearUserAccessTokens: PropTypes.func.isRequired
}).isRequired
}
constructor(props) {
super(props);
this.state = this.getDefaultState();
}
getDefaultState() {
return {
currentPassword: '',
newPassword: '',
confirmPassword: '',
passwordError: '',
serverError: '',
tokenError: '',
showConfirmModal: false,
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
}
);
}
if (this.props.canUseAccessTokens) {
this.props.actions.clearUserAccessTokens();
const userId = this.props.user ? this.props.user.id : '';
this.props.actions.getUserAccessTokensForUser(userId, 0, 200);
}
}
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 = (
);
mfaButtonText = (
);
} else {
mfaRemoveHelp = (
);
mfaButtonText = (
);
}
content = (
);
extraInfo = (
{mfaRemoveHelp}
);
} else {
content = (
);
extraInfo = (
);
}
const inputs = [];
inputs.push(
{content}
);
updateSectionStatus = function resetSection(e) {
this.props.updateSection('');
this.setState({serverError: null});
e.preventDefault();
}.bind(this);
return (
);
}
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 (
);
}
createPasswordSection = () => {
let updateSectionStatus;
if (this.props.activeSection === 'password') {
const inputs = [];
let submit;
if (this.props.user.auth_service === '') {
submit = this.submitPassword;
inputs.push(
);
inputs.push(
);
inputs.push(
);
} else if (this.props.user.auth_service === Constants.GITLAB_SERVICE) {
inputs.push(
);
} else if (this.props.user.auth_service === Constants.LDAP_SERVICE) {
inputs.push(
);
} else if (this.props.user.auth_service === Constants.SAML_SERVICE) {
inputs.push(
);
} else if (this.props.user.auth_service === Constants.GOOGLE_SERVICE) {
inputs.push(
);
} else if (this.props.user.auth_service === Constants.OFFICE365_SERVICE) {
inputs.push(
);
}
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 (
}
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 = (
),
time: (
)
}}
/>
);
} else if (this.props.user.auth_service === Constants.GITLAB_SERVICE) {
describe = (
);
} else if (this.props.user.auth_service === Constants.LDAP_SERVICE) {
describe = (
);
} else if (this.props.user.auth_service === Constants.SAML_SERVICE) {
describe = (
);
} else if (this.props.user.auth_service === Constants.GOOGLE_SERVICE) {
describe = (
);
} else if (this.props.user.auth_service === Constants.OFFICE365_SERVICE) {
describe = (
);
}
updateSectionStatus = function updateSection() {
this.props.updateSection('password');
}.bind(this);
return (
}
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 = (
);
}
if (global.window.mm_config.EnableSignUpWithGoogle === 'true') {
googleOption = (
);
}
if (global.window.mm_config.EnableSignUpWithOffice365 === 'true') {
office365Option = (
);
}
if (global.window.mm_config.EnableLdap === 'true') {
ldapOption = (
);
}
if (global.window.mm_config.EnableSaml === 'true') {
samlOption = (
);
}
} 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 = (
);
}
const inputs = [];
inputs.push(
{emailOption}
{gitlabOption}
{googleOption}
{office365Option}
{ldapOption}
{samlOption}
);
updateSectionStatus = function updateSection(e) {
this.props.updateSection('');
this.setState({serverError: null});
e.preventDefault();
}.bind(this);
const extraInfo = (
);
return (
);
}
updateSectionStatus = function updateSection() {
this.props.updateSection('signin');
}.bind(this);
let describe = (
);
if (this.props.user.auth_service === Constants.GITLAB_SERVICE) {
describe = (
);
} else if (this.props.user.auth_service === Constants.GOOGLE_SERVICE) {
describe = (
);
} else if (this.props.user.auth_service === Constants.OFFICE365_SERVICE) {
describe = (
);
} else if (this.props.user.auth_service === Constants.LDAP_SERVICE) {
describe = (
);
} else if (this.props.user.auth_service === Constants.SAML_SERVICE) {
describe = (
);
}
return (
);
}
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 = (
{app.homepage}
);
return (
{app.name}
{' -'} {homepage}
{app.description}
);
});
} else {
apps = (
);
}
const inputs = [];
let wrapperClass;
let helpText;
if (Array.isArray(apps)) {
wrapperClass = 'authorized-apps__wrapper';
helpText = (
);
}
inputs.push(
{apps}
);
updateSectionStatus = function updateSection(e) {
this.props.updateSection('');
this.setState({serverError: null});
e.preventDefault();
}.bind(this);
const title = (
{helpText}
);
return (
}
/>
);
}
updateSectionStatus = function updateSection() {
this.props.updateSection('apps');
}.bind(this);
return (
}
updateSection={updateSectionStatus}
/>
);
}
startCreatingToken = () => {
this.setState({tokenCreationState: TOKEN_CREATING});
}
stopCreatingToken = () => {
this.setState({tokenCreationState: TOKEN_NOT_CREATING});
}
handleCreateToken = async () => {
this.handleCancelConfirm();
const description = this.refs.newtokendescription ? this.refs.newtokendescription.value : '';
if (description === '') {
this.setState({tokenError: Utils.localizeMessage('user.settings.tokens.nameRequired', 'Please enter a description.')});
return;
}
this.setState({tokenError: ''});
const userId = this.props.user ? this.props.user.id : '';
const {data, error} = await this.props.actions.createUserAccessToken(userId, description);
if (data) {
this.setState({tokenCreationState: TOKEN_CREATED, newToken: data});
} else if (error) {
this.setState({serverError: error.message});
}
}
handleCancelConfirm = () => {
this.setState({
showConfirmModal: false,
confirmTitle: null,
confirmMessage: null,
confirmButton: null,
confirmComplete: null
});
}
confirmCreateToken = () => {
if (UserUtils.isSystemAdmin(this.props.user.roles)) {
this.setState({
showConfirmModal: true,
confirmTitle: (
),
confirmMessage: (
),
confirmButton: (
),
confirmComplete: () => {
this.handleCreateToken();
trackEvent('settings', 'system_admin_create_user_access_token');
}
});
return;
}
this.handleCreateToken();
}
saveTokenKeyPress = (e) => {
if (e.which === Constants.KeyCodes.ENTER) {
this.confirmCreateToken();
}
}
confirmRevokeToken = (tokenId) => {
const token = this.props.userAccessTokens[tokenId];
this.setState({
showConfirmModal: true,
confirmTitle: (
),
confirmMessage: (
),
confirmButton: (
),
confirmComplete: () => {
this.revokeToken(tokenId);
trackEvent('settings', 'revoke_user_access_token');
}
});
}
revokeToken = async (tokenId) => {
const {error} = await this.props.actions.revokeUserAccessToken(tokenId);
if (error) {
this.setState({serverError: error.message});
}
this.handleCancelConfirm();
}
createTokensSection = () => {
let updateSectionStatus;
let tokenListClass = '';
if (this.props.activeSection === 'tokens') {
const tokenList = [];
Object.values(this.props.userAccessTokens).forEach((token) => {
if (this.state.newToken && this.state.newToken.id === token.id) {
return;
}
tokenList.push(
{token.description}
{token.id}
);
});
let noTokenText;
if (tokenList.length === 0) {
noTokenText = (
);
}
let extraInfo;
if (isMobile()) {
extraInfo = (
);
} else {
extraInfo = (
);
}
let newTokenSection;
if (this.state.tokenCreationState === TOKEN_CREATING) {
newTokenSection = (
);
} else if (this.state.tokenCreationState === TOKEN_CREATED) {
if (tokenList.length === 0) {
tokenListClass = ' hidden';
}
newTokenSection = (
{this.state.newToken.description}
{this.state.newToken.id}
{this.state.newToken.token}
);
} else {
newTokenSection = (
);
}
const inputs = [];
inputs.push(
{tokenList}
{noTokenText}
{newTokenSection}
);
updateSectionStatus = function resetSection(e) {
this.props.updateSection('');
this.setState({newToken: null, tokenCreationState: TOKEN_NOT_CREATING, serverError: null, tokenError: ''});
e.preventDefault();
}.bind(this);
return (
}
/>
);
}
const describe = Utils.localizeMessage('user.settings.tokens.clickToEdit', "Click 'Edit' to manage your personal access tokens");
updateSectionStatus = function updateSection() {
this.props.updateSection('tokens');
}.bind(this);
return (
);
}
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();
}
let tokensSection;
if (this.props.canUseAccessTokens) {
tokensSection = this.createTokensSection();
}
return (
{passwordSection}
{mfaSection}
{oauthSection}
{tokensSection}
{signInSection}
{})} //eslint-disable-line no-empty-function
onCancel={this.handleCancelConfirm}
/>
);
}
}
SecurityTab.defaultProps = {
user: {},
activeSection: ''
};