mirror of
https://github.com/mattermost/mattermost.git
synced 2026-02-18 18:18:23 -05:00
PLT-6987 User access token UI (#7007)
* Add user access token UI * Fix enter press and update mattermost-redux * Updating UI for access token stuff (#7066) * Revert segment key
This commit is contained in:
parent
4ef844298f
commit
5da5c0bbfb
33 changed files with 1690 additions and 252 deletions
|
|
@ -302,6 +302,61 @@ func TestCreatePostPublic(t *testing.T) {
|
|||
CheckNoError(t, resp)
|
||||
}
|
||||
|
||||
func TestCreatePostAll(t *testing.T) {
|
||||
th := Setup().InitBasic().InitSystemAdmin()
|
||||
defer TearDown()
|
||||
Client := th.Client
|
||||
|
||||
post := &model.Post{ChannelId: th.BasicChannel.Id, Message: "#hashtag a" + model.NewId() + "a"}
|
||||
|
||||
user := model.User{Email: GenerateTestEmail(), Nickname: "Joram Wilander", Password: "hello1", Username: GenerateTestUsername(), Roles: model.ROLE_SYSTEM_USER.Id}
|
||||
|
||||
directChannel, _ := app.CreateDirectChannel(th.BasicUser.Id, th.BasicUser2.Id)
|
||||
|
||||
ruser, resp := Client.CreateUser(&user)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
Client.Login(user.Email, user.Password)
|
||||
|
||||
_, resp = Client.CreatePost(post)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
|
||||
app.UpdateUserRoles(ruser.Id, model.ROLE_SYSTEM_USER.Id+" "+model.ROLE_SYSTEM_POST_ALL.Id)
|
||||
app.InvalidateAllCaches()
|
||||
|
||||
Client.Login(user.Email, user.Password)
|
||||
|
||||
_, resp = Client.CreatePost(post)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
post.ChannelId = th.BasicPrivateChannel.Id
|
||||
_, resp = Client.CreatePost(post)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
post.ChannelId = directChannel.Id
|
||||
_, resp = Client.CreatePost(post)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
app.UpdateUserRoles(ruser.Id, model.ROLE_SYSTEM_USER.Id)
|
||||
app.JoinUserToTeam(th.BasicTeam, ruser, "")
|
||||
app.UpdateTeamMemberRoles(th.BasicTeam.Id, ruser.Id, model.ROLE_TEAM_USER.Id+" "+model.ROLE_TEAM_POST_ALL.Id)
|
||||
app.InvalidateAllCaches()
|
||||
|
||||
Client.Login(user.Email, user.Password)
|
||||
|
||||
post.ChannelId = th.BasicPrivateChannel.Id
|
||||
_, resp = Client.CreatePost(post)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
post.ChannelId = th.BasicChannel.Id
|
||||
_, resp = Client.CreatePost(post)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
post.ChannelId = directChannel.Id
|
||||
_, resp = Client.CreatePost(post)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
}
|
||||
|
||||
func TestUpdatePost(t *testing.T) {
|
||||
th := Setup().InitBasic().InitSystemAdmin()
|
||||
defer TearDown()
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ func SessionHasPermissionToChannel(session model.Session, channelId string, perm
|
|||
}
|
||||
|
||||
channel, err := GetChannel(channelId)
|
||||
if err == nil {
|
||||
if err == nil && channel.TeamId != "" {
|
||||
return SessionHasPermissionToTeam(session, channel.TeamId, permission)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -165,6 +165,7 @@ func trackConfig() {
|
|||
"enable_post_username_override": utils.Cfg.ServiceSettings.EnablePostUsernameOverride,
|
||||
"enable_post_icon_override": utils.Cfg.ServiceSettings.EnablePostIconOverride,
|
||||
"enable_apiv3": *utils.Cfg.ServiceSettings.EnableAPIv3,
|
||||
"enable_user_access_tokens": *utils.Cfg.ServiceSettings.EnableUserAccessTokens,
|
||||
"enable_custom_emoji": *utils.Cfg.ServiceSettings.EnableCustomEmoji,
|
||||
"enable_emoji_picker": *utils.Cfg.ServiceSettings.EnableEmojiPicker,
|
||||
"restrict_custom_emoji_creation": *utils.Cfg.ServiceSettings.RestrictCustomEmojiCreation,
|
||||
|
|
|
|||
16
i18n/en.json
16
i18n/en.json
|
|
@ -3503,6 +3503,14 @@
|
|||
"id": "app.import.validate_user_teams_import_data.team_name_missing.error",
|
||||
"translation": "Team name missing from User's Team Membership."
|
||||
},
|
||||
{
|
||||
"id": "authentication.roles.system_post_all.name",
|
||||
"translation": "Post in Public, Private and Direct Channels"
|
||||
},
|
||||
{
|
||||
"id": "authentication.roles.system_post_all.description",
|
||||
"translation": "A role with the permission to post in any public, private or direct channel on the system"
|
||||
},
|
||||
{
|
||||
"id": "authentication.roles.system_post_all_public.name",
|
||||
"translation": "Post in Public Channels"
|
||||
|
|
@ -3511,6 +3519,14 @@
|
|||
"id": "authentication.roles.system_post_all_public.description",
|
||||
"translation": "A role with the permission to post in any public channel on the system"
|
||||
},
|
||||
{
|
||||
"id": "authentication.roles.team_post_all.name",
|
||||
"translation": "Post in Public and Private Channels"
|
||||
},
|
||||
{
|
||||
"id": "authentication.roles.team_post_all.description",
|
||||
"translation": "A role with the permission to post in any public or private channel on the team"
|
||||
},
|
||||
{
|
||||
"id": "authentication.roles.team_post_all_public.name",
|
||||
"translation": "Post in Public Channels"
|
||||
|
|
|
|||
|
|
@ -71,11 +71,13 @@ var PERMISSION_MANAGE_SYSTEM *Permission
|
|||
|
||||
var ROLE_SYSTEM_USER *Role
|
||||
var ROLE_SYSTEM_ADMIN *Role
|
||||
var ROLE_SYSTEM_POST_ALL *Role
|
||||
var ROLE_SYSTEM_POST_ALL_PUBLIC *Role
|
||||
var ROLE_SYSTEM_USER_ACCESS_TOKEN *Role
|
||||
|
||||
var ROLE_TEAM_USER *Role
|
||||
var ROLE_TEAM_ADMIN *Role
|
||||
var ROLE_TEAM_POST_ALL *Role
|
||||
var ROLE_TEAM_POST_ALL_PUBLIC *Role
|
||||
|
||||
var ROLE_CHANNEL_USER *Role
|
||||
|
|
@ -376,6 +378,16 @@ func InitalizeRoles() {
|
|||
}
|
||||
BuiltInRoles[ROLE_TEAM_USER.Id] = ROLE_TEAM_USER
|
||||
|
||||
ROLE_TEAM_POST_ALL = &Role{
|
||||
"team_post_all",
|
||||
"authentication.roles.team_post_all.name",
|
||||
"authentication.roles.team_post_all.description",
|
||||
[]string{
|
||||
PERMISSION_CREATE_POST.Id,
|
||||
},
|
||||
}
|
||||
BuiltInRoles[ROLE_TEAM_POST_ALL.Id] = ROLE_TEAM_POST_ALL
|
||||
|
||||
ROLE_TEAM_POST_ALL_PUBLIC = &Role{
|
||||
"team_post_all_public",
|
||||
"authentication.roles.team_post_all_public.name",
|
||||
|
|
@ -417,6 +429,16 @@ func InitalizeRoles() {
|
|||
}
|
||||
BuiltInRoles[ROLE_SYSTEM_USER.Id] = ROLE_SYSTEM_USER
|
||||
|
||||
ROLE_SYSTEM_POST_ALL = &Role{
|
||||
"system_post_all",
|
||||
"authentication.roles.system_post_all.name",
|
||||
"authentication.roles.system_post_all.description",
|
||||
[]string{
|
||||
PERMISSION_CREATE_POST.Id,
|
||||
},
|
||||
}
|
||||
BuiltInRoles[ROLE_SYSTEM_POST_ALL.Id] = ROLE_SYSTEM_POST_ALL
|
||||
|
||||
ROLE_SYSTEM_POST_ALL_PUBLIC = &Role{
|
||||
"system_post_all_public",
|
||||
"authentication.roles.system_post_all_public.name",
|
||||
|
|
|
|||
|
|
@ -282,6 +282,12 @@ func UpgradeDatabaseToVersion40(sqlStore SqlStore) {
|
|||
func UpgradeDatabaseToVersion41(sqlStore SqlStore) {
|
||||
// TODO: Uncomment following condition when version 4.1.0 is released
|
||||
// if shouldPerformUpgrade(sqlStore, VERSION_4_0_0, VERSION_4_1_0) {
|
||||
|
||||
// Increase maximum length of the Users table Roles column.
|
||||
if sqlStore.GetMaxLengthOfColumnIfExists("Users", "Roles") != "256" {
|
||||
sqlStore.AlterColumnTypeIfExists("Users", "Roles", "varchar(256)", "varchar(256)")
|
||||
}
|
||||
|
||||
sqlStore.RemoveTableIfExists("JobStatuses")
|
||||
// saveSchemaVersion(sqlStore, VERSION_4_1_0)
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ func NewSqlUserStore(sqlStore SqlStore) UserStore {
|
|||
table.ColMap("Nickname").SetMaxSize(64)
|
||||
table.ColMap("FirstName").SetMaxSize(64)
|
||||
table.ColMap("LastName").SetMaxSize(64)
|
||||
table.ColMap("Roles").SetMaxSize(64)
|
||||
table.ColMap("Roles").SetMaxSize(256)
|
||||
table.ColMap("Props").SetMaxSize(4000)
|
||||
table.ColMap("NotifyProps").SetMaxSize(2000)
|
||||
table.ColMap("Locale").SetMaxSize(5)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
25
webapp/components/admin_console/manage_roles_modal/index.js
Normal file
25
webapp/components/admin_console/manage_roles_modal/index.js
Normal file
|
|
@ -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);
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
27
webapp/components/admin_console/manage_tokens_modal/index.js
Normal file
27
webapp/components/admin_console/manage_tokens_modal/index.js
Normal file
|
|
@ -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);
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
24
webapp/components/admin_console/revoke_token_button/index.js
Normal file
24
webapp/components/admin_console/revoke_token_button/index.js
Normal file
|
|
@ -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);
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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,28 +58,7 @@ export default class SystemUsersDropdown extends React.Component {
|
|||
};
|
||||
}
|
||||
|
||||
doMakeMember() {
|
||||
updateUserRoles(
|
||||
this.props.user.id,
|
||||
'system_user',
|
||||
null,
|
||||
(err) => {
|
||||
this.setState({serverError: err.message});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
handleMakeMember(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) {
|
||||
handleMakeActive = (e) => {
|
||||
e.preventDefault();
|
||||
updateActive(this.props.user.id, true, null,
|
||||
(err) => {
|
||||
|
|
@ -80,31 +67,30 @@ export default class SystemUsersDropdown extends React.Component {
|
|||
);
|
||||
}
|
||||
|
||||
handleMakeSystemAdmin(e) {
|
||||
e.preventDefault();
|
||||
|
||||
updateUserRoles(
|
||||
this.props.user.id,
|
||||
'system_user system_admin',
|
||||
null,
|
||||
(err) => {
|
||||
this.setState({serverError: err.message});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
handleManageTeams(e) {
|
||||
handleManageTeams = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
this.props.doManageTeams(this.props.user);
|
||||
}
|
||||
|
||||
handleResetPassword(e) {
|
||||
handleManageRoles = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
this.props.doManageRoles(this.props.user);
|
||||
}
|
||||
|
||||
handleManageTokens = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
this.props.doManageTokens(this.props.user);
|
||||
}
|
||||
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export default class SettingItemMax extends React.Component {
|
|||
}
|
||||
|
||||
onKeyDown(e) {
|
||||
if (e.keyCode === Constants.KeyCodes.ENTER) {
|
||||
if (e.keyCode === Constants.KeyCodes.ENTER && this.props.submit) {
|
||||
this.props.submit(e);
|
||||
}
|
||||
}
|
||||
|
|
@ -60,8 +60,13 @@ export default class SettingItemMax extends React.Component {
|
|||
}
|
||||
|
||||
var extraInfo = null;
|
||||
let hintClass = 'setting-list__hint';
|
||||
if (this.props.infoPosition === 'top') {
|
||||
hintClass = 'padding-bottom x2';
|
||||
}
|
||||
|
||||
if (this.props.extraInfo) {
|
||||
extraInfo = (<div className='setting-list__hint'>{this.props.extraInfo}</div>);
|
||||
extraInfo = (<div className={hintClass}>{this.props.extraInfo}</div>);
|
||||
}
|
||||
|
||||
var submit = '';
|
||||
|
|
@ -95,15 +100,40 @@ export default class SettingItemMax extends React.Component {
|
|||
titleProp = this.props.title;
|
||||
}
|
||||
|
||||
let listContent = (
|
||||
<li className='setting-list-item'>
|
||||
{inputs}
|
||||
{extraInfo}
|
||||
</li>
|
||||
);
|
||||
|
||||
if (this.props.infoPosition === 'top') {
|
||||
listContent = (
|
||||
<li>
|
||||
{extraInfo}
|
||||
{inputs}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
let cancelButtonText;
|
||||
if (this.props.cancelButtonText) {
|
||||
cancelButtonText = this.props.cancelButtonText;
|
||||
} else {
|
||||
cancelButtonText = (
|
||||
<FormattedMessage
|
||||
id='setting_item_max.cancel'
|
||||
defaultMessage='Cancel'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ul className='section-max form-horizontal'>
|
||||
{title}
|
||||
<li className={widthClass}>
|
||||
<ul className='setting-list'>
|
||||
<li className='setting-list-item'>
|
||||
{inputs}
|
||||
{extraInfo}
|
||||
</li>
|
||||
{listContent}
|
||||
<li className='setting-list-item'>
|
||||
<hr/>
|
||||
{this.props.submitExtra}
|
||||
|
|
@ -116,10 +146,7 @@ export default class SettingItemMax extends React.Component {
|
|||
href='#'
|
||||
onClick={this.props.updateSection}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='setting_item_max.cancel'
|
||||
defaultMessage='Cancel'
|
||||
/>
|
||||
{cancelButtonText}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
@ -134,9 +161,15 @@ SettingItemMax.propTypes = {
|
|||
client_error: PropTypes.string,
|
||||
server_error: PropTypes.string,
|
||||
extraInfo: PropTypes.element,
|
||||
infoPosition: PropTypes.string,
|
||||
updateSection: PropTypes.func,
|
||||
submit: PropTypes.func,
|
||||
title: PropTypes.node,
|
||||
width: PropTypes.string,
|
||||
submitExtra: PropTypes.node
|
||||
submitExtra: PropTypes.node,
|
||||
cancelButtonText: PropTypes.node
|
||||
};
|
||||
|
||||
SettingItemMax.defaultProps = {
|
||||
infoPosition: 'bottom'
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,20 +3,30 @@
|
|||
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {getMe} from 'mattermost-redux/actions/users';
|
||||
import {getMe, getUserAccessTokensForUser, createUserAccessToken, revokeUserAccessToken, clearUserAccessTokens} from 'mattermost-redux/actions/users';
|
||||
import * as UserUtils from 'mattermost-redux/utils/user_utils';
|
||||
|
||||
import SecurityTab from './user_settings_security.jsx';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const tokensEnabled = state.entities.general.config.EnableUserAccessTokens === 'true';
|
||||
const userHasTokenRole = UserUtils.hasUserAccessTokenRole(ownProps.user.roles) || UserUtils.isSystemAdmin(ownProps.user.roles);
|
||||
|
||||
return {
|
||||
...ownProps
|
||||
...ownProps,
|
||||
userAccessTokens: state.entities.users.myUserAccessTokens,
|
||||
canUseAccessTokens: tokensEnabled && userHasTokenRole
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
getMe
|
||||
getMe,
|
||||
getUserAccessTokensForUser,
|
||||
createUserAccessToken,
|
||||
revokeUserAccessToken,
|
||||
clearUserAccessTokens
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ 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';
|
||||
|
||||
|
|
@ -13,15 +14,22 @@ 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 {FormattedMessage, FormattedTime, FormattedDate} from 'react-intl';
|
||||
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,
|
||||
|
|
@ -31,26 +39,45 @@ export default class SecurityTab extends React.Component {
|
|||
closeModal: PropTypes.func.isRequired,
|
||||
collapseModal: PropTypes.func.isRequired,
|
||||
setEnforceFocus: PropTypes.func.isRequired,
|
||||
|
||||
/*
|
||||
* The user 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
|
||||
getMe: PropTypes.func.isRequired,
|
||||
|
||||
/*
|
||||
* Function to get user access tokens for a user
|
||||
*/
|
||||
getUserAccessTokensForUser: PropTypes.func.isRequired,
|
||||
|
||||
/*
|
||||
* Function to create a user access token
|
||||
*/
|
||||
createUserAccessToken: PropTypes.func.isRequired,
|
||||
|
||||
/*
|
||||
* Function to revoke a user access token
|
||||
*/
|
||||
revokeUserAccessToken: PropTypes.func.isRequired,
|
||||
|
||||
/*
|
||||
* Function to clear user access tokens locally
|
||||
*/
|
||||
clearUserAccessTokens: 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();
|
||||
}
|
||||
|
||||
|
|
@ -61,6 +88,8 @@ export default class SecurityTab extends React.Component {
|
|||
confirmPassword: '',
|
||||
passwordError: '',
|
||||
serverError: '',
|
||||
tokenError: '',
|
||||
showConfirmModal: false,
|
||||
authService: this.props.user.auth_service
|
||||
};
|
||||
}
|
||||
|
|
@ -73,11 +102,18 @@ export default class SecurityTab extends React.Component {
|
|||
},
|
||||
(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) {
|
||||
submitPassword = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
var user = this.props.user;
|
||||
|
|
@ -127,12 +163,12 @@ export default class SecurityTab extends React.Component {
|
|||
);
|
||||
}
|
||||
|
||||
setupMfa(e) {
|
||||
setupMfa = (e) => {
|
||||
e.preventDefault();
|
||||
browserHistory.push('/mfa/setup');
|
||||
}
|
||||
|
||||
removeMfa() {
|
||||
removeMfa = () => {
|
||||
deactivateMfa(
|
||||
() => {
|
||||
if (global.window.mm_license.MFA === 'true' &&
|
||||
|
|
@ -157,19 +193,19 @@ export default class SecurityTab extends React.Component {
|
|||
);
|
||||
}
|
||||
|
||||
updateCurrentPassword(e) {
|
||||
updateCurrentPassword = (e) => {
|
||||
this.setState({currentPassword: e.target.value});
|
||||
}
|
||||
|
||||
updateNewPassword(e) {
|
||||
updateNewPassword = (e) => {
|
||||
this.setState({newPassword: e.target.value});
|
||||
}
|
||||
|
||||
updateConfirmPassword(e) {
|
||||
updateConfirmPassword = (e) => {
|
||||
this.setState({confirmPassword: e.target.value});
|
||||
}
|
||||
|
||||
deauthorizeApp(e) {
|
||||
deauthorizeApp = (e) => {
|
||||
e.preventDefault();
|
||||
const appId = e.currentTarget.getAttribute('data-app');
|
||||
deauthorizeOAuthApp(
|
||||
|
|
@ -183,10 +219,11 @@ export default class SecurityTab extends React.Component {
|
|||
},
|
||||
(err) => {
|
||||
this.setState({serverError: err.message});
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
createMfaSection() {
|
||||
createMfaSection = () => {
|
||||
let updateSectionStatus;
|
||||
let submit;
|
||||
|
||||
|
|
@ -321,7 +358,7 @@ export default class SecurityTab extends React.Component {
|
|||
);
|
||||
}
|
||||
|
||||
createPasswordSection() {
|
||||
createPasswordSection = () => {
|
||||
let updateSectionStatus;
|
||||
|
||||
if (this.props.activeSection === 'password') {
|
||||
|
|
@ -578,7 +615,7 @@ export default class SecurityTab extends React.Component {
|
|||
);
|
||||
}
|
||||
|
||||
createSignInSection() {
|
||||
createSignInSection = () => {
|
||||
let updateSectionStatus;
|
||||
const user = this.props.user;
|
||||
|
||||
|
|
@ -793,7 +830,7 @@ export default class SecurityTab extends React.Component {
|
|||
);
|
||||
}
|
||||
|
||||
createOAuthAppsSection() {
|
||||
createOAuthAppsSection = () => {
|
||||
let updateSectionStatus;
|
||||
|
||||
if (this.props.activeSection === 'apps') {
|
||||
|
|
@ -929,6 +966,368 @@ export default class SecurityTab extends React.Component {
|
|||
);
|
||||
}
|
||||
|
||||
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 name.')});
|
||||
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: (
|
||||
<FormattedMessage
|
||||
id='user.settings.tokens.confirmCreateTitle'
|
||||
defaultMessage='Create System Admin User Access Token'
|
||||
/>
|
||||
),
|
||||
confirmMessage: (
|
||||
<div className='alert alert-danger'>
|
||||
<FormattedHTMLMessage
|
||||
id='user.settings.tokens.confirmCreateMessage'
|
||||
defaultMessage='You are generating a user access token with System Admin permissions. Are you sure want to create this token?'
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
confirmButton: (
|
||||
<FormattedMessage
|
||||
id='user.settings.tokens.confirmCreateButton'
|
||||
defaultMessage='Yes, Create'
|
||||
/>
|
||||
),
|
||||
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: (
|
||||
<FormattedMessage
|
||||
id='user.settings.tokens.confirmDeleteTitle'
|
||||
defaultMessage='Delete {name} Token?'
|
||||
values={{
|
||||
name: token.description
|
||||
}}
|
||||
/>
|
||||
),
|
||||
confirmMessage: (
|
||||
<div className='alert alert-danger'>
|
||||
<FormattedHTMLMessage
|
||||
id='user.settings.tokens.confirmDeleteMessage'
|
||||
defaultMessage='Any integrations using this token will no longer be able to access the Mattermost API. You cannot undo this action. Are you sure want to delete this token?'
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
confirmButton: (
|
||||
<FormattedMessage
|
||||
id='user.settings.tokens.confirmDeleteButton'
|
||||
defaultMessage='Yes, Delete'
|
||||
/>
|
||||
),
|
||||
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;
|
||||
|
||||
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(
|
||||
<div
|
||||
key={token.id}
|
||||
className='setting-box__item'
|
||||
>
|
||||
<div className='whitespace--nowrap overflow--ellipsis'>
|
||||
<strong>{token.description}</strong>
|
||||
</div>
|
||||
<div className='setting-box__token-id whitespace--nowrap overflow--ellipsis'>
|
||||
<FormattedMessage
|
||||
id='user.settings.tokens.tokenId'
|
||||
defaultMessage='Token ID: '
|
||||
/>
|
||||
{token.id}
|
||||
</div>
|
||||
<div>
|
||||
<a
|
||||
name={token.id}
|
||||
href='#'
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
this.confirmRevokeToken(token.id);
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='user.settings.tokens.delete'
|
||||
defaultMessage='Delete'
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<hr className='margin-bottom margin-top x2'/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
if (tokenList.length === 0) {
|
||||
tokenList.push(
|
||||
<FormattedMessage
|
||||
key='notokens'
|
||||
id='user.settings.tokens.userAccessTokensNone'
|
||||
defaultMessage='No user access tokens.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
let extraInfo;
|
||||
|
||||
if (isMobile()) {
|
||||
extraInfo = (
|
||||
<span>
|
||||
<FormattedHTMLMessage
|
||||
id='user.settings.tokens.description_mobile'
|
||||
defaultMessage='<a href="https://about.mattermost.com/default-user-access-tokens" target="_blank">User access tokens</a> 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>. Create new tokens on your desktop.'
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
extraInfo = (
|
||||
<span>
|
||||
<FormattedHTMLMessage
|
||||
id='user.settings.tokens.description'
|
||||
defaultMessage='<a href="https://about.mattermost.com/default-user-access-tokens" target="_blank">User access tokens</a> 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>.'
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
let newTokenSection;
|
||||
if (this.state.tokenCreationState === TOKEN_CREATING) {
|
||||
newTokenSection = (
|
||||
<div className='padding-left x2'>
|
||||
<div className='row'>
|
||||
<label className='col-sm-auto control-label padding-right x2'>
|
||||
<FormattedMessage
|
||||
id='user.settings.tokens.name'
|
||||
defaultMessage='Name: '
|
||||
/>
|
||||
</label>
|
||||
<div className='col-sm-5'>
|
||||
<input
|
||||
ref='newtokendescription'
|
||||
className='form-control'
|
||||
type='text'
|
||||
maxLength={64}
|
||||
onKeyPress={this.saveTokenKeyPress}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className='padding-top x2'>
|
||||
<FormattedMessage
|
||||
id='user.settings.tokens.nameDescription'
|
||||
defaultMessage='Give a name for your token, so you remember what it’s used for. A token is generated after you hit "Save".'
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
id='clientError'
|
||||
className='has-error margin-top margin-bottom'
|
||||
>
|
||||
{this.state.tokenError}
|
||||
</label>
|
||||
</div>
|
||||
<button
|
||||
className='btn btn-primary'
|
||||
onClick={this.confirmCreateToken}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='user.settings.tokens.save'
|
||||
defaultMessage='Save'
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className='btn btn-default'
|
||||
onClick={this.stopCreatingToken}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='user.settings.tokens.cancel'
|
||||
defaultMessage='Cancel'
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (this.state.tokenCreationState === TOKEN_CREATED) {
|
||||
newTokenSection = (
|
||||
<div
|
||||
className='alert alert-warning'
|
||||
>
|
||||
<i className='fa fa-warning margin-right'/>
|
||||
<FormattedMessage
|
||||
id='user.settings.tokens.copy'
|
||||
defaultMessage="Please copy the token below. You won't be able to see it again!"
|
||||
/>
|
||||
<br/>
|
||||
<br/>
|
||||
<FormattedMessage
|
||||
id='user.settings.tokens.name'
|
||||
defaultMessage='Name: '
|
||||
/>
|
||||
{this.state.newToken.description}
|
||||
<br/>
|
||||
<FormattedMessage
|
||||
id='user.settings.tokens.id'
|
||||
defaultMessage='ID: '
|
||||
/>
|
||||
{this.state.newToken.id}
|
||||
<br/>
|
||||
<strong>
|
||||
<FormattedMessage
|
||||
id='user.settings.tokens.token'
|
||||
defaultMessage='Token: '
|
||||
/>
|
||||
{this.state.newToken.token}
|
||||
</strong>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
newTokenSection = (
|
||||
<a
|
||||
className='btn btn-primary'
|
||||
href='#'
|
||||
onClick={this.startCreatingToken}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='user.settings.tokens.create'
|
||||
defaultMessage='Create New Token'
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
const inputs = [];
|
||||
inputs.push(
|
||||
<div
|
||||
key='tokensSetting'
|
||||
className='padding-top'
|
||||
>
|
||||
<div key='tokenList'>
|
||||
<div className='alert alert-transparent'>
|
||||
{tokenList}
|
||||
</div>
|
||||
<br/>
|
||||
{newTokenSection}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
updateSectionStatus = function resetSection(e) {
|
||||
this.props.updateSection('');
|
||||
this.setState({newToken: null, tokenCreationState: TOKEN_NOT_CREATING, serverError: null, tokenError: ''});
|
||||
e.preventDefault();
|
||||
}.bind(this);
|
||||
|
||||
return (
|
||||
<SettingItemMax
|
||||
title={Utils.localizeMessage('user.settings.tokens.title', 'User Access Tokens')}
|
||||
inputs={inputs}
|
||||
extraInfo={extraInfo}
|
||||
infoPosition='top'
|
||||
server_error={this.state.serverError}
|
||||
updateSection={updateSectionStatus}
|
||||
width='full'
|
||||
cancelButtonText={
|
||||
<FormattedMessage
|
||||
id='user.settings.security.close'
|
||||
defaultMessage='Close'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const describe = Utils.localizeMessage('user.settings.tokens.clickToEdit', "Click 'Edit' to manage your user access tokens");
|
||||
|
||||
updateSectionStatus = function updateSection() {
|
||||
this.props.updateSection('tokens');
|
||||
}.bind(this);
|
||||
|
||||
return (
|
||||
<SettingItemMin
|
||||
title={Utils.localizeMessage('user.settings.tokens.title', 'User Access Tokens')}
|
||||
describe={describe}
|
||||
updateSection={updateSectionStatus}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const user = this.props.user;
|
||||
const config = window.mm_config;
|
||||
|
|
@ -959,6 +1358,11 @@ export default class SecurityTab extends React.Component {
|
|||
oauthSection = this.createOAuthAppsSection();
|
||||
}
|
||||
|
||||
let tokensSection;
|
||||
if (this.props.canUseAccessTokens) {
|
||||
tokensSection = this.createTokensSection();
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='modal-header'>
|
||||
|
|
@ -1001,6 +1405,8 @@ export default class SecurityTab extends React.Component {
|
|||
<div className='divider-light'/>
|
||||
{oauthSection}
|
||||
<div className='divider-light'/>
|
||||
{tokensSection}
|
||||
<div className='divider-light'/>
|
||||
{signInSection}
|
||||
<div className='divider-dark'/>
|
||||
<br/>
|
||||
|
|
@ -1014,7 +1420,7 @@ export default class SecurityTab extends React.Component {
|
|||
defaultMessage='View Access History'
|
||||
/>
|
||||
</ToggleModalButton>
|
||||
<b/>
|
||||
<br/>
|
||||
<ToggleModalButton
|
||||
className='security-links theme'
|
||||
dialogType={ActivityLogModal}
|
||||
|
|
@ -1026,6 +1432,14 @@ export default class SecurityTab extends React.Component {
|
|||
/>
|
||||
</ToggleModalButton>
|
||||
</div>
|
||||
<ConfirmModal
|
||||
title={this.state.confirmTitle}
|
||||
message={this.state.confirmMessage}
|
||||
confirmButtonText={this.state.confirmButton}
|
||||
show={this.state.showConfirmModal}
|
||||
onConfirm={this.state.confirmComplete || (() => {})} //eslint-disable-line no-empty-function
|
||||
onCancel={this.handleCancelConfirm}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,6 +745,10 @@
|
|||
"admin.select_team.close": "Close",
|
||||
"admin.select_team.select": "Select",
|
||||
"admin.select_team.selectTeam": "Select Team",
|
||||
"admin.service.userAccessTokensTitle": "Enable User Access Tokens: ",
|
||||
"admin.service.userAccessTokensDescription": "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.",
|
||||
"admin.service.userAccessTokensNameLabel": "Name: ",
|
||||
"admin.service.userAccessTokensIdLabel": "Token ID: ",
|
||||
"admin.service.attemptDescription": "Number of login attempts allowed before a user is locked out and required to reset their password via email.",
|
||||
"admin.service.attemptExample": "E.g.: \"10\"",
|
||||
"admin.service.attemptTitle": "Maximum Login Attempts:",
|
||||
|
|
@ -954,6 +958,26 @@
|
|||
"admin.team_analytics.activeUsers": "Active Users With Posts",
|
||||
"admin.team_analytics.totalPosts": "Total Posts",
|
||||
"admin.true": "true",
|
||||
"admin.manage_tokens.userAccessTokensNone": "No user access tokens.",
|
||||
"admin.manage_tokens.manageTokensTitle": "Manage User Access Tokens",
|
||||
"admin.manage_tokens.userAccessTokensDescription": "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>.",
|
||||
"admin.manage_roles.saveError": "Unable to save roles.",
|
||||
"admin.manage_roles.additionalRoles": "Select additional permissions for the account. <a href=\"https://about.mattermost.com/default-permissions\" target=\"_blank\">Read more about roles and permissions</a>.",
|
||||
"admin.manage_roles.postAllRoleTitle": "post:all",
|
||||
"admin.manage_roles.postAllRole": "Access to post to all Mattermost channels including direct messages.",
|
||||
"admin.manage_roles.postAllPublicRoleTitle": "post:channels",
|
||||
"admin.manage_roles.postAllPublicRole": "Access to post to all Mattermost public channels.",
|
||||
"admin.manage_roles.allowUserAccessTokens": "Allow this account to generate <a href=\"https://about.mattermost.com/default-user-access-tokens\" target=\"_blank\">user access tokens</a>.",
|
||||
"admin.manage_roles.systemAdmin": "System Admin",
|
||||
"admin.manage_roles.systemMember": "Member",
|
||||
"admin.manage_roles.manageRolesTitle": "Manage Roles",
|
||||
"admin.manage_roles.cancel": "Cancel",
|
||||
"admin.manage_roles.save": "Save",
|
||||
"admin.user_item.userAccessTokenNo": "<strong>User Access Tokens:</strong> No",
|
||||
"admin.user_item.userAccessTokenAdmin": "<strong>User Access Tokens:</strong> Yes (with system_admin)",
|
||||
"admin.user_item.userAccessTokenPostAll": "<strong>User Access Tokens:</strong> Yes (with post:all)",
|
||||
"admin.user_item.userAccessTokenPostAllPublic": "<strong>User Access Tokens:</strong> Yes (with post:channels)",
|
||||
"admin.user_item.userAccessTokenYes": "<strong>User Access Tokens:</strong> Yes",
|
||||
"admin.user_item.authServiceEmail": "<strong>Sign-in Method:</strong> Email",
|
||||
"admin.user_item.authServiceNotEmail": "<strong>Sign-in Method:</strong> {service}",
|
||||
"admin.user_item.confirmDemoteDescription": "If you demote yourself from the System Admin role and there is not another user with System Admin privileges, you'll need to re-assign a System Admin by accessing the Mattermost server through a terminal and running the following command.",
|
||||
|
|
@ -968,6 +992,8 @@
|
|||
"admin.user_item.makeSysAdmin": "Make System Admin",
|
||||
"admin.user_item.makeTeamAdmin": "Make Team Admin",
|
||||
"admin.user_item.manageTeams": "Manage Teams",
|
||||
"admin.user_item.manageRoles": "Manage Roles",
|
||||
"admin.user_item.manageTokens": "Manage Tokens",
|
||||
"admin.user_item.member": "Member",
|
||||
"admin.user_item.mfaNo": "<strong>MFA</strong>: No",
|
||||
"admin.user_item.mfaYes": "<strong>MFA</strong>: Yes",
|
||||
|
|
@ -2434,6 +2460,27 @@
|
|||
"user.settings.push_notification.send": "Send mobile push notifications",
|
||||
"user.settings.push_notification.status": "Trigger push notifications when",
|
||||
"user.settings.push_notification.status_info": "Notification alerts are only pushed to your mobile device when your online status matches the selection above.",
|
||||
"user.settings.tokens.confirmCreateTitle": "Create System Admin User Access Token",
|
||||
"user.settings.tokens.confirmCreateMessage": "You are generating a user access token with System Admin permissions. Are you sure want to create this token?",
|
||||
"user.settings.tokens.confirmCreateButton": "Yes, Create",
|
||||
"user.settings.tokens.confirmDeleteTitle": "Delete {name} Token?",
|
||||
"user.settings.tokens.confirmDeleteMessage": "Any integrations using this token will no longer be able to access the Mattermost API. You cannot undo this action. Are you sure want to delete this token?",
|
||||
"user.settings.tokens.confirmDeleteButton": "Yes, Delete",
|
||||
"user.settings.tokens.tokenId": "Token ID: ",
|
||||
"user.settings.tokens.delete": "Delete",
|
||||
"user.settings.tokens.userAccessTokensNone": "No user access tokens.",
|
||||
"user.settings.tokens.description": "<a href=\"https://about.mattermost.com/default-user-access-tokens\" target=\"_blank\">User access tokens</a> 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>.",
|
||||
"user.settings.tokens.description_mobile": "<a href=\"https://about.mattermost.com/default-user-access-tokens\" target=\"_blank\">User access tokens</a> 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>. Create new tokens on your desktop.",
|
||||
"user.settings.tokens.name": "Name: ",
|
||||
"user.settings.tokens.nameDescription": "Give a name for your token, so you remember what it’s used for. A token is generated after you hit \"Save\".",
|
||||
"user.settings.tokens.save": "Save",
|
||||
"user.settings.tokens.cancel": "Cancel",
|
||||
"user.settings.tokens.id": "ID: ",
|
||||
"user.settings.tokens.token": "Token: ",
|
||||
"user.settings.tokens.copy": "Please copy the token below. You won't be able to see it again!",
|
||||
"user.settings.tokens.create": "Create New Token",
|
||||
"user.settings.tokens.title": "User Access Tokens",
|
||||
"user.settings.tokens.clickToEdit": "Click 'Edit' to manage your user access tokens",
|
||||
"user.settings.security.active": "Active",
|
||||
"user.settings.security.close": "Close",
|
||||
"user.settings.security.currentPassword": "Current Password",
|
||||
|
|
|
|||
|
|
@ -11,3 +11,4 @@
|
|||
margin: 1px 0 0 10px;
|
||||
padding: 4px 10px;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -87,3 +87,9 @@
|
|||
.delete-message-text {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.col-sm-auto {
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -91,35 +91,3 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.padding-top {
|
||||
padding-top: 7px;
|
||||
|
||||
&.x2 {
|
||||
padding-top: 14px;
|
||||
}
|
||||
|
||||
&.x3 {
|
||||
padding-top: 21px;
|
||||
}
|
||||
}
|
||||
|
||||
.padding-bottom {
|
||||
padding-bottom: 7px;
|
||||
|
||||
&.x2 {
|
||||
padding-bottom: 14px;
|
||||
}
|
||||
|
||||
&.x3 {
|
||||
padding-bottom: 21px;
|
||||
}
|
||||
|
||||
.control-label {
|
||||
font-weight: 600;
|
||||
|
||||
&.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -212,12 +212,14 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.post {
|
||||
.attachment {
|
||||
.attachment__image {
|
||||
&.attachment__image--openraph {
|
||||
max-height: 70px;
|
||||
max-width: 300px;
|
||||
|
||||
&.loading {
|
||||
height: 70px;
|
||||
}
|
||||
|
|
@ -229,6 +231,10 @@
|
|||
|
||||
// Tablet and desktop
|
||||
@media screen and (min-width: 768px) {
|
||||
.col-sm-auto {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.second-bar {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@
|
|||
|
||||
.log__panel {
|
||||
background-color: white;
|
||||
border: 1px solid #ddd;
|
||||
border: $border-gray;
|
||||
height: calc(100vh - 200px);
|
||||
margin-top: 10px;
|
||||
overflow: scroll;
|
||||
|
|
@ -180,7 +180,7 @@
|
|||
|
||||
.banner {
|
||||
background: $white;
|
||||
border: 1px solid #ddd;
|
||||
border: $border-gray;
|
||||
font-size: .95em;
|
||||
margin: 2em 0;
|
||||
padding: .8em 1.5rem;
|
||||
|
|
@ -535,11 +535,34 @@
|
|||
.manage-teams {
|
||||
.manage-teams__user {
|
||||
align-items: center;
|
||||
border-bottom-color: lightgray;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
display: flex;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.manage-teams__teams {
|
||||
border-top: $border-gray;
|
||||
margin: 1em 0 .3em;
|
||||
|
||||
.btn-link {
|
||||
&.danger {
|
||||
color: #c55151;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.member-row--padded {
|
||||
padding-left: 20px;
|
||||
|
||||
strong {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.manage-row--inner {
|
||||
padding: 15px 0 4px;
|
||||
|
||||
& + div {
|
||||
border-top: $border-gray;
|
||||
}
|
||||
}
|
||||
|
||||
.manage-teams__profile-picture {
|
||||
|
|
@ -573,21 +596,31 @@
|
|||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.manage-teams__team {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
padding: 10px;
|
||||
.manage-teams__teams {
|
||||
margin-top: 1em;
|
||||
|
||||
.manage-row__empty {
|
||||
padding: 9px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.manage-teams__team + .manage-teams__team {
|
||||
border-top-color: lightgray;
|
||||
border-top-style: solid;
|
||||
border-top-width: 1px;
|
||||
.manage-teams__team {
|
||||
align-items: center;
|
||||
border-bottom: $border-gray;
|
||||
display: flex;
|
||||
padding: 7px 10px;
|
||||
|
||||
.btn {
|
||||
font-size: .9em;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
padding: 6px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.manage-teams__team-name {
|
||||
flex: 1;
|
||||
font-weight: bold;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -360,6 +360,22 @@
|
|||
padding: 0;
|
||||
}
|
||||
|
||||
.setting-box__item {
|
||||
&:first-child {
|
||||
padding-top: 3px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
hr {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.setting-box__token-id {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.setting-list__hint {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +1,105 @@
|
|||
@charset 'UTF-8';
|
||||
|
||||
.margin--right {
|
||||
margin-right: 5px;
|
||||
.padding-top {
|
||||
padding-top: 7px;
|
||||
|
||||
&.x2 {
|
||||
margin-right: 10px;
|
||||
padding-top: 14px;
|
||||
}
|
||||
|
||||
&.x3 {
|
||||
padding-top: 21px;
|
||||
}
|
||||
}
|
||||
|
||||
.margin--left {
|
||||
margin-left: 5px;
|
||||
.padding-bottom {
|
||||
padding-bottom: 7px;
|
||||
|
||||
&.x2 {
|
||||
margin-left: 10px;
|
||||
padding-bottom: 14px;
|
||||
}
|
||||
|
||||
&.x3 {
|
||||
padding-bottom: 21px;
|
||||
}
|
||||
|
||||
.control-label {
|
||||
font-weight: 600;
|
||||
|
||||
&.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.padding--right {
|
||||
padding-right: 5px;
|
||||
.padding-left {
|
||||
padding-left: 7px;
|
||||
|
||||
&.x2 {
|
||||
padding-right: 10px;
|
||||
padding-left: 14px;
|
||||
}
|
||||
|
||||
&.x3 {
|
||||
padding-left: 21px;
|
||||
}
|
||||
}
|
||||
|
||||
.padding--left {
|
||||
padding-left: 5px;
|
||||
.padding-right {
|
||||
padding-right: 7px;
|
||||
|
||||
&.x2 {
|
||||
padding-left: 10px;
|
||||
padding-right: 14px;
|
||||
}
|
||||
|
||||
&.x3 {
|
||||
padding-right: 21px;
|
||||
}
|
||||
}
|
||||
|
||||
.margin-right {
|
||||
margin-right: 7px;
|
||||
|
||||
&.x2 {
|
||||
margin-right: 14px;
|
||||
}
|
||||
|
||||
&.x3 {
|
||||
margin-right: 21px;
|
||||
}
|
||||
}
|
||||
|
||||
.margin-left {
|
||||
margin-left: 7px;
|
||||
|
||||
&.x2 {
|
||||
margin-left: 14px;
|
||||
}
|
||||
|
||||
&.x3 {
|
||||
margin-left: 21px;
|
||||
}
|
||||
}
|
||||
|
||||
.margin-top {
|
||||
margin-top: 7px;
|
||||
|
||||
&.x2 {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
&.x3 {
|
||||
margin-top: 21px;
|
||||
}
|
||||
}
|
||||
|
||||
.margin-bottom {
|
||||
margin-bottom: 7px;
|
||||
|
||||
&.x2 {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
&.x3 {
|
||||
margin-bottom: 21px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -579,7 +579,7 @@ export function applyTheme(theme) {
|
|||
if (theme.centerChannelBg) {
|
||||
changeCss('@media(min-width: 768px){.app__body .post:hover .post__header .col__reply, .app__body .post.post--hovered .post__header .col__reply', 'background:' + theme.centerChannelBg);
|
||||
changeCss('@media(max-width: 320px){.tutorial-steps__container', 'background:' + theme.centerChannelBg);
|
||||
changeCss('.app__body .status-wrapper .status_dropdown__toggle .status .icon__container:after, .app__body .app__content, .app__body .markdown__table, .app__body .markdown__table tbody tr, .app__body .suggestion-list__content, .app__body .modal .modal-content, .app__body .modal .modal-footer, .app__body .post.post--compact .post-image__column, .app__body .suggestion-list__divider > span, .app__body .status-wrapper .status', 'background:' + theme.centerChannelBg);
|
||||
changeCss('.app__body .status-wrapper .status_dropdown__toggle .status .icon__container:after, .app__body .app__content, .app__body .markdown__table, .app__body .markdown__table tbody tr, .app__body .suggestion-list__content, .app__body .modal .modal-content, .app__body .modal .modal-footer, .app__body .post.post--compact .post-image__column, .app__body .suggestion-list__divider > span, .app__body .status-wrapper .status, .app__body .alert.alert-transparent', 'background:' + theme.centerChannelBg);
|
||||
changeCss('#post-list .post-list-holder-by-time, .app__body .post .dropdown-menu a', 'background:' + theme.centerChannelBg);
|
||||
changeCss('#post-create', 'background:' + theme.centerChannelBg);
|
||||
changeCss('.app__body .date-separator .separator__text, .app__body .new-separator .separator__text', 'background:' + theme.centerChannelBg);
|
||||
|
|
@ -601,7 +601,7 @@ export function applyTheme(theme) {
|
|||
|
||||
if (theme.centerChannelColor) {
|
||||
changeCss('.app__body .mentions__name .status.status--group, .app__body .multi-select__note', 'background:' + changeOpacity(theme.centerChannelColor, 0.12));
|
||||
changeCss('.app__body .channel-header .channel-header__icon, .app__body .search-bar__container .search__form', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.12));
|
||||
changeCss('.app__body .alert.alert-transparent, .app__body .channel-header .channel-header__icon, .app__body .search-bar__container .search__form', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.12));
|
||||
changeCss('.app__body .post-list__arrows, .app__body .post .flag-icon__container', 'fill:' + changeOpacity(theme.centerChannelColor, 0.3));
|
||||
changeCss('@media(min-width: 768px){.app__body .search__icon svg', 'stroke:' + changeOpacity(theme.centerChannelColor, 0.4));
|
||||
changeCss('.app__body .channel-header__icon svg', 'fill:' + changeOpacity(theme.centerChannelColor, 0.4));
|
||||
|
|
|
|||
|
|
@ -5004,7 +5004,7 @@ math-expression-evaluator@^1.2.14:
|
|||
|
||||
mattermost-redux@mattermost/mattermost-redux#master:
|
||||
version "0.0.1"
|
||||
resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/9797cb8bd8fa61252336a7c6150bd364f7ca28b1"
|
||||
resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/d3a8c94d59a687a957ca8808fbe1b9cb76077bce"
|
||||
dependencies:
|
||||
deep-equal "1.0.1"
|
||||
harmony-reflect "1.5.1"
|
||||
|
|
|
|||
Loading…
Reference in a new issue