Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MM-58492][MM-58523] Fixed some access control bugs around archived channels by replacing the permission check with HasPermissionToReadChannel #27409

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 6 additions & 1 deletion server/channels/api4/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -755,7 +755,12 @@ func getPinnedPosts(c *Context, w http.ResponseWriter, r *http.Request) {
return
}

if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionReadChannelContent) {
channel, err := c.App.GetChannel(c.AppContext, c.Params.ChannelId)
if err != nil {
c.Err = err
return
}
if !c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) {
c.SetPermissionError(model.PermissionReadChannelContent)
return
}
Expand Down
7 changes: 6 additions & 1 deletion server/channels/api4/channel_bookmark.go
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,12 @@ func listChannelBookmarksForChannel(c *Context, w http.ResponseWriter, r *http.R
return
}

if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionReadChannelContent) {
channel, appErr := c.App.GetChannel(c.AppContext, c.Params.ChannelId)
if appErr != nil {
c.Err = appErr
return
}
if !c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) {
c.SetPermissionError(model.PermissionReadChannelContent)
return
}
Expand Down
2 changes: 1 addition & 1 deletion server/channels/api4/channel_bookmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1375,7 +1375,7 @@ func TestListChannelBookmarksForChannel(t *testing.T) {

// an open channel for which the guest is a member but the basic
// user is not
onlyGuestChannel := th.CreateChannelWithClient(th.SystemAdminClient, model.ChannelTypeOpen)
onlyGuestChannel := th.CreateChannelWithClient(th.SystemAdminClient, model.ChannelTypePrivate)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, I'd prefer if we added a new test case rather than changing an existing one.

th.AddUserToChannel(guest, onlyGuestChannel)
guestBookmark := createBookmark("guest", onlyGuestChannel.Id)

Expand Down
2 changes: 1 addition & 1 deletion server/channels/api4/channel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2813,7 +2813,7 @@ func TestGetPinnedPosts(t *testing.T) {

_, resp, err = client.GetPinnedPosts(context.Background(), GenerateTestID(), "")
require.Error(t, err)
CheckForbiddenStatus(t, resp)
CheckNotFoundStatus(t, resp)

_, resp, err = client.GetPinnedPosts(context.Background(), "junk", "")
require.Error(t, err)
Expand Down
35 changes: 30 additions & 5 deletions server/channels/api4/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,12 @@ func getFile(c *Context, w http.ResponseWriter, r *http.Request) {
}
audit.AddEventParameterAuditable(auditRec, "file", info)

perm := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), info.ChannelId, model.PermissionReadChannelContent)
channel, err := c.App.GetChannel(c.AppContext, info.ChannelId)
if err != nil {
c.Err = err
return
}
perm := c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel)
if info.CreatorId == model.BookmarkFileOwner {
if !perm {
c.SetPermissionError(model.PermissionReadChannelContent)
Expand Down Expand Up @@ -521,7 +526,12 @@ func getFileThumbnail(c *Context, w http.ResponseWriter, r *http.Request) {
return
}

perm := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), info.ChannelId, model.PermissionReadChannelContent)
channel, err := c.App.GetChannel(c.AppContext, info.ChannelId)
if err != nil {
c.Err = err
return
}
perm := c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel)
if info.CreatorId == model.BookmarkFileOwner {
if !perm {
c.SetPermissionError(model.PermissionReadChannelContent)
Expand Down Expand Up @@ -570,7 +580,12 @@ func getFileLink(c *Context, w http.ResponseWriter, r *http.Request) {
}
audit.AddEventParameterAuditable(auditRec, "file", info)

perm := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), info.ChannelId, model.PermissionReadChannelContent)
channel, err := c.App.GetChannel(c.AppContext, info.ChannelId)
if err != nil {
c.Err = err
return
}
perm := c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel)
if info.CreatorId == model.BookmarkFileOwner {
if !perm {
c.SetPermissionError(model.PermissionReadChannelContent)
Expand Down Expand Up @@ -609,7 +624,12 @@ func getFilePreview(c *Context, w http.ResponseWriter, r *http.Request) {
return
}

perm := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), info.ChannelId, model.PermissionReadChannelContent)
channel, err := c.App.GetChannel(c.AppContext, info.ChannelId)
if err != nil {
c.Err = err
return
}
perm := c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel)
if info.CreatorId == model.BookmarkFileOwner {
if !perm {
c.SetPermissionError(model.PermissionReadChannelContent)
Expand Down Expand Up @@ -649,7 +669,12 @@ func getFileInfo(c *Context, w http.ResponseWriter, r *http.Request) {
return
}

perm := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), info.ChannelId, model.PermissionReadChannelContent)
channel, err := c.App.GetChannel(c.AppContext, info.ChannelId)
if err != nil {
c.Err = err
return
}
perm := c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel)
if info.CreatorId == model.BookmarkFileOwner {
if !perm {
c.SetPermissionError(model.PermissionReadChannelContent)
Expand Down
14 changes: 12 additions & 2 deletions server/channels/api4/integration_action.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@ func doPostAction(c *Context, w http.ResponseWriter, r *http.Request) {
c.Err = model.NewAppError("DoPostAction", "api.post.do_action.action_integration.app_error", nil, "", http.StatusBadRequest).Wrap(err)
return
}
if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), cookie.ChannelId, model.PermissionReadChannelContent) {
channel, err := c.App.GetChannel(c.AppContext, cookie.ChannelId)
if err != nil {
c.Err = err
return
}
if !c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) {
c.SetPermissionError(model.PermissionReadChannelContent)
return
}
Expand Down Expand Up @@ -108,7 +113,12 @@ func submitDialog(c *Context, w http.ResponseWriter, r *http.Request) {

submit.UserId = c.AppContext.Session().UserId

if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), submit.ChannelId, model.PermissionReadChannelContent) {
channel, err := c.App.GetChannel(c.AppContext, submit.ChannelId)
if err != nil {
c.Err = err
return
}
if !c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) {
c.SetPermissionError(model.PermissionReadChannelContent)
return
}
Expand Down
2 changes: 1 addition & 1 deletion server/channels/api4/integration_action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ func TestSubmitDialog(t *testing.T) {
submit.ChannelId = model.NewId()
submitResp, resp, err = client.SubmitInteractiveDialog(context.Background(), submit)
require.Error(t, err)
CheckForbiddenStatus(t, resp)
CheckNotFoundStatus(t, resp)
assert.Nil(t, submitResp)

submit.URL = ts.URL
Expand Down
77 changes: 57 additions & 20 deletions server/channels/api4/post.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,15 +257,20 @@ func getPostsForChannel(c *Context, w http.ResponseWriter, r *http.Request) {
return
}

if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channelId, model.PermissionReadChannelContent) {
channel, err := c.App.GetChannel(c.AppContext, channelId)
if err != nil {
c.Err = err
return
}
if !c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I am not mistaken, from the implementation of HasPermissionToReadChannel, we check first the channel being archived, before checking whether the user is a sysadmin or not.

This means that now sysadmins won't be able to get the posts for an archived channel. Is that the desired behavior, or we have to rethink the HasPermissionToReadChannel function?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's bring this up in the sync meeting.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a note, anywhere else we check for archived channels we don't check if they're a system admin, so I think it actually makes sense to deny if it's off.

Technically that is a system-wide configuration item so as I see it the proper way to configure access control would be to flip the global setting on and then control it separately per user group using the other permissions controls.

We don't have per-user group permissions to view, only to archive 😅

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decided not to make an exception for system admins: https://hub.mattermost.com/private-core/pl/iopz1gkws7yjbqpqzq8k6kbacy

c.SetPermissionError(model.PermissionReadChannelContent)
return
}
devinbinnie marked this conversation as resolved.
Show resolved Hide resolved

if !*c.App.Config().TeamSettings.ExperimentalViewArchivedChannels {
channel, err := c.App.GetChannel(c.AppContext, channelId)
if err != nil {
c.Err = err
channel, appErr := c.App.GetChannel(c.AppContext, channelId)
if appErr != nil {
c.Err = appErr
return
}
if channel.DeleteAt != 0 {
Expand All @@ -275,7 +280,6 @@ func getPostsForChannel(c *Context, w http.ResponseWriter, r *http.Request) {
}

var list *model.PostList
var err *model.AppError
etag := ""

if since > 0 {
Expand Down Expand Up @@ -341,7 +345,12 @@ func getPostsForChannelAroundLastUnread(c *Context, w http.ResponseWriter, r *ht
}

channelId := c.Params.ChannelId
if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channelId, model.PermissionReadChannelContent) {
channel, err := c.App.GetChannel(c.AppContext, channelId)
if err != nil {
c.Err = err
Comment on lines +348 to +350
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and in a few other places I'd recommend to use appErr as the variable name for consistency and also to avoid future interface conversion bugs.

return
}
if !c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) {
c.SetPermissionError(model.PermissionReadChannelContent)
return
}
Expand Down Expand Up @@ -423,6 +432,20 @@ func getFlaggedPostsForUser(c *Context, w http.ResponseWriter, r *http.Request)
return
}

channelMap := make(map[string]*model.Channel)
channelIds := []string{}
for _, post := range posts.Posts {
channelIds = append(channelIds, post.ChannelId)
}
channels, err := c.App.GetChannels(c.AppContext, channelIds)
if err != nil {
c.Err = err
return
}
for _, channel := range channels {
channelMap[channel.Id] = channel
}

pl := model.NewPostList()
channelReadPermission := make(map[string]bool)

Expand All @@ -432,7 +455,11 @@ func getFlaggedPostsForUser(c *Context, w http.ResponseWriter, r *http.Request)
if !ok {
allowed = false

if c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), post.ChannelId, model.PermissionReadChannelContent) {
channel, ok := channelMap[post.ChannelId]
if !ok {
continue
}
if c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) {
allowed = true
}

Expand Down Expand Up @@ -523,23 +550,28 @@ func getPostsByIds(c *Context, w http.ResponseWriter, r *http.Request) {
return
}

var posts = []*model.Post{}
channelMap := make(map[string]*model.Channel)
channelIds := []string{}
for _, post := range postsList {
channelIds = append(channelIds, post.ChannelId)
}
channels, appErr := c.App.GetChannels(c.AppContext, channelIds)
if appErr != nil {
c.Err = appErr
return
}
for _, channel := range channels {
channelMap[channel.Id] = channel
}

var posts = []*model.Post{}
for _, post := range postsList {
var channel *model.Channel
if val, ok := channelMap[post.ChannelId]; ok {
channel = val
} else {
channel, appErr = c.App.GetChannel(c.AppContext, post.ChannelId)
if appErr != nil {
c.Err = appErr
return
}
channelMap[channel.Id] = channel
channel, ok := channelMap[post.ChannelId]
if !ok {
continue
}

if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channel.Id, model.PermissionReadChannelContent) {
if !c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) {
if channel.Type != model.ChannelTypeOpen || (channel.Type == model.ChannelTypeOpen && !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), channel.TeamId, model.PermissionReadPublicChannel)) {
continue
}
Expand Down Expand Up @@ -1031,7 +1063,12 @@ func saveIsPinnedPost(c *Context, w http.ResponseWriter, isPinned bool) {
auditRec.AddEventPriorState(post)
auditRec.AddEventObjectType("post")

if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), post.ChannelId, model.PermissionReadChannelContent) {
channel, err := c.App.GetChannel(c.AppContext, post.ChannelId)
if err != nil {
c.Err = err
return
}
if !c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) {
c.SetPermissionError(model.PermissionReadChannelContent)
return
}
Expand Down
2 changes: 1 addition & 1 deletion server/channels/api4/post_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1831,7 +1831,7 @@ func TestGetPostsForChannel(t *testing.T) {

_, resp, err := client.GetPostsForChannel(context.Background(), model.NewId(), 0, 60, "", false, false)
require.Error(t, err)
CheckForbiddenStatus(t, resp)
CheckNotFoundStatus(t, resp)

client.Logout(context.Background())
_, resp, err = client.GetPostsForChannel(context.Background(), model.NewId(), 0, 60, "", false, false)
Expand Down
12 changes: 11 additions & 1 deletion server/channels/api4/preference.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ func updatePreferences(c *Context, w http.ResponseWriter, r *http.Request) {
}

var sanitizedPreferences model.Preferences
channelMap := make(map[string]*model.Channel)

for _, pref := range preferences {
if pref.Category == model.PreferenceCategoryFlaggedPost {
Expand All @@ -122,7 +123,16 @@ func updatePreferences(c *Context, w http.ResponseWriter, r *http.Request) {
return
}

if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), post.ChannelId, model.PermissionReadChannelContent) {
channel, ok := channelMap[post.ChannelId]
if !ok {
channel, err = c.App.GetChannel(c.AppContext, post.ChannelId)
if err != nil {
c.Err = err
return
}
}

if !c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) {
c.SetPermissionError(model.PermissionReadChannelContent)
return
}
Expand Down
8 changes: 4 additions & 4 deletions server/channels/api4/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func createIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) {
return
}

if channel.Type != model.ChannelTypeOpen && !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channel.Id, model.PermissionReadChannelContent) {
if channel.Type != model.ChannelTypeOpen && !c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) {
c.LogAudit("fail - bad channel permissions")
c.SetPermissionError(model.PermissionReadChannelContent)
return
Expand Down Expand Up @@ -155,7 +155,7 @@ func updateIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) {
return
}

if channel.Type != model.ChannelTypeOpen && !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channel.Id, model.PermissionReadChannelContent) {
if channel.Type != model.ChannelTypeOpen && !c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) {
c.LogAudit("fail - bad channel permissions")
c.SetPermissionError(model.PermissionReadChannelContent)
return
Expand Down Expand Up @@ -260,7 +260,7 @@ func getIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) {
}

if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), hook.TeamId, model.PermissionManageIncomingWebhooks) ||
(channel.Type != model.ChannelTypeOpen && !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), hook.ChannelId, model.PermissionReadChannelContent)) {
(channel.Type != model.ChannelTypeOpen && !c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel)) {
c.LogAudit("fail - bad permissions")
c.SetPermissionError(model.PermissionManageIncomingWebhooks)
return
Expand Down Expand Up @@ -314,7 +314,7 @@ func deleteIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) {
auditRec.AddMeta("team_id", hook.TeamId)

if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), hook.TeamId, model.PermissionManageIncomingWebhooks) ||
(channel.Type != model.ChannelTypeOpen && !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), hook.ChannelId, model.PermissionReadChannelContent)) {
(channel.Type != model.ChannelTypeOpen && !c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel)) {
c.LogAudit("fail - bad permissions")
c.SetPermissionError(model.PermissionManageIncomingWebhooks)
return
Expand Down
1 change: 1 addition & 0 deletions server/channels/app/app_iface.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions server/channels/app/authorization.go
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,14 @@ func (a *App) SessionHasPermissionToManageBot(rctx request.CTX, session model.Se
return nil
}

func (a *App) SessionHasPermissionToReadChannel(c request.CTX, session model.Session, channel *model.Channel) bool {
if session.IsUnrestricted() {
return true
}

return a.HasPermissionToReadChannel(c, session.UserId, channel)
}

func (a *App) HasPermissionToReadChannel(c request.CTX, userID string, channel *model.Channel) bool {
if !*a.Config().TeamSettings.ExperimentalViewArchivedChannels && channel.DeleteAt != 0 {
return false
Expand Down