diff --git a/api4/post_test.go b/api4/post_test.go
index 53babc6e6fe..f136ba676f0 100644
--- a/api4/post_test.go
+++ b/api4/post_test.go
@@ -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()
diff --git a/app/authorization.go b/app/authorization.go
index 9fc2edfb91e..28f968f682d 100644
--- a/app/authorization.go
+++ b/app/authorization.go
@@ -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)
}
diff --git a/app/diagnostics.go b/app/diagnostics.go
index 54fe843ac02..603ceb8a5e7 100644
--- a/app/diagnostics.go
+++ b/app/diagnostics.go
@@ -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,
diff --git a/i18n/en.json b/i18n/en.json
index c4940295b79..3287af260a9 100644
--- a/i18n/en.json
+++ b/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"
diff --git a/model/authorization.go b/model/authorization.go
index cf7e2b4818c..d413e294c2e 100644
--- a/model/authorization.go
+++ b/model/authorization.go
@@ -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",
diff --git a/store/sql_upgrade.go b/store/sql_upgrade.go
index a7b72124e3f..157a85507ee 100644
--- a/store/sql_upgrade.go
+++ b/store/sql_upgrade.go
@@ -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)
// }
diff --git a/store/sql_user_store.go b/store/sql_user_store.go
index ab031ea19b2..64079c8d35d 100644
--- a/store/sql_user_store.go
+++ b/store/sql_user_store.go
@@ -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)
diff --git a/webapp/components/admin_console/custom_integrations_settings.jsx b/webapp/components/admin_console/custom_integrations_settings.jsx
index 18fdd22fd85..3b5c511718c 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}
/>
+
+ }
+ helpText={
+
+ }
+ value={this.state.enableUserAccessTokens}
+ onChange={this.handleChange}
+ />
);
}
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 00000000000..1ca243621f8
--- /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 00000000000..2358f0241ff
--- /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(
+
+ );
+ }
+ }
+
+ renderContents = () => {
+ const {user} = this.props;
+
+ if (user == null) {
+ return
;
+ }
+
+ let name = UserUtils.getFullName(user);
+ if (name) {
+ name += ` (@${user.username})`;
+ } else {
+ name = `@${user.username}`;
+ }
+
+ let additionalRoles;
+ if (this.state.hasUserAccessTokenRole || this.state.isSystemAdmin) {
+ additionalRoles = (
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+ let userAccessTokenContent;
+ if (this.props.userAccessTokensEnabled) {
+ userAccessTokenContent = (
+
+
+
+
+
+ {additionalRoles}
+
+
+ );
+ }
+
+ return (
+
+
+

+
+
+ {name}
+
+
+ {user.email}
+
+
+
+
+
+ {userAccessTokenContent}
+
+
+ );
+ }
+
+ render() {
+ return (
+
+
+
+
+
+
+
+ {this.renderContents()}
+ {this.state.error}
+
+
+
+
+
+
+ );
+ }
+}
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 a579ab03c44..21f9d762d32 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 28e9fde8f35..69579d46fd4 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 (