diff --git a/api/post.go b/api/post.go
index bbdce78e8d..0e3ad2aa54 100644
--- a/api/post.go
+++ b/api/post.go
@@ -91,6 +91,16 @@ func createPost(c *Context, w http.ResponseWriter, r *http.Request) {
}
func updatePost(c *Context, w http.ResponseWriter, r *http.Request) {
+
+ if utils.IsLicensed {
+ if *utils.Cfg.ServiceSettings.AllowEditPost == model.ALLOW_EDIT_POST_NEVER {
+ c.Err = model.NewLocAppError("updatePost", "api.post.update_post.permissions.app_error", nil,
+ c.T("api.post.update_post.permissions_denied.app_error"))
+ c.Err.StatusCode = http.StatusForbidden
+ return
+ }
+ }
+
post := model.PostFromJson(r.Body)
if post == nil {
@@ -135,6 +145,15 @@ func updatePost(c *Context, w http.ResponseWriter, r *http.Request) {
c.Err.StatusCode = http.StatusForbidden
return
}
+
+ if utils.IsLicensed {
+ if *utils.Cfg.ServiceSettings.AllowEditPost == model.ALLOW_EDIT_POST_TIME_LIMIT && model.GetMillis() > oldPost.CreateAt+int64(*utils.Cfg.ServiceSettings.PostEditTimeLimit*1000) {
+ c.Err = model.NewLocAppError("updatePost", "api.post.update_post.permissions.app_error", nil,
+ c.T("api.post.update_post.permissions_time_limit.app_error", map[string]interface{}{"timeLimit": *utils.Cfg.ServiceSettings.PostEditTimeLimit}))
+ c.Err.StatusCode = http.StatusForbidden
+ return
+ }
+ }
}
newPost := &model.Post{}
@@ -402,7 +421,7 @@ func deletePost(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if !HasPermissionToChannelContext(c, channelId, model.PERMISSION_EDIT_POST) {
+ if !HasPermissionToChannelContext(c, channelId, model.PERMISSION_DELETE_POST) {
return
}
@@ -426,7 +445,7 @@ func deletePost(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if post.UserId != c.Session.UserId && !HasPermissionToChannelContext(c, post.ChannelId, model.PERMISSION_EDIT_OTHERS_POSTS) {
+ if post.UserId != c.Session.UserId && !HasPermissionToChannelContext(c, post.ChannelId, model.PERMISSION_DELETE_OTHERS_POSTS) {
c.Err = model.NewLocAppError("deletePost", "api.post.delete_post.permissions.app_error", nil, "")
c.Err.StatusCode = http.StatusForbidden
return
diff --git a/api/post_test.go b/api/post_test.go
index 151387953a..4d3ee80b50 100644
--- a/api/post_test.go
+++ b/api/post_test.go
@@ -315,6 +315,15 @@ func TestUpdatePost(t *testing.T) {
Client := th.BasicClient
channel1 := th.BasicChannel
+ allowEditPost := *utils.Cfg.ServiceSettings.AllowEditPost
+ postEditTimeLimit := *utils.Cfg.ServiceSettings.PostEditTimeLimit
+ defer func() {
+ *utils.Cfg.ServiceSettings.AllowEditPost = allowEditPost
+ *utils.Cfg.ServiceSettings.PostEditTimeLimit = postEditTimeLimit
+ }()
+
+ *utils.Cfg.ServiceSettings.AllowEditPost = model.ALLOW_EDIT_POST_ALWAYS
+
post1 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
rpost1, err := Client.CreatePost(post1)
if err != nil {
@@ -377,6 +386,56 @@ func TestUpdatePost(t *testing.T) {
if _, err := Client.UpdatePost(up3); err == nil {
t.Fatal("shouldn't have been able to update system message")
}
+
+ // Test licensed policy controls for edit post
+ isLicensed := utils.IsLicensed
+ license := utils.License
+ defer func() {
+ utils.IsLicensed = isLicensed
+ utils.License = license
+ }()
+ utils.IsLicensed = true
+ utils.License = &model.License{Features: &model.Features{}}
+ utils.License.Features.SetDefaults()
+
+ *utils.Cfg.ServiceSettings.AllowEditPost = model.ALLOW_EDIT_POST_NEVER
+
+ post4 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a", RootId: rpost1.Data.(*model.Post).Id}
+ rpost4, err := Client.CreatePost(post4)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ up4 := &model.Post{Id: rpost4.Data.(*model.Post).Id, ChannelId: channel1.Id, Message: "a" + model.NewId() + " update post 4"}
+ if _, err := Client.UpdatePost(up4); err == nil {
+ t.Fatal("shouldn't have been able to update a message when not allowed")
+ }
+
+ *utils.Cfg.ServiceSettings.AllowEditPost = model.ALLOW_EDIT_POST_TIME_LIMIT
+ *utils.Cfg.ServiceSettings.PostEditTimeLimit = 1 //seconds
+
+ post5 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a", RootId: rpost1.Data.(*model.Post).Id}
+ rpost5, err := Client.CreatePost(post5)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ msg5 := "a" + model.NewId() + " update post 5"
+ up5 := &model.Post{Id: rpost5.Data.(*model.Post).Id, ChannelId: channel1.Id, Message: msg5}
+ if rup5, err := Client.UpdatePost(up5); err != nil {
+ t.Fatal(err)
+ } else {
+ if rup5.Data.(*model.Post).Message != up5.Message {
+ t.Fatal("failed to updates")
+ }
+ }
+
+ time.Sleep(1000 * time.Millisecond)
+
+ up6 := &model.Post{Id: rpost5.Data.(*model.Post).Id, ChannelId: channel1.Id, Message: "a" + model.NewId() + " update post 5"}
+ if _, err := Client.UpdatePost(up6); err == nil {
+ t.Fatal("shouldn't have been able to update a message after time limit")
+ }
}
func TestGetPosts(t *testing.T) {
@@ -805,10 +864,18 @@ func TestGetPostsCache(t *testing.T) {
}
func TestDeletePosts(t *testing.T) {
- th := Setup().InitBasic()
+ th := Setup().InitBasic().InitSystemAdmin()
Client := th.BasicClient
channel1 := th.BasicChannel
- UpdateUserToTeamAdmin(th.BasicUser2, th.BasicTeam)
+ team1 := th.BasicTeam
+
+ restrictPostDelete := *utils.Cfg.ServiceSettings.RestrictPostDelete
+ defer func() {
+ *utils.Cfg.ServiceSettings.RestrictPostDelete = restrictPostDelete
+ utils.SetDefaultRolesBasedOnConfig()
+ }()
+ *utils.Cfg.ServiceSettings.RestrictPostDelete = model.PERMISSIONS_DELETE_POST_ALL
+ utils.SetDefaultRolesBasedOnConfig()
time.Sleep(10 * time.Millisecond)
post1 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
@@ -840,17 +907,93 @@ func TestDeletePosts(t *testing.T) {
r2 := Client.Must(Client.GetPosts(channel1.Id, 0, 10, "")).Data.(*model.PostList)
if len(r2.Posts) != 5 {
- t.Fatal("should have returned 4 items")
+ t.Fatal("should have returned 5 items")
}
time.Sleep(10 * time.Millisecond)
- post4 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
- post4 = Client.Must(Client.CreatePost(post4)).Data.(*model.Post)
+ post4a := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
+ post4a = Client.Must(Client.CreatePost(post4a)).Data.(*model.Post)
+
+ time.Sleep(10 * time.Millisecond)
+ post4b := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
+ post4b = Client.Must(Client.CreatePost(post4b)).Data.(*model.Post)
+
+ SystemAdminClient := th.SystemAdminClient
+ LinkUserToTeam(th.SystemAdminUser, th.BasicTeam)
+ SystemAdminClient.Must(SystemAdminClient.JoinChannel(channel1.Id))
th.LoginBasic2()
Client.Must(Client.JoinChannel(channel1.Id))
- Client.Must(Client.DeletePost(channel1.Id, post4.Id))
+ if _, err := Client.DeletePost(channel1.Id, post4a.Id); err == nil {
+ t.Fatal(err)
+ }
+
+ // Test licensed policy controls for delete post
+ isLicensed := utils.IsLicensed
+ license := utils.License
+ defer func() {
+ utils.IsLicensed = isLicensed
+ utils.License = license
+ }()
+ utils.IsLicensed = true
+ utils.License = &model.License{Features: &model.Features{}}
+ utils.License.Features.SetDefaults()
+
+ UpdateUserToTeamAdmin(th.BasicUser2, th.BasicTeam)
+
+ Client.Logout()
+ th.LoginBasic2()
+ Client.SetTeamId(team1.Id)
+
+ Client.Must(Client.DeletePost(channel1.Id, post4a.Id))
+
+ SystemAdminClient.Must(SystemAdminClient.DeletePost(channel1.Id, post4b.Id))
+
+ *utils.Cfg.ServiceSettings.RestrictPostDelete = model.PERMISSIONS_DELETE_POST_TEAM_ADMIN
+ utils.SetDefaultRolesBasedOnConfig()
+
+ th.LoginBasic()
+
+ time.Sleep(10 * time.Millisecond)
+ post5a := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
+ post5a = Client.Must(Client.CreatePost(post5a)).Data.(*model.Post)
+
+ time.Sleep(10 * time.Millisecond)
+ post5b := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
+ post5b = Client.Must(Client.CreatePost(post5b)).Data.(*model.Post)
+
+ if _, err := Client.DeletePost(channel1.Id, post5a.Id); err == nil {
+ t.Fatal(err)
+ }
+
+ th.LoginBasic2()
+
+ Client.Must(Client.DeletePost(channel1.Id, post5a.Id))
+
+ SystemAdminClient.Must(SystemAdminClient.DeletePost(channel1.Id, post5b.Id))
+
+ *utils.Cfg.ServiceSettings.RestrictPostDelete = model.PERMISSIONS_DELETE_POST_SYSTEM_ADMIN
+ utils.SetDefaultRolesBasedOnConfig()
+
+ th.LoginBasic()
+
+ time.Sleep(10 * time.Millisecond)
+ post6a := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
+ post6a = Client.Must(Client.CreatePost(post6a)).Data.(*model.Post)
+
+ if _, err := Client.DeletePost(channel1.Id, post6a.Id); err == nil {
+ t.Fatal(err)
+ }
+
+ th.LoginBasic2()
+
+ if _, err := Client.DeletePost(channel1.Id, post6a.Id); err == nil {
+ t.Fatal(err)
+ }
+
+ SystemAdminClient.Must(SystemAdminClient.DeletePost(channel1.Id, post6a.Id))
+
}
func TestEmailMention(t *testing.T) {
diff --git a/config/config.json b/config/config.json
index 971e778379..f538e96865 100644
--- a/config/config.json
+++ b/config/config.json
@@ -35,7 +35,10 @@
"WebsocketPort": 80,
"WebserverMode": "gzip",
"EnableCustomEmoji": false,
- "RestrictCustomEmojiCreation": "all"
+ "RestrictCustomEmojiCreation": "all",
+ "RestrictPostDelete": "all",
+ "AllowEditPost": "always",
+ "PostEditTimeLimit": 300
},
"TeamSettings": {
"SiteName": "Mattermost",
diff --git a/i18n/en.json b/i18n/en.json
index 84306f0ce0..ac08329398 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -1607,6 +1607,14 @@
"id": "api.post.update_post.permissions_details.app_error",
"translation": "Already deleted id={{.PostId}}"
},
+ {
+ "id": "api.post.update_post.permissions_denied.app_error",
+ "translation": "Post edit has been disabled. Please ask your systems administrator for details."
+ },
+ {
+ "id": "api.post.update_post.permissions_time_limit.app_error",
+ "translation": "Post edit is only allowed for {{.timeLimit}} seconds. Please ask your systems administrator for details."
+ },
{
"id": "api.post.update_post.system_message.app_error",
"translation": "Unable to update system message"
diff --git a/model/authorization.go b/model/authorization.go
index 58fed5854f..78d7d3960b 100644
--- a/model/authorization.go
+++ b/model/authorization.go
@@ -47,6 +47,8 @@ var PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH *Permission
var PERMISSION_CREATE_POST *Permission
var PERMISSION_EDIT_POST *Permission
var PERMISSION_EDIT_OTHERS_POSTS *Permission
+var PERMISSION_DELETE_POST *Permission
+var PERMISSION_DELETE_OTHERS_POSTS *Permission
var PERMISSION_REMOVE_USER_FROM_TEAM *Permission
var PERMISSION_MANAGE_TEAM *Permission
var PERMISSION_IMPORT_TEAM *Permission
@@ -229,6 +231,16 @@ func InitalizePermissions() {
"authentication.permissions.edit_others_posts.name",
"authentication.permissions.edit_others_posts.description",
}
+ PERMISSION_DELETE_POST = &Permission{
+ "delete_post",
+ "authentication.permissions.delete_post.name",
+ "authentication.permissions.delete_post.description",
+ }
+ PERMISSION_DELETE_OTHERS_POSTS = &Permission{
+ "delete_others_posts",
+ "authentication.permissions.delete_others_posts.name",
+ "authentication.permissions.delete_others_posts.description",
+ }
PERMISSION_REMOVE_USER_FROM_TEAM = &Permission{
"remove_user_from_team",
"authentication.permissions.remove_user_from_team.name",
@@ -349,6 +361,8 @@ func InitalizeRoles() {
PERMISSION_EDIT_OTHER_USERS.Id,
PERMISSION_MANAGE_OAUTH.Id,
PERMISSION_INVITE_USER.Id,
+ PERMISSION_DELETE_POST.Id,
+ PERMISSION_DELETE_OTHERS_POSTS.Id,
},
ROLE_TEAM_USER.Permissions...,
),
diff --git a/model/config.go b/model/config.go
index 0134e1a346..13e795170e 100644
--- a/model/config.go
+++ b/model/config.go
@@ -49,6 +49,14 @@ const (
RESTRICT_EMOJI_CREATION_ADMIN = "admin"
RESTRICT_EMOJI_CREATION_SYSTEM_ADMIN = "system_admin"
+ PERMISSIONS_DELETE_POST_ALL = "all"
+ PERMISSIONS_DELETE_POST_TEAM_ADMIN = "team_admin"
+ PERMISSIONS_DELETE_POST_SYSTEM_ADMIN = "system_admin"
+
+ ALLOW_EDIT_POST_ALWAYS = "always"
+ ALLOW_EDIT_POST_NEVER = "never"
+ ALLOW_EDIT_POST_TIME_LIMIT = "time_limit"
+
EMAIL_BATCHING_BUFFER_SIZE = 256
EMAIL_BATCHING_INTERVAL = 30
@@ -92,6 +100,9 @@ type ServiceSettings struct {
WebserverMode *string
EnableCustomEmoji *bool
RestrictCustomEmojiCreation *string
+ RestrictPostDelete *string
+ AllowEditPost *string
+ PostEditTimeLimit *int
}
type ClusterSettings struct {
@@ -827,6 +838,21 @@ func (o *Config) SetDefaults() {
*o.ServiceSettings.RestrictCustomEmojiCreation = RESTRICT_EMOJI_CREATION_ALL
}
+ if o.ServiceSettings.RestrictPostDelete == nil {
+ o.ServiceSettings.RestrictPostDelete = new(string)
+ *o.ServiceSettings.RestrictPostDelete = PERMISSIONS_DELETE_POST_ALL
+ }
+
+ if o.ServiceSettings.AllowEditPost == nil {
+ o.ServiceSettings.AllowEditPost = new(string)
+ *o.ServiceSettings.AllowEditPost = ALLOW_EDIT_POST_TIME_LIMIT
+ }
+
+ if o.ServiceSettings.PostEditTimeLimit == nil {
+ o.ServiceSettings.PostEditTimeLimit = new(int)
+ *o.ServiceSettings.PostEditTimeLimit = 300
+ }
+
if o.ClusterSettings.InterNodeListenAddress == nil {
o.ClusterSettings.InterNodeListenAddress = new(string)
*o.ClusterSettings.InterNodeListenAddress = ":8075"
diff --git a/utils/authorization.go b/utils/authorization.go
index 5338084676..ba47681400 100644
--- a/utils/authorization.go
+++ b/utils/authorization.go
@@ -148,4 +148,26 @@ func SetDefaultRolesBasedOnConfig() {
model.PERMISSION_INVITE_USER.Id,
)
}
+
+ switch *Cfg.ServiceSettings.RestrictPostDelete {
+ case model.PERMISSIONS_DELETE_POST_ALL:
+ model.ROLE_CHANNEL_USER.Permissions = append(
+ model.ROLE_CHANNEL_USER.Permissions,
+ model.PERMISSION_DELETE_POST.Id,
+ )
+ model.ROLE_TEAM_ADMIN.Permissions = append(
+ model.ROLE_TEAM_ADMIN.Permissions,
+ model.PERMISSION_DELETE_POST.Id,
+ model.PERMISSION_DELETE_OTHERS_POSTS.Id,
+ )
+ break
+ case model.PERMISSIONS_DELETE_POST_TEAM_ADMIN:
+ model.ROLE_TEAM_ADMIN.Permissions = append(
+ model.ROLE_TEAM_ADMIN.Permissions,
+ model.PERMISSION_DELETE_POST.Id,
+ model.PERMISSION_DELETE_OTHERS_POSTS.Id,
+ )
+ break
+ }
+
}
diff --git a/utils/config.go b/utils/config.go
index da070012ed..3825d397d9 100644
--- a/utils/config.go
+++ b/utils/config.go
@@ -267,6 +267,9 @@ func getClientConfig(c *model.Config) map[string]string {
props["EnableTesting"] = strconv.FormatBool(c.ServiceSettings.EnableTesting)
props["EnableDeveloper"] = strconv.FormatBool(*c.ServiceSettings.EnableDeveloper)
props["EnableDiagnostics"] = strconv.FormatBool(*c.LogSettings.EnableDiagnostics)
+ props["RestrictPostDelete"] = *c.ServiceSettings.RestrictPostDelete
+ props["AllowEditPost"] = *c.ServiceSettings.AllowEditPost
+ props["PostEditTimeLimit"] = fmt.Sprintf("%v", *c.ServiceSettings.PostEditTimeLimit)
props["SendEmailNotifications"] = strconv.FormatBool(c.EmailSettings.SendEmailNotifications)
props["SendPushNotifications"] = strconv.FormatBool(*c.EmailSettings.SendPushNotifications)
diff --git a/utils/diagnostic.go b/utils/diagnostic.go
index 7509ccbb56..525dfd7947 100644
--- a/utils/diagnostic.go
+++ b/utils/diagnostic.go
@@ -82,6 +82,9 @@ func trackConfig() {
"restrict_custom_emoji_creation": *Cfg.ServiceSettings.RestrictCustomEmojiCreation,
"enable_testing": Cfg.ServiceSettings.EnableTesting,
"enable_developer": *Cfg.ServiceSettings.EnableDeveloper,
+ "restrict_post_delete": *Cfg.ServiceSettings.RestrictPostDelete,
+ "allow_edit_post": *Cfg.ServiceSettings.AllowEditPost,
+ "post_edit_time_limit": *Cfg.ServiceSettings.PostEditTimeLimit,
})
SendDiagnostic(TRACK_CONFIG_TEAM, map[string]interface{}{
diff --git a/webapp/components/admin_console/policy_settings.jsx b/webapp/components/admin_console/policy_settings.jsx
index 0e224af731..391726a932 100644
--- a/webapp/components/admin_console/policy_settings.jsx
+++ b/webapp/components/admin_console/policy_settings.jsx
@@ -6,6 +6,8 @@ import React from 'react';
import AdminSettings from './admin_settings.jsx';
import SettingsGroup from './settings_group.jsx';
import DropdownSetting from './dropdown_setting.jsx';
+import RadioSetting from './radio_setting.jsx';
+import PostEditSetting from './post_edit_setting.jsx';
import Constants from 'utils/constants.jsx';
import * as Utils from 'utils/utils.jsx';
@@ -22,6 +24,9 @@ export default class PolicySettings extends AdminSettings {
}
getConfigFromState(config) {
+ config.ServiceSettings.RestrictPostDelete = this.state.restrictPostDelete;
+ config.ServiceSettings.AllowEditPost = this.state.allowEditPost;
+ config.ServiceSettings.PostEditTimeLimit = this.parseIntNonZero(this.state.postEditTimeLimit, Constants.DEFAULT_POST_EDIT_TIME_LIMIT);
config.TeamSettings.RestrictTeamInvite = this.state.restrictTeamInvite;
config.TeamSettings.RestrictPublicChannelCreation = this.state.restrictPublicChannelCreation;
config.TeamSettings.RestrictPrivateChannelCreation = this.state.restrictPrivateChannelCreation;
@@ -35,6 +40,9 @@ export default class PolicySettings extends AdminSettings {
getStateFromConfig(config) {
return {
+ restrictPostDelete: config.ServiceSettings.RestrictPostDelete,
+ allowEditPost: config.ServiceSettings.AllowEditPost,
+ postEditTimeLimit: config.ServiceSettings.PostEditTimeLimit,
restrictTeamInvite: config.TeamSettings.RestrictTeamInvite,
restrictPublicChannelCreation: config.TeamSettings.RestrictPublicChannelCreation,
restrictPrivateChannelCreation: config.TeamSettings.RestrictPrivateChannelCreation,
@@ -241,6 +249,47 @@ export default class PolicySettings extends AdminSettings {
/>
}
/>
+