github.com/vnforks/kid@v5.11.1+incompatible/api4/post.go (about)

     1  // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package api4
     5  
     6  import (
     7  	"encoding/json"
     8  	"net/http"
     9  	"strconv"
    10  	"time"
    11  
    12  	"github.com/mattermost/mattermost-server/model"
    13  )
    14  
    15  func (api *API) InitPost() {
    16  	api.BaseRoutes.Posts.Handle("", api.ApiSessionRequired(createPost)).Methods("POST")
    17  	api.BaseRoutes.Post.Handle("", api.ApiSessionRequired(getPost)).Methods("GET")
    18  	api.BaseRoutes.Post.Handle("", api.ApiSessionRequired(deletePost)).Methods("DELETE")
    19  	api.BaseRoutes.Posts.Handle("/ephemeral", api.ApiSessionRequired(createEphemeralPost)).Methods("POST")
    20  	api.BaseRoutes.Post.Handle("/thread", api.ApiSessionRequired(getPostThread)).Methods("GET")
    21  	api.BaseRoutes.Post.Handle("/files/info", api.ApiSessionRequired(getFileInfosForPost)).Methods("GET")
    22  	api.BaseRoutes.PostsForChannel.Handle("", api.ApiSessionRequired(getPostsForChannel)).Methods("GET")
    23  	api.BaseRoutes.PostsForUser.Handle("/flagged", api.ApiSessionRequired(getFlaggedPostsForUser)).Methods("GET")
    24  
    25  	api.BaseRoutes.Team.Handle("/posts/search", api.ApiSessionRequired(searchPosts)).Methods("POST")
    26  	api.BaseRoutes.Post.Handle("", api.ApiSessionRequired(updatePost)).Methods("PUT")
    27  	api.BaseRoutes.Post.Handle("/patch", api.ApiSessionRequired(patchPost)).Methods("PUT")
    28  	api.BaseRoutes.Post.Handle("/pin", api.ApiSessionRequired(pinPost)).Methods("POST")
    29  	api.BaseRoutes.Post.Handle("/unpin", api.ApiSessionRequired(unpinPost)).Methods("POST")
    30  }
    31  
    32  func createPost(c *Context, w http.ResponseWriter, r *http.Request) {
    33  	post := model.PostFromJson(r.Body)
    34  	if post == nil {
    35  		c.SetInvalidParam("post")
    36  		return
    37  	}
    38  
    39  	post.UserId = c.App.Session.UserId
    40  
    41  	hasPermission := false
    42  	if c.App.SessionHasPermissionToChannel(c.App.Session, post.ChannelId, model.PERMISSION_CREATE_POST) {
    43  		hasPermission = true
    44  	} else if channel, err := c.App.GetChannel(post.ChannelId); err == nil {
    45  		// Temporary permission check method until advanced permissions, please do not copy
    46  		if channel.Type == model.CHANNEL_OPEN && c.App.SessionHasPermissionToTeam(c.App.Session, channel.TeamId, model.PERMISSION_CREATE_POST_PUBLIC) {
    47  			hasPermission = true
    48  		}
    49  	}
    50  
    51  	if !hasPermission {
    52  		c.SetPermissionError(model.PERMISSION_CREATE_POST)
    53  		return
    54  	}
    55  
    56  	if post.CreateAt != 0 && !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) {
    57  		post.CreateAt = 0
    58  	}
    59  
    60  	rp, err := c.App.CreatePostAsUser(c.App.PostWithProxyRemovedFromImageURLs(post), c.App.Session.Id)
    61  	if err != nil {
    62  		c.Err = err
    63  		return
    64  	}
    65  
    66  	c.App.SetStatusOnline(c.App.Session.UserId, false)
    67  	c.App.UpdateLastActivityAtIfNeeded(c.App.Session)
    68  
    69  	w.WriteHeader(http.StatusCreated)
    70  
    71  	// Note that rp has already had PreparePostForClient called on it by App.CreatePost
    72  	w.Write([]byte(rp.ToJson()))
    73  }
    74  
    75  func createEphemeralPost(c *Context, w http.ResponseWriter, r *http.Request) {
    76  	ephRequest := model.PostEphemeral{}
    77  
    78  	json.NewDecoder(r.Body).Decode(&ephRequest)
    79  	if ephRequest.UserID == "" {
    80  		c.SetInvalidParam("user_id")
    81  		return
    82  	}
    83  
    84  	if ephRequest.Post == nil {
    85  		c.SetInvalidParam("post")
    86  		return
    87  	}
    88  
    89  	ephRequest.Post.UserId = c.App.Session.UserId
    90  	ephRequest.Post.CreateAt = model.GetMillis()
    91  
    92  	if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_CREATE_POST_EPHEMERAL) {
    93  		c.SetPermissionError(model.PERMISSION_CREATE_POST_EPHEMERAL)
    94  		return
    95  	}
    96  
    97  	rp := c.App.SendEphemeralPost(ephRequest.UserID, c.App.PostWithProxyRemovedFromImageURLs(ephRequest.Post))
    98  
    99  	w.WriteHeader(http.StatusCreated)
   100  	rp = model.AddPostActionCookies(rp, c.App.PostActionCookieSecret())
   101  	rp = c.App.PreparePostForClient(rp, true)
   102  	w.Write([]byte(rp.ToJson()))
   103  }
   104  
   105  func getPostsForChannel(c *Context, w http.ResponseWriter, r *http.Request) {
   106  	c.RequireChannelId()
   107  	if c.Err != nil {
   108  		return
   109  	}
   110  
   111  	afterPost := r.URL.Query().Get("after")
   112  	beforePost := r.URL.Query().Get("before")
   113  	sinceString := r.URL.Query().Get("since")
   114  
   115  	var since int64
   116  	var parseError error
   117  
   118  	if len(sinceString) > 0 {
   119  		since, parseError = strconv.ParseInt(sinceString, 10, 64)
   120  		if parseError != nil {
   121  			c.SetInvalidParam("since")
   122  			return
   123  		}
   124  	}
   125  
   126  	if !c.App.SessionHasPermissionToChannel(c.App.Session, c.Params.ChannelId, model.PERMISSION_READ_CHANNEL) {
   127  		c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
   128  		return
   129  	}
   130  
   131  	var list *model.PostList
   132  	var err *model.AppError
   133  	etag := ""
   134  
   135  	if since > 0 {
   136  		list, err = c.App.GetPostsSince(c.Params.ChannelId, since)
   137  	} else if len(afterPost) > 0 {
   138  		etag = c.App.GetPostsEtag(c.Params.ChannelId)
   139  
   140  		if c.HandleEtag(etag, "Get Posts After", w, r) {
   141  			return
   142  		}
   143  
   144  		list, err = c.App.GetPostsAfterPost(c.Params.ChannelId, afterPost, c.Params.Page, c.Params.PerPage)
   145  	} else if len(beforePost) > 0 {
   146  		etag = c.App.GetPostsEtag(c.Params.ChannelId)
   147  
   148  		if c.HandleEtag(etag, "Get Posts Before", w, r) {
   149  			return
   150  		}
   151  
   152  		list, err = c.App.GetPostsBeforePost(c.Params.ChannelId, beforePost, c.Params.Page, c.Params.PerPage)
   153  	} else {
   154  		etag = c.App.GetPostsEtag(c.Params.ChannelId)
   155  
   156  		if c.HandleEtag(etag, "Get Posts", w, r) {
   157  			return
   158  		}
   159  
   160  		list, err = c.App.GetPostsPage(c.Params.ChannelId, c.Params.Page, c.Params.PerPage)
   161  	}
   162  
   163  	if err != nil {
   164  		c.Err = err
   165  		return
   166  	}
   167  
   168  	if len(etag) > 0 {
   169  		w.Header().Set(model.HEADER_ETAG_SERVER, etag)
   170  	}
   171  
   172  	w.Write([]byte(c.App.PreparePostListForClient(list).ToJson()))
   173  }
   174  
   175  func getFlaggedPostsForUser(c *Context, w http.ResponseWriter, r *http.Request) {
   176  	c.RequireUserId()
   177  	if c.Err != nil {
   178  		return
   179  	}
   180  
   181  	if !c.App.SessionHasPermissionToUser(c.App.Session, c.Params.UserId) {
   182  		c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
   183  		return
   184  	}
   185  
   186  	channelId := r.URL.Query().Get("channel_id")
   187  	teamId := r.URL.Query().Get("team_id")
   188  
   189  	var posts *model.PostList
   190  	var err *model.AppError
   191  
   192  	if len(channelId) > 0 {
   193  		posts, err = c.App.GetFlaggedPostsForChannel(c.Params.UserId, channelId, c.Params.Page, c.Params.PerPage)
   194  	} else if len(teamId) > 0 {
   195  		posts, err = c.App.GetFlaggedPostsForTeam(c.Params.UserId, teamId, c.Params.Page, c.Params.PerPage)
   196  	} else {
   197  		posts, err = c.App.GetFlaggedPosts(c.Params.UserId, c.Params.Page, c.Params.PerPage)
   198  	}
   199  
   200  	pl := model.NewPostList()
   201  	channelReadPermission := make(map[string]bool)
   202  
   203  	for _, post := range posts.Posts {
   204  		allowed, ok := channelReadPermission[post.ChannelId]
   205  
   206  		if !ok {
   207  			allowed = false
   208  
   209  			if c.App.SessionHasPermissionToChannel(c.App.Session, post.ChannelId, model.PERMISSION_READ_CHANNEL) {
   210  				allowed = true
   211  			}
   212  
   213  			channelReadPermission[post.ChannelId] = allowed
   214  		}
   215  
   216  		if !allowed {
   217  			continue
   218  		}
   219  
   220  		pl.AddPost(post)
   221  		pl.AddOrder(post.Id)
   222  	}
   223  
   224  	pl.SortByCreateAt()
   225  
   226  	if err != nil {
   227  		c.Err = err
   228  		return
   229  	}
   230  
   231  	w.Write([]byte(c.App.PreparePostListForClient(pl).ToJson()))
   232  }
   233  
   234  func getPost(c *Context, w http.ResponseWriter, r *http.Request) {
   235  	c.RequirePostId()
   236  	if c.Err != nil {
   237  		return
   238  	}
   239  
   240  	post, err := c.App.GetSinglePost(c.Params.PostId)
   241  	if err != nil {
   242  		c.Err = err
   243  		return
   244  	}
   245  
   246  	channel, err := c.App.GetChannel(post.ChannelId)
   247  	if err != nil {
   248  		c.Err = err
   249  		return
   250  	}
   251  
   252  	if !c.App.SessionHasPermissionToChannel(c.App.Session, channel.Id, model.PERMISSION_READ_CHANNEL) {
   253  		if channel.Type == model.CHANNEL_OPEN {
   254  			if !c.App.SessionHasPermissionToTeam(c.App.Session, channel.TeamId, model.PERMISSION_READ_PUBLIC_CHANNEL) {
   255  				c.SetPermissionError(model.PERMISSION_READ_PUBLIC_CHANNEL)
   256  				return
   257  			}
   258  		} else {
   259  			c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
   260  			return
   261  		}
   262  	}
   263  
   264  	post = c.App.PreparePostForClient(post, false)
   265  
   266  	if c.HandleEtag(post.Etag(), "Get Post", w, r) {
   267  		return
   268  	}
   269  
   270  	w.Header().Set(model.HEADER_ETAG_SERVER, post.Etag())
   271  	w.Write([]byte(post.ToJson()))
   272  }
   273  
   274  func deletePost(c *Context, w http.ResponseWriter, r *http.Request) {
   275  	c.RequirePostId()
   276  	if c.Err != nil {
   277  		return
   278  	}
   279  
   280  	post, err := c.App.GetSinglePost(c.Params.PostId)
   281  	if err != nil {
   282  		c.SetPermissionError(model.PERMISSION_DELETE_POST)
   283  		return
   284  	}
   285  
   286  	if c.App.Session.UserId == post.UserId {
   287  		if !c.App.SessionHasPermissionToChannel(c.App.Session, post.ChannelId, model.PERMISSION_DELETE_POST) {
   288  			c.SetPermissionError(model.PERMISSION_DELETE_POST)
   289  			return
   290  		}
   291  	} else {
   292  		if !c.App.SessionHasPermissionToChannel(c.App.Session, post.ChannelId, model.PERMISSION_DELETE_OTHERS_POSTS) {
   293  			c.SetPermissionError(model.PERMISSION_DELETE_OTHERS_POSTS)
   294  			return
   295  		}
   296  	}
   297  
   298  	if _, err := c.App.DeletePost(c.Params.PostId, c.App.Session.UserId); err != nil {
   299  		c.Err = err
   300  		return
   301  	}
   302  
   303  	ReturnStatusOK(w)
   304  }
   305  
   306  func getPostThread(c *Context, w http.ResponseWriter, r *http.Request) {
   307  	c.RequirePostId()
   308  	if c.Err != nil {
   309  		return
   310  	}
   311  
   312  	list, err := c.App.GetPostThread(c.Params.PostId)
   313  	if err != nil {
   314  		c.Err = err
   315  		return
   316  	}
   317  
   318  	post, ok := list.Posts[c.Params.PostId]
   319  	if !ok {
   320  		c.SetInvalidUrlParam("post_id")
   321  		return
   322  	}
   323  
   324  	channel, err := c.App.GetChannel(post.ChannelId)
   325  	if err != nil {
   326  		c.Err = err
   327  		return
   328  	}
   329  
   330  	if !c.App.SessionHasPermissionToChannel(c.App.Session, channel.Id, model.PERMISSION_READ_CHANNEL) {
   331  		if channel.Type == model.CHANNEL_OPEN {
   332  			if !c.App.SessionHasPermissionToTeam(c.App.Session, channel.TeamId, model.PERMISSION_READ_PUBLIC_CHANNEL) {
   333  				c.SetPermissionError(model.PERMISSION_READ_PUBLIC_CHANNEL)
   334  				return
   335  			}
   336  		} else {
   337  			c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
   338  			return
   339  		}
   340  	}
   341  
   342  	if c.HandleEtag(list.Etag(), "Get Post Thread", w, r) {
   343  		return
   344  	}
   345  
   346  	clientPostList := c.App.PreparePostListForClient(list)
   347  
   348  	w.Header().Set(model.HEADER_ETAG_SERVER, clientPostList.Etag())
   349  
   350  	w.Write([]byte(clientPostList.ToJson()))
   351  }
   352  
   353  func searchPosts(c *Context, w http.ResponseWriter, r *http.Request) {
   354  	c.RequireTeamId()
   355  	if c.Err != nil {
   356  		return
   357  	}
   358  
   359  	if !c.App.SessionHasPermissionToTeam(c.App.Session, c.Params.TeamId, model.PERMISSION_VIEW_TEAM) {
   360  		c.SetPermissionError(model.PERMISSION_VIEW_TEAM)
   361  		return
   362  	}
   363  
   364  	params := model.SearchParameterFromJson(r.Body)
   365  
   366  	if params.Terms == nil || len(*params.Terms) == 0 {
   367  		c.SetInvalidParam("terms")
   368  		return
   369  	}
   370  	terms := *params.Terms
   371  
   372  	timeZoneOffset := 0
   373  	if params.TimeZoneOffset != nil {
   374  		timeZoneOffset = *params.TimeZoneOffset
   375  	}
   376  
   377  	isOrSearch := false
   378  	if params.IsOrSearch != nil {
   379  		isOrSearch = *params.IsOrSearch
   380  	}
   381  
   382  	page := 0
   383  	if params.Page != nil {
   384  		page = *params.Page
   385  	}
   386  
   387  	perPage := 60
   388  	if params.PerPage != nil {
   389  		perPage = *params.PerPage
   390  	}
   391  
   392  	includeDeletedChannels := false
   393  	if params.IncludeDeletedChannels != nil {
   394  		includeDeletedChannels = *params.IncludeDeletedChannels
   395  	}
   396  
   397  	startTime := time.Now()
   398  
   399  	results, err := c.App.SearchPostsInTeamForUser(terms, c.App.Session.UserId, c.Params.TeamId, isOrSearch, includeDeletedChannels, int(timeZoneOffset), page, perPage)
   400  
   401  	elapsedTime := float64(time.Since(startTime)) / float64(time.Second)
   402  	metrics := c.App.Metrics
   403  	if metrics != nil {
   404  		metrics.IncrementPostsSearchCounter()
   405  		metrics.ObservePostsSearchDuration(elapsedTime)
   406  	}
   407  
   408  	if err != nil {
   409  		c.Err = err
   410  		return
   411  	}
   412  
   413  	clientPostList := c.App.PreparePostListForClient(results.PostList)
   414  
   415  	results = model.MakePostSearchResults(clientPostList, results.Matches)
   416  
   417  	w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
   418  	w.Write([]byte(results.ToJson()))
   419  }
   420  
   421  func updatePost(c *Context, w http.ResponseWriter, r *http.Request) {
   422  	c.RequirePostId()
   423  	if c.Err != nil {
   424  		return
   425  	}
   426  
   427  	post := model.PostFromJson(r.Body)
   428  
   429  	if post == nil {
   430  		c.SetInvalidParam("post")
   431  		return
   432  	}
   433  
   434  	// The post being updated in the payload must be the same one as indicated in the URL.
   435  	if post.Id != c.Params.PostId {
   436  		c.SetInvalidParam("id")
   437  		return
   438  	}
   439  
   440  	// Updating the file_ids of a post is not a supported operation and will be ignored
   441  	post.FileIds = nil
   442  
   443  	if !c.App.SessionHasPermissionToChannelByPost(c.App.Session, c.Params.PostId, model.PERMISSION_EDIT_POST) {
   444  		c.SetPermissionError(model.PERMISSION_EDIT_POST)
   445  		return
   446  	}
   447  
   448  	originalPost, err := c.App.GetSinglePost(c.Params.PostId)
   449  	if err != nil {
   450  		c.SetPermissionError(model.PERMISSION_EDIT_POST)
   451  		return
   452  	}
   453  
   454  	if c.App.Session.UserId != originalPost.UserId {
   455  		if !c.App.SessionHasPermissionToChannelByPost(c.App.Session, c.Params.PostId, model.PERMISSION_EDIT_OTHERS_POSTS) {
   456  			c.SetPermissionError(model.PERMISSION_EDIT_OTHERS_POSTS)
   457  			return
   458  		}
   459  	}
   460  
   461  	post.Id = c.Params.PostId
   462  
   463  	rpost, err := c.App.UpdatePost(c.App.PostWithProxyRemovedFromImageURLs(post), false)
   464  	if err != nil {
   465  		c.Err = err
   466  		return
   467  	}
   468  
   469  	w.Write([]byte(rpost.ToJson()))
   470  }
   471  
   472  func patchPost(c *Context, w http.ResponseWriter, r *http.Request) {
   473  	c.RequirePostId()
   474  	if c.Err != nil {
   475  		return
   476  	}
   477  
   478  	post := model.PostPatchFromJson(r.Body)
   479  
   480  	if post == nil {
   481  		c.SetInvalidParam("post")
   482  		return
   483  	}
   484  
   485  	// Updating the file_ids of a post is not a supported operation and will be ignored
   486  	post.FileIds = nil
   487  
   488  	if !c.App.SessionHasPermissionToChannelByPost(c.App.Session, c.Params.PostId, model.PERMISSION_EDIT_POST) {
   489  		c.SetPermissionError(model.PERMISSION_EDIT_POST)
   490  		return
   491  	}
   492  
   493  	originalPost, err := c.App.GetSinglePost(c.Params.PostId)
   494  	if err != nil {
   495  		c.SetPermissionError(model.PERMISSION_EDIT_POST)
   496  		return
   497  	}
   498  
   499  	if c.App.Session.UserId != originalPost.UserId {
   500  		if !c.App.SessionHasPermissionToChannelByPost(c.App.Session, c.Params.PostId, model.PERMISSION_EDIT_OTHERS_POSTS) {
   501  			c.SetPermissionError(model.PERMISSION_EDIT_OTHERS_POSTS)
   502  			return
   503  		}
   504  	}
   505  
   506  	patchedPost, err := c.App.PatchPost(c.Params.PostId, c.App.PostPatchWithProxyRemovedFromImageURLs(post))
   507  	if err != nil {
   508  		c.Err = err
   509  		return
   510  	}
   511  
   512  	w.Write([]byte(patchedPost.ToJson()))
   513  }
   514  
   515  func saveIsPinnedPost(c *Context, w http.ResponseWriter, r *http.Request, isPinned bool) {
   516  	c.RequirePostId()
   517  	if c.Err != nil {
   518  		return
   519  	}
   520  
   521  	if !c.App.SessionHasPermissionToChannelByPost(c.App.Session, c.Params.PostId, model.PERMISSION_READ_CHANNEL) {
   522  		c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
   523  		return
   524  	}
   525  
   526  	// Restrict pinning if the experimental read-only-town-square setting is on.
   527  	user, err := c.App.GetUser(c.App.Session.UserId)
   528  	if err != nil {
   529  		c.Err = err
   530  		return
   531  	}
   532  
   533  	post, err := c.App.GetSinglePost(c.Params.PostId)
   534  	if err != nil {
   535  		c.Err = err
   536  		return
   537  	}
   538  
   539  	channel, err := c.App.GetChannel(post.ChannelId)
   540  	if err != nil {
   541  		c.Err = err
   542  		return
   543  	}
   544  
   545  	if c.App.License() != nil &&
   546  		*c.App.Config().TeamSettings.ExperimentalTownSquareIsReadOnly &&
   547  		channel.Name == model.DEFAULT_CHANNEL &&
   548  		!c.App.RolesGrantPermission(user.GetRoles(), model.PERMISSION_MANAGE_SYSTEM.Id) {
   549  		c.Err = model.NewAppError("saveIsPinnedPost", "api.post.save_is_pinned_post.town_square_read_only", nil, "", http.StatusForbidden)
   550  		return
   551  	}
   552  
   553  	patch := &model.PostPatch{}
   554  	patch.IsPinned = model.NewBool(isPinned)
   555  
   556  	_, err = c.App.PatchPost(c.Params.PostId, patch)
   557  	if err != nil {
   558  		c.Err = err
   559  		return
   560  	}
   561  
   562  	ReturnStatusOK(w)
   563  }
   564  
   565  func pinPost(c *Context, w http.ResponseWriter, r *http.Request) {
   566  	saveIsPinnedPost(c, w, r, true)
   567  }
   568  
   569  func unpinPost(c *Context, w http.ResponseWriter, r *http.Request) {
   570  	saveIsPinnedPost(c, w, r, false)
   571  }
   572  
   573  func getFileInfosForPost(c *Context, w http.ResponseWriter, r *http.Request) {
   574  	c.RequirePostId()
   575  	if c.Err != nil {
   576  		return
   577  	}
   578  
   579  	if !c.App.SessionHasPermissionToChannelByPost(c.App.Session, c.Params.PostId, model.PERMISSION_READ_CHANNEL) {
   580  		c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
   581  		return
   582  	}
   583  
   584  	infos, err := c.App.GetFileInfosForPostWithMigration(c.Params.PostId)
   585  	if err != nil {
   586  		c.Err = err
   587  		return
   588  	}
   589  
   590  	if c.HandleEtag(model.GetEtagForFileInfos(infos), "Get File Infos For Post", w, r) {
   591  		return
   592  	}
   593  
   594  	w.Header().Set("Cache-Control", "max-age=2592000, public")
   595  	w.Header().Set(model.HEADER_ETAG_SERVER, model.GetEtagForFileInfos(infos))
   596  	w.Write([]byte(model.FileInfosToJson(infos)))
   597  }