summaryrefslogtreecommitdiffstats
path: root/webapp/components/admin_console
diff options
context:
space:
mode:
Diffstat (limited to 'webapp/components/admin_console')
-rw-r--r--webapp/components/admin_console/custom_integrations_settings.jsx21
-rw-r--r--webapp/components/admin_console/manage_roles_modal/index.js25
-rw-r--r--webapp/components/admin_console/manage_roles_modal/manage_roles_modal.jsx349
-rw-r--r--webapp/components/admin_console/manage_teams_modal/manage_teams_modal.jsx21
-rw-r--r--webapp/components/admin_console/manage_teams_modal/remove_from_team_button.jsx2
-rw-r--r--webapp/components/admin_console/manage_tokens_modal/index.js27
-rw-r--r--webapp/components/admin_console/manage_tokens_modal/manage_tokens_modal.jsx181
-rw-r--r--webapp/components/admin_console/revoke_token_button/index.js24
-rw-r--r--webapp/components/admin_console/revoke_token_button/revoke_token_button.jsx56
-rw-r--r--webapp/components/admin_console/system_users/index.js5
-rw-r--r--webapp/components/admin_console/system_users/system_users.jsx25
-rw-r--r--webapp/components/admin_console/system_users/system_users_dropdown.jsx175
-rw-r--r--webapp/components/admin_console/system_users/system_users_list.jsx103
13 files changed, 873 insertions, 141 deletions
diff --git a/webapp/components/admin_console/custom_integrations_settings.jsx b/webapp/components/admin_console/custom_integrations_settings.jsx
index 18fdd22fd..3b5c51171 100644
--- a/webapp/components/admin_console/custom_integrations_settings.jsx
+++ b/webapp/components/admin_console/custom_integrations_settings.jsx
@@ -25,6 +25,7 @@ export default class WebhookSettings extends AdminSettings {
config.ServiceSettings.EnablePostUsernameOverride = this.state.enablePostUsernameOverride;
config.ServiceSettings.EnablePostIconOverride = this.state.enablePostIconOverride;
config.ServiceSettings.EnableOAuthServiceProvider = this.state.enableOAuthServiceProvider;
+ config.ServiceSettings.EnableUserAccessTokens = this.state.enableUserAccessTokens;
return config;
}
@@ -37,7 +38,8 @@ export default class WebhookSettings extends AdminSettings {
enableOnlyAdminIntegrations: config.ServiceSettings.EnableOnlyAdminIntegrations,
enablePostUsernameOverride: config.ServiceSettings.EnablePostUsernameOverride,
enablePostIconOverride: config.ServiceSettings.EnablePostIconOverride,
- enableOAuthServiceProvider: config.ServiceSettings.EnableOAuthServiceProvider
+ enableOAuthServiceProvider: config.ServiceSettings.EnableOAuthServiceProvider,
+ enableUserAccessTokens: config.ServiceSettings.EnableUserAccessTokens
};
}
@@ -172,6 +174,23 @@ export default class WebhookSettings extends AdminSettings {
value={this.state.enablePostIconOverride}
onChange={this.handleChange}
/>
+ <BooleanSetting
+ id='enableUserAccessTokens'
+ label={
+ <FormattedMessage
+ id='admin.service.userAccessTokensTitle'
+ defaultMessage='Enable User Access Tokens: '
+ />
+ }
+ helpText={
+ <FormattedHTMLMessage
+ id='admin.service.userAccessTokensDescription'
+ defaultMessage='When true, users can create <a href="https://about.mattermost.com/default-user-access-tokens" target="_blank">user access tokens</a> for integrations in <strong>Account Settings > Security</strong>. They can be used to authenticate against the API and give full access to the account.<br/><br/>To manage who can create user access tokens, go to the <strong>System Console > Users</strong> page.'
+ />
+ }
+ value={this.state.enableUserAccessTokens}
+ onChange={this.handleChange}
+ />
</SettingsGroup>
);
}
diff --git a/webapp/components/admin_console/manage_roles_modal/index.js b/webapp/components/admin_console/manage_roles_modal/index.js
new file mode 100644
index 000000000..1ca243621
--- /dev/null
+++ b/webapp/components/admin_console/manage_roles_modal/index.js
@@ -0,0 +1,25 @@
+// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {connect} from 'react-redux';
+import {bindActionCreators} from 'redux';
+import {updateUserRoles} from 'mattermost-redux/actions/users';
+
+import ManageRolesModal from './manage_roles_modal.jsx';
+
+function mapStateToProps(state, ownProps) {
+ return {
+ ...ownProps,
+ userAccessTokensEnabled: state.entities.admin.config.ServiceSettings.EnableUserAccessTokens
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ actions: bindActionCreators({
+ updateUserRoles
+ }, dispatch)
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(ManageRolesModal);
diff --git a/webapp/components/admin_console/manage_roles_modal/manage_roles_modal.jsx b/webapp/components/admin_console/manage_roles_modal/manage_roles_modal.jsx
new file mode 100644
index 000000000..2358f0241
--- /dev/null
+++ b/webapp/components/admin_console/manage_roles_modal/manage_roles_modal.jsx
@@ -0,0 +1,349 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import * as UserUtils from 'mattermost-redux/utils/user_utils';
+import {Client4} from 'mattermost-redux/client';
+import {General} from 'mattermost-redux/constants';
+
+import {trackEvent} from 'actions/diagnostics_actions.jsx';
+
+import React from 'react';
+import {Modal} from 'react-bootstrap';
+import PropTypes from 'prop-types';
+import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
+
+function getStateFromProps(props) {
+ const roles = props.user && props.user.roles ? props.user.roles : '';
+
+ return {
+ error: null,
+ hasPostAllRole: UserUtils.hasPostAllRole(roles),
+ hasPostAllPublicRole: UserUtils.hasPostAllPublicRole(roles),
+ hasUserAccessTokenRole: UserUtils.hasUserAccessTokenRole(roles),
+ isSystemAdmin: UserUtils.isSystemAdmin(roles)
+ };
+}
+
+export default class ManageRolesModal extends React.PureComponent {
+ static propTypes = {
+
+ /**
+ * Set to render the modal
+ */
+ show: PropTypes.bool.isRequired,
+
+ /**
+ * The user the roles are being managed for
+ */
+ user: PropTypes.object,
+
+ /**
+ * Set if user access tokens are enabled
+ */
+ userAccessTokensEnabled: PropTypes.bool.isRequired,
+
+ /**
+ * Function called when modal is dismissed
+ */
+ onModalDismissed: PropTypes.func.isRequired,
+
+ actions: PropTypes.shape({
+
+ /**
+ * Function to update a user's roles
+ */
+ updateUserRoles: PropTypes.func.isRequired
+ }).isRequired
+ };
+
+ constructor(props) {
+ super(props);
+ this.state = getStateFromProps(props);
+ }
+
+ componentWillReceiveProps(nextProps) {
+ const user = this.props.user || {};
+ const nextUser = nextProps.user || {};
+ if (user.id !== nextUser.id) {
+ this.setState(getStateFromProps(nextProps));
+ }
+ }
+
+ handleError = (error) => {
+ this.setState({
+ error
+ });
+ }
+
+ handleSystemAdminChange = (e) => {
+ if (e.target.name === 'systemadmin') {
+ this.setState({isSystemAdmin: true});
+ } else if (e.target.name === 'systemmember') {
+ this.setState({isSystemAdmin: false});
+ }
+ };
+
+ handleUserAccessTokenChange = (e) => {
+ this.setState({
+ hasUserAccessTokenRole: e.target.checked
+ });
+ };
+
+ handlePostAllChange = (e) => {
+ this.setState({
+ hasPostAllRole: e.target.checked
+ });
+ };
+
+ handlePostAllPublicChange = (e) => {
+ this.setState({
+ hasPostAllPublicRole: e.target.checked
+ });
+ };
+
+ trackRoleChanges = (roles, oldRoles) => {
+ if (UserUtils.hasUserAccessTokenRole(roles) && !UserUtils.hasUserAccessTokenRole(oldRoles)) {
+ trackEvent('actions', 'add_roles', {role: General.SYSTEM_USER_ACCESS_TOKEN_ROLE});
+ } else if (!UserUtils.hasUserAccessTokenRole(roles) && UserUtils.hasUserAccessTokenRole(oldRoles)) {
+ trackEvent('actions', 'remove_roles', {role: General.SYSTEM_USER_ACCESS_TOKEN_ROLE});
+ }
+
+ if (UserUtils.hasPostAllRole(roles) && !UserUtils.hasPostAllRole(oldRoles)) {
+ trackEvent('actions', 'add_roles', {role: General.SYSTEM_POST_ALL_ROLE});
+ } else if (!UserUtils.hasPostAllRole(roles) && UserUtils.hasPostAllRole(oldRoles)) {
+ trackEvent('actions', 'remove_roles', {role: General.SYSTEM_POST_ALL_ROLE});
+ }
+
+ if (UserUtils.hasPostAllPublicRole(roles) && !UserUtils.hasPostAllPublicRole(oldRoles)) {
+ trackEvent('actions', 'add_roles', {role: General.SYSTEM_POST_ALL_PUBLIC_ROLE});
+ } else if (!UserUtils.hasPostAllPublicRole(roles) && UserUtils.hasPostAllPublicRole(oldRoles)) {
+ trackEvent('actions', 'remove_roles', {role: General.SYSTEM_POST_ALL_PUBLIC_ROLE});
+ }
+ }
+
+ handleSave = async () => {
+ this.setState({error: null});
+
+ let roles = General.SYSTEM_USER_ROLE;
+
+ if (this.state.isSystemAdmin) {
+ roles += ' ' + General.SYSTEM_ADMIN_ROLE;
+ } else if (this.state.hasUserAccessTokenRole) {
+ roles += ' ' + General.SYSTEM_USER_ACCESS_TOKEN_ROLE;
+ if (this.state.hasPostAllRole) {
+ roles += ' ' + General.SYSTEM_POST_ALL_ROLE;
+ } else if (this.state.hasPostAllPublicRole) {
+ roles += ' ' + General.SYSTEM_POST_ALL_PUBLIC_ROLE;
+ }
+ }
+
+ const data = await this.props.actions.updateUserRoles(this.props.user.id, roles);
+
+ this.trackRoleChanges(roles, this.props.user.roles);
+
+ if (data) {
+ this.props.onModalDismissed();
+ } else {
+ this.handleError(
+ <FormattedMessage
+ id='admin.manage_roles.saveError'
+ defaultMessage='Unable to save roles.'
+ />
+ );
+ }
+ }
+
+ renderContents = () => {
+ const {user} = this.props;
+
+ if (user == null) {
+ return <div/>;
+ }
+
+ let name = UserUtils.getFullName(user);
+ if (name) {
+ name += ` (@${user.username})`;
+ } else {
+ name = `@${user.username}`;
+ }
+
+ let additionalRoles;
+ if (this.state.hasUserAccessTokenRole || this.state.isSystemAdmin) {
+ additionalRoles = (
+ <div>
+ <p>
+ <FormattedHTMLMessage
+ id='admin.manage_roles.additionalRoles'
+ defaultMessage='Select additional permissions for the account. <a href="https://about.mattermost.com/default-permissions" target="_blank">Read more about roles and permissions</a>.'
+ />
+ </p>
+ <div className='checkbox'>
+ <label>
+ <input
+ type='checkbox'
+ ref='postall'
+ checked={this.state.hasPostAllRole || this.state.isSystemAdmin}
+ disabled={this.state.isSystemAdmin}
+ onChange={this.handlePostAllChange}
+ />
+ <strong>
+ <FormattedMessage
+ id='admin.manage_roles.postAllRoleTitle'
+ defaultMessage='post:all'
+ />
+ </strong>
+ <FormattedMessage
+ id='admin.manage_roles.postAllRole'
+ defaultMessage='Access to post to all Mattermost channels including direct messages.'
+ />
+ </label>
+ </div>
+ <div className='checkbox'>
+ <label>
+ <input
+ type='checkbox'
+ ref='postallpublic'
+ checked={this.state.hasPostAllPublicRole || this.state.hasPostAllRole || this.state.isSystemAdmin}
+ disabled={this.state.hasPostAllRole || this.state.isSystemAdmin}
+ onChange={this.handlePostAllPublicChange}
+ />
+ <strong>
+ <FormattedMessage
+ id='admin.manage_roles.postAllPublicRoleTitle'
+ defaultMessage='post:channels'
+ />
+ </strong>
+ <FormattedMessage
+ id='admin.manage_roles.postAllPublicRole'
+ defaultMessage='Access to post to all Mattermost public channels.'
+ />
+ </label>
+ </div>
+ </div>
+ );
+ }
+
+ let userAccessTokenContent;
+ if (this.props.userAccessTokensEnabled) {
+ userAccessTokenContent = (
+ <div>
+ <div className='checkbox'>
+ <label>
+ <input
+ type='checkbox'
+ ref='postall'
+ checked={this.state.hasUserAccessTokenRole || this.state.isSystemAdmin}
+ disabled={this.state.isSystemAdmin}
+ onChange={this.handleUserAccessTokenChange}
+ />
+ <FormattedHTMLMessage
+ id='admin.manage_roles.allowUserAccessTokens'
+ defaultMessage='Allow this account to generate <a href="https://about.mattermost.com/default-user-access-tokens" target="_blank">user access tokens</a>.'
+ />
+ </label>
+ </div>
+ <div className='member-row--padded'>
+ {additionalRoles}
+ </div>
+ </div>
+ );
+ }
+
+ return (
+ <div>
+ <div className='manage-teams__user'>
+ <img
+ className='manage-teams__profile-picture'
+ src={Client4.getProfilePictureUrl(user.id, user.last_picture_update)}
+ />
+ <div className='manage-teams__info'>
+ <div className='manage-teams__name'>
+ {name}
+ </div>
+ <div className='manage-teams__email'>
+ {user.email}
+ </div>
+ </div>
+ </div>
+ <div>
+ <div className='manage-row--inner'>
+ <div className='radio-inline'>
+ <label>
+ <input
+ name='systemadmin'
+ type='radio'
+ checked={this.state.isSystemAdmin}
+ onChange={this.handleSystemAdminChange}
+ />
+ <FormattedMessage
+ id='admin.manage_roles.systemAdmin'
+ defaultMessage='System Admin'
+ />
+ </label>
+ </div>
+ <div className='radio-inline'>
+ <label>
+ <input
+ name='systemmember'
+ type='radio'
+ checked={!this.state.isSystemAdmin}
+ onChange={this.handleSystemAdminChange}
+ />
+ <FormattedMessage
+ id='admin.manage_roles.systemMember'
+ defaultMessage='Member'
+ />
+ </label>
+ </div>
+ </div>
+ {userAccessTokenContent}
+ </div>
+ </div>
+ );
+ }
+
+ render() {
+ return (
+ <Modal
+ show={this.props.show}
+ onHide={this.props.onModalDismissed}
+ dialogClassName='manage-teams'
+ >
+ <Modal.Header closeButton={true}>
+ <Modal.Title>
+ <FormattedMessage
+ id='admin.manage_roles.manageRolesTitle'
+ defaultMessage='Manage Roles'
+ />
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ {this.renderContents()}
+ {this.state.error}
+ </Modal.Body>
+ <Modal.Footer>
+ <button
+ type='button'
+ className='btn btn-link'
+ onClick={this.props.onModalDismissed}
+ >
+ <FormattedMessage
+ id='admin.manage_roles.cancel'
+ defaultMessage='Cancel'
+ />
+ </button>
+ <button
+ type='button'
+ className='btn btn-primary'
+ onClick={this.handleSave}
+ >
+ <FormattedMessage
+ id='admin.manage_roles.save'
+ defaultMessage='Save'
+ />
+ </button>
+ </Modal.Footer>
+ </Modal>
+ );
+ }
+}
diff --git a/webapp/components/admin_console/manage_teams_modal/manage_teams_modal.jsx b/webapp/components/admin_console/manage_teams_modal/manage_teams_modal.jsx
index a579ab03c..21f9d762d 100644
--- a/webapp/components/admin_console/manage_teams_modal/manage_teams_modal.jsx
+++ b/webapp/components/admin_console/manage_teams_modal/manage_teams_modal.jsx
@@ -1,11 +1,10 @@
-import PropTypes from 'prop-types';
-
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import {Modal} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
+import PropTypes from 'prop-types';
import * as TeamActions from 'actions/team_actions.jsx';
@@ -29,14 +28,6 @@ export default class ManageTeamsModal extends React.Component {
constructor(props) {
super(props);
- this.loadTeamsAndTeamMembers = this.loadTeamsAndTeamMembers.bind(this);
-
- this.handleError = this.handleError.bind(this);
- this.handleMemberChange = this.handleMemberChange.bind(this);
- this.handleMemberRemove = this.handleMemberRemove.bind(this);
-
- this.renderContents = this.renderContents.bind(this);
-
this.state = {
error: null,
teams: null,
@@ -66,7 +57,7 @@ export default class ManageTeamsModal extends React.Component {
}
}
- loadTeamsAndTeamMembers(user = this.props.user) {
+ loadTeamsAndTeamMembers = (user = this.props.user) => {
TeamActions.getTeamsForUser(user.id, (teams) => {
this.setState({
teams: teams.sort(sortTeamsByDisplayName)
@@ -80,13 +71,13 @@ export default class ManageTeamsModal extends React.Component {
});
}
- handleError(error) {
+ handleError = (error) => {
this.setState({
error
});
}
- handleMemberChange() {
+ handleMemberChange = () => {
TeamActions.getTeamMembersForUser(this.props.user.id, (teamMembers) => {
this.setState({
teamMembers
@@ -94,14 +85,14 @@ export default class ManageTeamsModal extends React.Component {
});
}
- handleMemberRemove(teamId) {
+ handleMemberRemove = (teamId) => {
this.setState({
teams: this.state.teams.filter((team) => team.id !== teamId),
teamMembers: this.state.teamMembers.filter((teamMember) => teamMember.team_id !== teamId)
});
}
- renderContents() {
+ renderContents = () => {
const {user} = this.props;
const {teams, teamMembers} = this.state;
diff --git a/webapp/components/admin_console/manage_teams_modal/remove_from_team_button.jsx b/webapp/components/admin_console/manage_teams_modal/remove_from_team_button.jsx
index 28e9fde8f..69579d46f 100644
--- a/webapp/components/admin_console/manage_teams_modal/remove_from_team_button.jsx
+++ b/webapp/components/admin_console/manage_teams_modal/remove_from_team_button.jsx
@@ -41,7 +41,7 @@ export default class RemoveFromTeamButton extends React.PureComponent {
render() {
return (
<button
- className='btn btn-default'
+ className='btn btn-danger'
onClick={this.handleClick}
>
<FormattedMessage
diff --git a/webapp/components/admin_console/manage_tokens_modal/index.js b/webapp/components/admin_console/manage_tokens_modal/index.js
new file mode 100644
index 000000000..9f7a31141
--- /dev/null
+++ b/webapp/components/admin_console/manage_tokens_modal/index.js
@@ -0,0 +1,27 @@
+// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {connect} from 'react-redux';
+import {bindActionCreators} from 'redux';
+import {getUserAccessTokensForUser} from 'mattermost-redux/actions/users';
+
+import ManageTokensModal from './manage_tokens_modal.jsx';
+
+function mapStateToProps(state, ownProps) {
+ const userId = ownProps.user ? ownProps.user.id : '';
+
+ return {
+ ...ownProps,
+ userAccessTokens: state.entities.admin.userAccessTokens[userId]
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ actions: bindActionCreators({
+ getUserAccessTokensForUser
+ }, dispatch)
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(ManageTokensModal);
diff --git a/webapp/components/admin_console/manage_tokens_modal/manage_tokens_modal.jsx b/webapp/components/admin_console/manage_tokens_modal/manage_tokens_modal.jsx
new file mode 100644
index 000000000..c31325291
--- /dev/null
+++ b/webapp/components/admin_console/manage_tokens_modal/manage_tokens_modal.jsx
@@ -0,0 +1,181 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import LoadingScreen from 'components/loading_screen.jsx';
+import RevokeTokenButton from 'components/admin_console/revoke_token_button';
+
+import {Client4} from 'mattermost-redux/client';
+import * as UserUtils from 'mattermost-redux/utils/user_utils';
+
+import React from 'react';
+import {Modal} from 'react-bootstrap';
+import PropTypes from 'prop-types';
+import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
+
+export default class ManageTokensModal extends React.PureComponent {
+ static propTypes = {
+
+ /**
+ * Set to render the modal
+ */
+ show: PropTypes.bool.isRequired,
+
+ /**
+ * The user the roles are being managed for
+ */
+ user: PropTypes.object,
+
+ /**
+ * The user access tokens for a user, object with token ids as keys
+ */
+ userAccessTokens: PropTypes.object,
+
+ /**
+ * Function called when modal is dismissed
+ */
+ onModalDismissed: PropTypes.func.isRequired,
+
+ actions: PropTypes.shape({
+
+ /**
+ * Function to get a user's access tokens
+ */
+ getUserAccessTokensForUser: PropTypes.func.isRequired
+ }).isRequired
+ };
+
+ constructor(props) {
+ super(props);
+ this.state = {error: null};
+ }
+
+ componentWillReceiveProps(nextProps) {
+ const userId = this.props.user ? this.props.user.id : null;
+ const nextUserId = nextProps.user ? nextProps.user.id : null;
+ if (nextUserId && nextUserId !== userId) {
+ this.props.actions.getUserAccessTokensForUser(nextUserId, 0, 200);
+ }
+ }
+
+ handleError = (error) => {
+ this.setState({
+ error
+ });
+ }
+
+ renderContents = () => {
+ const {user, userAccessTokens} = this.props;
+
+ if (!user) {
+ return <LoadingScreen/>;
+ }
+
+ let name = UserUtils.getFullName(user);
+ if (name) {
+ name += ` (@${user.username})`;
+ } else {
+ name = `@${user.username}`;
+ }
+
+ let tokenList;
+ if (userAccessTokens) {
+ const userAccessTokensList = Object.values(userAccessTokens);
+
+ if (userAccessTokensList.length === 0) {
+ tokenList = (
+ <div className='manage-row__empty'>
+ <FormattedMessage
+ id='admin.manage_tokens.userAccessTokensNone'
+ defaultMessage='No user access tokens.'
+ />
+ </div>
+ );
+ } else {
+ tokenList = userAccessTokensList.map((token) => {
+ return (
+ <div
+ key={token.id}
+ className='manage-teams__team'
+ >
+ <div className='manage-teams__team-name'>
+ <div>
+ <FormattedMessage
+ id='admin.manage_tokens.userAccessTokensNameLabel'
+ defaultMessage='Name: '
+ />
+ {token.description}
+ </div>
+ <div>
+ <FormattedMessage
+ id='admin.manage_tokens.userAccessTokensIdLabel'
+ defaultMessage='Token ID: '
+ />
+ {token.id}
+ </div>
+ </div>
+ <div className='manage-teams__team-actions'>
+ <RevokeTokenButton
+ tokenId={token.id}
+ onError={this.handleError}
+ />
+ </div>
+ </div>
+ );
+ });
+ }
+ } else {
+ tokenList = <LoadingScreen/>;
+ }
+
+ return (
+ <div>
+ <div className='manage-teams__user'>
+ <img
+ className='manage-teams__profile-picture'
+ src={Client4.getProfilePictureUrl(user.id, user.last_picture_update)}
+ />
+ <div className='manage-teams__info'>
+ <div className='manage-teams__name'>
+ {name}
+ </div>
+ <div className='manage-teams__email'>
+ {user.email}
+ </div>
+ </div>
+ </div>
+ <div className='padding-top x2'>
+ <FormattedHTMLMessage
+ id='admin.manage_tokens.userAccessTokensDescription'
+ defaultMessage='User access tokens function similar to session tokens and can be used by integrations to <a href="https://about.mattermost.com/default-api-authentication" target="_blank">authenticate against the REST API</a>. Learn more about <a href="https://about.mattermost.com/default-user-access-tokens" target="_blank">user access tokens</a>.'
+ />
+ </div>
+ <div className='manage-teams__teams'>
+ {tokenList}
+ </div>
+ </div>
+ );
+ }
+
+ render() {
+ return (
+ <Modal
+ show={this.props.show}
+ onHide={this.props.onModalDismissed}
+ dialogClassName='manage-teams'
+ >
+ <Modal.Header closeButton={true}>
+ <Modal.Title>
+ <FormattedMessage
+ id='admin.manage_tokens.manageTokensTitle'
+ defaultMessage='Manage User Access Tokens'
+ />
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ {this.renderContents()}
+ {this.state.error}
+ </Modal.Body>
+ </Modal>
+ );
+ }
+}
diff --git a/webapp/components/admin_console/revoke_token_button/index.js b/webapp/components/admin_console/revoke_token_button/index.js
new file mode 100644
index 000000000..6fada1bcc
--- /dev/null
+++ b/webapp/components/admin_console/revoke_token_button/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 {revokeUserAccessToken} from 'mattermost-redux/actions/users';
+
+import RevokeTokenButton from './revoke_token_button.jsx';
+
+function mapStateToProps(state, ownProps) {
+ return {
+ ...ownProps
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ actions: bindActionCreators({
+ revokeUserAccessToken
+ }, dispatch)
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(RevokeTokenButton);
diff --git a/webapp/components/admin_console/revoke_token_button/revoke_token_button.jsx b/webapp/components/admin_console/revoke_token_button/revoke_token_button.jsx
new file mode 100644
index 000000000..4829a0cde
--- /dev/null
+++ b/webapp/components/admin_console/revoke_token_button/revoke_token_button.jsx
@@ -0,0 +1,56 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+import PropTypes from 'prop-types';
+import {FormattedMessage} from 'react-intl';
+
+import {trackEvent} from 'actions/diagnostics_actions.jsx';
+
+export default class RevokeTokenButton extends React.PureComponent {
+ static propTypes = {
+
+ /*
+ * Token id to revoke
+ */
+ tokenId: PropTypes.string.isRequired,
+
+ /*
+ * Function to call on error
+ */
+ onError: PropTypes.func.isRequired,
+
+ actions: PropTypes.shape({
+
+ /**
+ * Function to revoke a user access token
+ */
+ revokeUserAccessToken: PropTypes.func.isRequired
+ }).isRequired
+ };
+
+ handleClick = async (e) => {
+ e.preventDefault();
+
+ const {error} = await this.props.actions.revokeUserAccessToken(this.props.tokenId);
+ trackEvent('system_console', 'revoke_user_access_token');
+
+ if (error) {
+ this.props.onError(error.message);
+ }
+ }
+
+ render() {
+ return (
+ <button
+ className='btn btn-danger'
+ onClick={this.handleClick}
+ >
+ <FormattedMessage
+ id='admin.revoke_token_button.delete'
+ defaultMessage='Delete'
+ />
+ </button>
+ );
+ }
+}
diff --git a/webapp/components/admin_console/system_users/index.js b/webapp/components/admin_console/system_users/index.js
index 8f1c0dc35..261a11d7e 100644
--- a/webapp/components/admin_console/system_users/index.js
+++ b/webapp/components/admin_console/system_users/index.js
@@ -4,7 +4,7 @@
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {getTeams, getTeamStats} from 'mattermost-redux/actions/teams';
-import {getUser} from 'mattermost-redux/actions/users';
+import {getUser, getUserAccessToken} from 'mattermost-redux/actions/users';
import {getTeamsList} from 'mattermost-redux/selectors/entities/teams';
@@ -22,7 +22,8 @@ function mapDispatchToProps(dispatch) {
actions: bindActionCreators({
getTeams,
getTeamStats,
- getUser
+ getUser,
+ getUserAccessToken
}, dispatch)
};
}
diff --git a/webapp/components/admin_console/system_users/system_users.jsx b/webapp/components/admin_console/system_users/system_users.jsx
index 5c8aa9bfd..4fbdc26d8 100644
--- a/webapp/components/admin_console/system_users/system_users.jsx
+++ b/webapp/components/admin_console/system_users/system_users.jsx
@@ -54,7 +54,12 @@ export default class SystemUsers extends React.Component {
/*
* Function to get a user
*/
- getUser: PropTypes.func.isRequired
+ getUser: PropTypes.func.isRequired,
+
+ /*
+ * Function to get a user access token
+ */
+ getUserAccessToken: PropTypes.func.isRequired
}).isRequired
}
@@ -240,7 +245,7 @@ export default class SystemUsers extends React.Component {
(users) => {
if (users.length === 0 && term.length === USER_ID_LENGTH) {
// This term didn't match any users name, but it does look like it might be a user's ID
- this.getUserById(term);
+ this.getUserByTokenOrId(term);
} else {
this.setState({loading: false});
}
@@ -269,6 +274,22 @@ export default class SystemUsers extends React.Component {
);
}
+ getUserByTokenOrId = async (id) => {
+ if (global.window.mm_config.EnableUserAccessTokens === 'true') {
+ const {data} = await this.props.actions.getUserAccessToken(id);
+
+ if (data) {
+ this.term = data.user_id;
+ this.setState({term: data.user_id});
+ this.updateUsersFromStore(this.state.teamId, data.user_id);
+ this.getUserById(data.user_id);
+ return;
+ }
+ }
+
+ this.getUserById(id);
+ }
+
renderFilterRow(doSearch) {
const teams = this.props.teams.map((team) => {
return (
diff --git a/webapp/components/admin_console/system_users/system_users_dropdown.jsx b/webapp/components/admin_console/system_users/system_users_dropdown.jsx
index fe53ade44..1dbb6b325 100644
--- a/webapp/components/admin_console/system_users/system_users_dropdown.jsx
+++ b/webapp/components/admin_console/system_users/system_users_dropdown.jsx
@@ -8,7 +8,7 @@ import UserStore from 'stores/user_store.jsx';
import Constants from 'utils/constants.jsx';
import * as Utils from 'utils/utils.jsx';
-import {updateUserRoles, updateActive} from 'actions/user_actions.jsx';
+import {updateActive} from 'actions/user_actions.jsx';
import {adminResetMfa} from 'actions/admin_actions.jsx';
import {FormattedMessage} from 'react-intl';
@@ -19,28 +19,36 @@ import React from 'react';
export default class SystemUsersDropdown extends React.Component {
static propTypes = {
+
+ /*
+ * User to manage with dropdown
+ */
user: PropTypes.object.isRequired,
+
+ /*
+ * Function to open password reset, takes user as an argument
+ */
doPasswordReset: PropTypes.func.isRequired,
- doManageTeams: PropTypes.func.isRequired
+
+ /*
+ * Function to open manage teams, takes user as an argument
+ */
+ doManageTeams: PropTypes.func.isRequired,
+
+ /*
+ * Function to open manage roles, takes user as an argument
+ */
+ doManageRoles: PropTypes.func.isRequired,
+
+ /*
+ * Function to open manage tokens, takes user as an argument
+ */
+ doManageTokens: PropTypes.func.isRequired
};
constructor(props) {
super(props);
- this.handleMakeMember = this.handleMakeMember.bind(this);
- this.handleMakeActive = this.handleMakeActive.bind(this);
- this.handleShowDeactivateMemberModal = this.handleShowDeactivateMemberModal.bind(this);
- this.handleDeactivateMember = this.handleDeactivateMember.bind(this);
- this.handleDeactivateCancel = this.handleDeactivateCancel.bind(this);
- this.handleMakeSystemAdmin = this.handleMakeSystemAdmin.bind(this);
- this.handleManageTeams = this.handleManageTeams.bind(this);
- this.handleResetPassword = this.handleResetPassword.bind(this);
- this.handleResetMfa = this.handleResetMfa.bind(this);
- this.handleDemoteSystemAdmin = this.handleDemoteSystemAdmin.bind(this);
- this.handleDemoteSubmit = this.handleDemoteSubmit.bind(this);
- this.handleDemoteCancel = this.handleDemoteCancel.bind(this);
- this.renderDeactivateMemberModal = this.renderDeactivateMemberModal.bind(this);
-
this.state = {
serverError: null,
showDemoteModal: false,
@@ -50,61 +58,39 @@ export default class SystemUsersDropdown extends React.Component {
};
}
- doMakeMember() {
- updateUserRoles(
- this.props.user.id,
- 'system_user',
- null,
+ handleMakeActive = (e) => {
+ e.preventDefault();
+ updateActive(this.props.user.id, true, null,
(err) => {
this.setState({serverError: err.message});
}
);
}
- handleMakeMember(e) {
+ handleManageTeams = (e) => {
e.preventDefault();
- const me = UserStore.getCurrentUser();
- if (this.props.user.id === me.id && me.roles.includes('system_admin')) {
- this.handleDemoteSystemAdmin(this.props.user, 'member');
- } else {
- this.doMakeMember();
- }
- }
- handleMakeActive(e) {
- e.preventDefault();
- updateActive(this.props.user.id, true, null,
- (err) => {
- this.setState({serverError: err.message});
- }
- );
+ this.props.doManageTeams(this.props.user);
}
- handleMakeSystemAdmin(e) {
+ handleManageRoles = (e) => {
e.preventDefault();
- updateUserRoles(
- this.props.user.id,
- 'system_user system_admin',
- null,
- (err) => {
- this.setState({serverError: err.message});
- }
- );
+ this.props.doManageRoles(this.props.user);
}
- handleManageTeams(e) {
+ handleManageTokens = (e) => {
e.preventDefault();
- this.props.doManageTeams(this.props.user);
+ this.props.doManageTokens(this.props.user);
}
- handleResetPassword(e) {
+ handleResetPassword = (e) => {
e.preventDefault();
this.props.doPasswordReset(this.props.user);
}
- handleResetMfa(e) {
+ handleResetMfa = (e) => {
e.preventDefault();
adminResetMfa(this.props.user.id,
@@ -115,7 +101,7 @@ export default class SystemUsersDropdown extends React.Component {
);
}
- handleDemoteSystemAdmin(user, role) {
+ handleDemoteSystemAdmin = (user, role) => {
this.setState({
serverError: this.state.serverError,
showDemoteModal: true,
@@ -124,7 +110,7 @@ export default class SystemUsersDropdown extends React.Component {
});
}
- handleDemoteCancel() {
+ handleDemoteCancel = () => {
this.setState({
serverError: null,
showDemoteModal: false,
@@ -133,7 +119,7 @@ export default class SystemUsersDropdown extends React.Component {
});
}
- handleDemoteSubmit() {
+ handleDemoteSubmit = () => {
if (this.state.role === 'member') {
this.doMakeMember();
}
@@ -147,13 +133,13 @@ export default class SystemUsersDropdown extends React.Component {
}
}
- handleShowDeactivateMemberModal(e) {
+ handleShowDeactivateMemberModal = (e) => {
e.preventDefault();
this.setState({showDeactivateMemberModal: true});
}
- handleDeactivateMember() {
+ handleDeactivateMember = () => {
updateActive(this.props.user.id, false, null,
(err) => {
this.setState({serverError: err.message});
@@ -163,11 +149,11 @@ export default class SystemUsersDropdown extends React.Component {
this.setState({showDeactivateMemberModal: false});
}
- handleDeactivateCancel() {
+ handleDeactivateCancel = () => {
this.setState({showDeactivateMemberModal: false});
}
- renderDeactivateMemberModal() {
+ renderDeactivateMemberModal = () => {
const title = (
<FormattedMessage
id='deactivate_member_modal.title'
@@ -240,8 +226,6 @@ export default class SystemUsersDropdown extends React.Component {
}
const me = UserStore.getCurrentUser();
- let showMakeMember = Utils.isSystemAdmin(user.roles);
- let showMakeSystemAdmin = !Utils.isSystemAdmin(user.roles);
let showMakeActive = false;
let showMakeNotActive = !Utils.isSystemAdmin(user.roles);
let showManageTeams = true;
@@ -255,8 +239,6 @@ export default class SystemUsersDropdown extends React.Component {
defaultMessage='Inactive'
/>
);
- showMakeMember = false;
- showMakeSystemAdmin = false;
showMakeActive = true;
showMakeNotActive = false;
showManageTeams = false;
@@ -267,44 +249,6 @@ export default class SystemUsersDropdown extends React.Component {
disableActivationToggle = true;
}
- let makeSystemAdmin = null;
- if (showMakeSystemAdmin) {
- makeSystemAdmin = (
- <li role='presentation'>
- <a
- id='makeSystemAdmin'
- role='menuitem'
- href='#'
- onClick={this.handleMakeSystemAdmin}
- >
- <FormattedMessage
- id='admin.user_item.makeSysAdmin'
- defaultMessage='Make System Admin'
- />
- </a>
- </li>
- );
- }
-
- let makeMember = null;
- if (showMakeMember) {
- makeMember = (
- <li role='presentation'>
- <a
- id='makeMember'
- role='menuitem'
- href='#'
- onClick={this.handleMakeMember}
- >
- <FormattedMessage
- id='admin.user_item.makeMember'
- defaultMessage='Make Member'
- />
- </a>
- </li>
- );
- }
-
let menuClass = '';
if (disableActivationToggle) {
menuClass = 'disabled';
@@ -427,6 +371,25 @@ export default class SystemUsersDropdown extends React.Component {
);
}
+ let manageTokens;
+ if (global.window.mm_config.EnableUserAccessTokens === 'true') {
+ manageTokens = (
+ <li role='presentation'>
+ <a
+ id='manageTokens'
+ role='menuitem'
+ href='#'
+ onClick={this.handleManageTokens}
+ >
+ <FormattedMessage
+ id='admin.user_item.manageTokens'
+ defaultMessage='Manage Tokens'
+ />
+ </a>
+ </li>
+ );
+ }
+
let makeDemoteModal = null;
if (this.props.user.id === me.id) {
const title = (
@@ -498,11 +461,23 @@ export default class SystemUsersDropdown extends React.Component {
className='dropdown-menu member-menu'
role='menu'
>
- {makeMember}
{makeActive}
{makeNotActive}
- {makeSystemAdmin}
+ <li role='presentation'>
+ <a
+ id='manageRoles'
+ role='menuitem'
+ href='#'
+ onClick={this.handleManageRoles}
+ >
+ <FormattedMessage
+ id='admin.user_item.manageRoles'
+ defaultMessage='Manage Roles'
+ />
+ </a>
+ </li>
{manageTeams}
+ {manageTokens}
{mfaReset}
{passwordReset}
</ul>
diff --git a/webapp/components/admin_console/system_users/system_users_list.jsx b/webapp/components/admin_console/system_users/system_users_list.jsx
index 6d58137ff..2863f9cec 100644
--- a/webapp/components/admin_console/system_users/system_users_list.jsx
+++ b/webapp/components/admin_console/system_users/system_users_list.jsx
@@ -6,6 +6,8 @@ import PropTypes from 'prop-types';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
import ManageTeamsModal from 'components/admin_console/manage_teams_modal/manage_teams_modal.jsx';
+import ManageRolesModal from 'components/admin_console/manage_roles_modal';
+import ManageTokensModal from 'components/admin_console/manage_tokens_modal';
import ResetPasswordModal from 'components/admin_console/reset_password_modal.jsx';
import SearchableUserList from 'components/searchable_user_list/searchable_user_list.jsx';
@@ -14,6 +16,7 @@ const dispatch = store.dispatch;
const getState = store.getState;
import {getUser} from 'mattermost-redux/actions/users';
+import * as UserUtils from 'mattermost-redux/utils/user_utils';
import {Constants} from 'utils/constants.jsx';
import * as Utils from 'utils/utils.jsx';
@@ -37,21 +40,12 @@ export default class SystemUsersList extends React.Component {
constructor(props) {
super(props);
- this.nextPage = this.nextPage.bind(this);
- this.previousPage = this.previousPage.bind(this);
- this.search = this.search.bind(this);
-
- this.doManageTeams = this.doManageTeams.bind(this);
- this.doManageTeamsDismiss = this.doManageTeamsDismiss.bind(this);
-
- this.doPasswordReset = this.doPasswordReset.bind(this);
- this.doPasswordResetDismiss = this.doPasswordResetDismiss.bind(this);
- this.doPasswordResetSubmit = this.doPasswordResetSubmit.bind(this);
-
this.state = {
page: 0,
showManageTeamsModal: false,
+ showManageRolesModal: false,
+ showManageTokensModal: false,
showPasswordModal: false,
user: null
};
@@ -63,17 +57,17 @@ export default class SystemUsersList extends React.Component {
}
}
- nextPage() {
+ nextPage = () => {
this.setState({page: this.state.page + 1});
this.props.nextPage(this.state.page + 1);
}
- previousPage() {
+ previousPage = () => {
this.setState({page: this.state.page - 1});
}
- search(term) {
+ search = (term) => {
this.props.search(term);
if (term !== '') {
@@ -81,35 +75,63 @@ export default class SystemUsersList extends React.Component {
}
}
- doManageTeams(user) {
+ doManageTeams = (user) => {
this.setState({
showManageTeamsModal: true,
user
});
}
- doManageTeamsDismiss() {
+ doManageRoles = (user) => {
+ this.setState({
+ showManageRolesModal: true,
+ user
+ });
+ }
+
+ doManageTokens = (user) => {
+ this.setState({
+ showManageTokensModal: true,
+ user
+ });
+ }
+
+ doManageTeamsDismiss = () => {
this.setState({
showManageTeamsModal: false,
user: null
});
}
- doPasswordReset(user) {
+ doManageRolesDismiss = () => {
+ this.setState({
+ showManageRolesModal: false,
+ user: null
+ });
+ }
+
+ doManageTokensDismiss = () => {
+ this.setState({
+ showManageTokensModal: false,
+ user: null
+ });
+ }
+
+ doPasswordReset = (user) => {
this.setState({
showPasswordModal: true,
user
});
}
- doPasswordResetDismiss() {
+ doPasswordResetDismiss = () => {
this.setState({
showPasswordModal: false,
user: null
});
}
- doPasswordResetSubmit(user) {
+ doPasswordResetSubmit = (user) => {
getUser(user.id)(dispatch, getState);
this.setState({
@@ -174,6 +196,35 @@ export default class SystemUsersList extends React.Component {
}
}
+ const userAccessTokensEnabled = global.window.mm_config.EnableUserAccessTokens === 'true';
+ if (userAccessTokensEnabled) {
+ const hasPostAllRole = UserUtils.hasPostAllRole(user.roles);
+ const hasPostAllPublicRole = UserUtils.hasPostAllPublicRole(user.roles);
+ const hasUserAccessTokenRole = UserUtils.hasUserAccessTokenRole(user.roles);
+ const isSystemAdmin = UserUtils.isSystemAdmin(user.roles);
+
+ let messageId = 'admin.user_item.userAccessTokenNo';
+ if (hasUserAccessTokenRole || isSystemAdmin) {
+ if (isSystemAdmin) {
+ messageId = 'admin.user_item.userAccessTokenAdmin';
+ } else if (hasPostAllRole) {
+ messageId = 'admin.user_item.userAccessTokenPostAll';
+ } else if (hasPostAllPublicRole) {
+ messageId = 'admin.user_item.userAccessTokenPostAllPublic';
+ } else {
+ messageId = 'admin.user_item.userAccessTokenYes';
+ }
+ }
+
+ info.push(', ');
+ info.push(
+ <FormattedHTMLMessage
+ key='admin.user_item.userAccessToken'
+ id={messageId}
+ />
+ );
+ }
+
return info;
}
@@ -236,7 +287,9 @@ export default class SystemUsersList extends React.Component {
actions={[SystemUsersDropdown]}
actionProps={{
doPasswordReset: this.doPasswordReset,
- doManageTeams: this.doManageTeams
+ doManageTeams: this.doManageTeams,
+ doManageRoles: this.doManageRoles,
+ doManageTokens: this.doManageTokens
}}
nextPage={this.nextPage}
previousPage={this.previousPage}
@@ -250,6 +303,16 @@ export default class SystemUsersList extends React.Component {
show={this.state.showManageTeamsModal}
onModalDismissed={this.doManageTeamsDismiss}
/>
+ <ManageRolesModal
+ user={this.state.user}
+ show={this.state.showManageRolesModal}
+ onModalDismissed={this.doManageRolesDismiss}
+ />
+ <ManageTokensModal
+ user={this.state.user}
+ show={this.state.showManageTokensModal}
+ onModalDismissed={this.doManageTokensDismiss}
+ />
<ResetPasswordModal
user={this.state.user}
show={this.state.showPasswordModal}