Implement APIv4 infrastructure (#5191)
* Implement APIv4 infrastructure * Update parameter requirement functions per feedback
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
3e2f879b77
Коммит
c01d9ad6cf
189
api4/api.go
Обычный файл
189
api4/api.go
Обычный файл
@@ -0,0 +1,189 @@
|
||||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package api4
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/mattermost/platform/app"
|
||||
"github.com/mattermost/platform/einterfaces"
|
||||
"github.com/mattermost/platform/model"
|
||||
"github.com/mattermost/platform/utils"
|
||||
|
||||
_ "github.com/nicksnyder/go-i18n/i18n"
|
||||
)
|
||||
|
||||
type Routes struct {
|
||||
Root *mux.Router // ''
|
||||
ApiRoot *mux.Router // 'api/v4'
|
||||
|
||||
Users *mux.Router // 'api/v4/users'
|
||||
User *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}'
|
||||
UserByUsername *mux.Router // 'api/v4/users/username/{username:[A-Za-z0-9_-\.]+}'
|
||||
UserByEmail *mux.Router // 'api/v4/users/email/{email}'
|
||||
|
||||
Teams *mux.Router // 'api/v4/teams'
|
||||
TeamsForUser *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}/teams'
|
||||
Team *mux.Router // 'api/v4/teams/{team_id:[A-Za-z0-9]+}'
|
||||
TeamByName *mux.Router // 'api/v4/teams/name/{team_name:[A-Za-z0-9_-]+}'
|
||||
TeamMembers *mux.Router // 'api/v4/teams/{team_id:[A-Za-z0-9_-]+}/members'
|
||||
TeamMember *mux.Router // 'api/v4/teams/{team_id:[A-Za-z0-9_-]+}/members/{user_id:[A-Za-z0-9_-]+}'
|
||||
|
||||
Channels *mux.Router // 'api/v4/channels'
|
||||
Channel *mux.Router // 'api/v4/channels/{channel_id:[A-Za-z0-9]+}'
|
||||
ChannelByName *mux.Router // 'api/v4/teams/{team_id:[A-Za-z0-9]+}/channels/name/{channel_name:[A-Za-z0-9_-]+}'
|
||||
ChannelsForTeam *mux.Router // 'api/v4/teams/{team_id:[A-Za-z0-9]+}/channels'
|
||||
ChannelMembers *mux.Router // 'api/v4/channels/{channel_id:[A-Za-z0-9]+}/members'
|
||||
ChannelMember *mux.Router // 'api/v4/channels/{channel_id:[A-Za-z0-9]+}/members/{user_id:[A-Za-z0-9]+}'
|
||||
ChannelMembersForUser *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}/channels/members'
|
||||
|
||||
Posts *mux.Router // 'api/v4/posts'
|
||||
Post *mux.Router // 'api/v4/posts/{post_id:[A-Za-z0-9]+}'
|
||||
PostsForChannel *mux.Router // 'api/v4/channels/{channel_id:[A-Za-z0-9]+}/posts'
|
||||
|
||||
Files *mux.Router // 'api/v4/files'
|
||||
File *mux.Router // 'api/v4/files/{file_id:[A-Za-z0-9]+}'
|
||||
|
||||
Commands *mux.Router // 'api/v4/commands'
|
||||
Command *mux.Router // 'api/v4/commands/{command_id:[A-Za-z0-9]+}'
|
||||
CommandsForTeam *mux.Router // 'api/v4/teams/{team_id:[A-Za-z0-9]+}/commands'
|
||||
|
||||
Hooks *mux.Router // 'api/v4/teams/hooks'
|
||||
IncomingHooks *mux.Router // 'api/v4/teams/hooks/incoming'
|
||||
IncomingHook *mux.Router // 'api/v4/teams/hooks/incoming/{hook_id:[A-Za-z0-9]+}'
|
||||
OutgoingHooks *mux.Router // 'api/v4/teams/hooks/outgoing'
|
||||
OutgoingHook *mux.Router // 'api/v4/teams/hooks/outgoing/{hook_id:[A-Za-z0-9]+}'
|
||||
|
||||
OAuth *mux.Router // 'api/v4/oauth'
|
||||
|
||||
Admin *mux.Router // 'api/v4/admin'
|
||||
|
||||
System *mux.Router // 'api/v4/system'
|
||||
|
||||
Preferences *mux.Router // 'api/v4/preferences'
|
||||
|
||||
License *mux.Router // 'api/v4/license'
|
||||
|
||||
Public *mux.Router // 'api/v4/public'
|
||||
|
||||
Emojis *mux.Router // 'api/v4/emoji'
|
||||
Emoji *mux.Router // 'api/v4/emoji/{emoji_id:[A-Za-z0-9]+}'
|
||||
|
||||
Webrtc *mux.Router // 'api/v4/webrtc'
|
||||
}
|
||||
|
||||
var BaseRoutes *Routes
|
||||
|
||||
func InitRouter() {
|
||||
app.Srv.Router = mux.NewRouter()
|
||||
app.Srv.Router.NotFoundHandler = http.HandlerFunc(Handle404)
|
||||
app.Srv.WebSocketRouter = app.NewWebSocketRouter()
|
||||
}
|
||||
|
||||
func InitApi(full bool) {
|
||||
BaseRoutes = &Routes{}
|
||||
BaseRoutes.Root = app.Srv.Router
|
||||
BaseRoutes.ApiRoot = app.Srv.Router.PathPrefix(model.API_URL_SUFFIX).Subrouter()
|
||||
|
||||
BaseRoutes.Users = BaseRoutes.ApiRoot.PathPrefix("/users").Subrouter()
|
||||
BaseRoutes.User = BaseRoutes.Users.PathPrefix("/{user_id:[A-Za-z0-9]+}").Subrouter()
|
||||
BaseRoutes.UserByUsername = BaseRoutes.Users.PathPrefix("/username/{username:[A-Za-z0-9_-.]+}").Subrouter()
|
||||
BaseRoutes.UserByEmail = BaseRoutes.Users.PathPrefix("/email/{email}").Subrouter()
|
||||
|
||||
BaseRoutes.Teams = BaseRoutes.ApiRoot.PathPrefix("/teams").Subrouter()
|
||||
BaseRoutes.TeamsForUser = BaseRoutes.Users.PathPrefix("/teams").Subrouter()
|
||||
BaseRoutes.Team = BaseRoutes.Teams.PathPrefix("/{team_id:[A-Za-z0-9]+}").Subrouter()
|
||||
BaseRoutes.TeamByName = BaseRoutes.Teams.PathPrefix("/name/{team_name:[A-Za-z0-9_-]+}").Subrouter()
|
||||
BaseRoutes.TeamMembers = BaseRoutes.Team.PathPrefix("/members").Subrouter()
|
||||
BaseRoutes.TeamMember = BaseRoutes.TeamMembers.PathPrefix("/{user_id:[A-Za-z0-9]+}").Subrouter()
|
||||
|
||||
BaseRoutes.Channels = BaseRoutes.ApiRoot.PathPrefix("/channels").Subrouter()
|
||||
BaseRoutes.Channel = BaseRoutes.Channels.PathPrefix("/{channel_id:[A-Za-z0-9]+}").Subrouter()
|
||||
BaseRoutes.ChannelByName = BaseRoutes.Team.PathPrefix("/name/{channel_name:[A-Za-z0-9_-]+}").Subrouter()
|
||||
BaseRoutes.ChannelsForTeam = BaseRoutes.Team.PathPrefix("/channels").Subrouter()
|
||||
BaseRoutes.ChannelMembers = BaseRoutes.Channel.PathPrefix("/members").Subrouter()
|
||||
BaseRoutes.ChannelMember = BaseRoutes.ChannelMembers.PathPrefix("/{user_id:[A-Za-z0-9]+}").Subrouter()
|
||||
BaseRoutes.ChannelMembersForUser = BaseRoutes.User.PathPrefix("/channels/members").Subrouter()
|
||||
|
||||
BaseRoutes.Posts = BaseRoutes.ApiRoot.PathPrefix("/posts").Subrouter()
|
||||
BaseRoutes.Post = BaseRoutes.Posts.PathPrefix("/{post_id:[A-Za-z0-9]+}").Subrouter()
|
||||
BaseRoutes.PostsForChannel = BaseRoutes.Channel.PathPrefix("/posts").Subrouter()
|
||||
|
||||
BaseRoutes.Files = BaseRoutes.ApiRoot.PathPrefix("/files").Subrouter()
|
||||
BaseRoutes.File = BaseRoutes.Files.PathPrefix("/{file_id:[A-Za-z0-9]+}").Subrouter()
|
||||
|
||||
BaseRoutes.Commands = BaseRoutes.ApiRoot.PathPrefix("/commands").Subrouter()
|
||||
BaseRoutes.Command = BaseRoutes.Commands.PathPrefix("/{command_id:[A-Za-z0-9]+}").Subrouter()
|
||||
BaseRoutes.CommandsForTeam = BaseRoutes.Team.PathPrefix("/commands").Subrouter()
|
||||
|
||||
BaseRoutes.Hooks = BaseRoutes.ApiRoot.PathPrefix("/hooks").Subrouter()
|
||||
BaseRoutes.IncomingHooks = BaseRoutes.Hooks.PathPrefix("/incoming").Subrouter()
|
||||
BaseRoutes.IncomingHook = BaseRoutes.IncomingHooks.PathPrefix("/{hook_id:[A-Za-z0-9]+}").Subrouter()
|
||||
BaseRoutes.OutgoingHooks = BaseRoutes.Hooks.PathPrefix("/outgoing").Subrouter()
|
||||
BaseRoutes.OutgoingHook = BaseRoutes.OutgoingHooks.PathPrefix("/{hook_id:[A-Za-z0-9]+}").Subrouter()
|
||||
|
||||
BaseRoutes.OAuth = BaseRoutes.ApiRoot.PathPrefix("/oauth").Subrouter()
|
||||
BaseRoutes.Admin = BaseRoutes.ApiRoot.PathPrefix("/admin").Subrouter()
|
||||
BaseRoutes.System = BaseRoutes.ApiRoot.PathPrefix("/system").Subrouter()
|
||||
BaseRoutes.Preferences = BaseRoutes.ApiRoot.PathPrefix("/preferences").Subrouter()
|
||||
BaseRoutes.License = BaseRoutes.ApiRoot.PathPrefix("/license").Subrouter()
|
||||
BaseRoutes.Public = BaseRoutes.ApiRoot.PathPrefix("/public").Subrouter()
|
||||
|
||||
BaseRoutes.Emojis = BaseRoutes.ApiRoot.PathPrefix("/emoji").Subrouter()
|
||||
BaseRoutes.Emoji = BaseRoutes.Emojis.PathPrefix("/{emoji_id:[A-Za-z0-9]+}").Subrouter()
|
||||
|
||||
BaseRoutes.Webrtc = BaseRoutes.ApiRoot.PathPrefix("/webrtc").Subrouter()
|
||||
|
||||
InitUser()
|
||||
|
||||
// REMOVE CONDITION WHEN APIv3 REMOVED
|
||||
if full {
|
||||
// 404 on any api route before web.go has a chance to serve it
|
||||
app.Srv.Router.Handle("/api/{anything:.*}", http.HandlerFunc(Handle404))
|
||||
|
||||
utils.InitHTML()
|
||||
|
||||
app.InitEmailBatching()
|
||||
}
|
||||
}
|
||||
|
||||
func HandleEtag(etag string, routeName string, w http.ResponseWriter, r *http.Request) bool {
|
||||
metrics := einterfaces.GetMetricsInterface()
|
||||
if et := r.Header.Get(model.HEADER_ETAG_CLIENT); len(etag) > 0 {
|
||||
if et == etag {
|
||||
w.Header().Set(model.HEADER_ETAG_SERVER, etag)
|
||||
w.WriteHeader(http.StatusNotModified)
|
||||
if metrics != nil {
|
||||
metrics.IncrementEtagHitCounter(routeName)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if metrics != nil {
|
||||
metrics.IncrementEtagMissCounter(routeName)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func Handle404(w http.ResponseWriter, r *http.Request) {
|
||||
err := model.NewLocAppError("Handle404", "api.context.404.app_error", nil, "")
|
||||
err.Translate(utils.T)
|
||||
err.StatusCode = http.StatusNotFound
|
||||
|
||||
l4g.Debug("%v: code=404 ip=%v", r.URL.Path, utils.GetIpAddress(r))
|
||||
|
||||
w.WriteHeader(err.StatusCode)
|
||||
err.DetailedError = "There doesn't appear to be an api call for the url='" + r.URL.Path + "'."
|
||||
w.Write([]byte(err.ToJson()))
|
||||
}
|
||||
|
||||
func ReturnStatusOK(w http.ResponseWriter) {
|
||||
m := make(map[string]string)
|
||||
m[model.STATUS] = model.STATUS_OK
|
||||
w.Write([]byte(model.MapToJson(m)))
|
||||
}
|
||||
211
api4/apitestlib.go
Обычный файл
211
api4/apitestlib.go
Обычный файл
@@ -0,0 +1,211 @@
|
||||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package api4
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/platform/app"
|
||||
"github.com/mattermost/platform/model"
|
||||
"github.com/mattermost/platform/store"
|
||||
"github.com/mattermost/platform/utils"
|
||||
)
|
||||
|
||||
type TestHelper struct {
|
||||
Client *model.Client4
|
||||
BasicUser *model.User
|
||||
SystemAdminUser *model.User
|
||||
}
|
||||
|
||||
func Setup() *TestHelper {
|
||||
if app.Srv == nil {
|
||||
utils.TranslationsPreInit()
|
||||
utils.LoadConfig("config.json")
|
||||
utils.InitTranslations(utils.Cfg.LocalizationSettings)
|
||||
utils.Cfg.TeamSettings.MaxUsersPerTeam = 50
|
||||
*utils.Cfg.RateLimitSettings.Enable = false
|
||||
utils.Cfg.EmailSettings.SendEmailNotifications = true
|
||||
utils.Cfg.EmailSettings.SMTPServer = "dockerhost"
|
||||
utils.Cfg.EmailSettings.SMTPPort = "2500"
|
||||
utils.Cfg.EmailSettings.FeedbackEmail = "test@example.com"
|
||||
utils.DisableDebugLogForTest()
|
||||
app.NewServer()
|
||||
app.InitStores()
|
||||
InitRouter()
|
||||
app.StartServer()
|
||||
InitApi(true)
|
||||
utils.EnableDebugLogForTest()
|
||||
app.Srv.Store.MarkSystemRanUnitTests()
|
||||
|
||||
*utils.Cfg.TeamSettings.EnableOpenServer = true
|
||||
}
|
||||
|
||||
th := &TestHelper{}
|
||||
th.Client = th.CreateClient()
|
||||
return th
|
||||
}
|
||||
|
||||
func (me *TestHelper) InitBasic() *TestHelper {
|
||||
me.BasicUser = me.CreateUser()
|
||||
app.UpdateUserRoles(me.BasicUser.Id, model.ROLE_SYSTEM_USER.Id)
|
||||
me.LoginBasic()
|
||||
|
||||
return me
|
||||
}
|
||||
|
||||
func (me *TestHelper) InitSystemAdmin() *TestHelper {
|
||||
me.SystemAdminUser = me.CreateUser()
|
||||
app.UpdateUserRoles(me.SystemAdminUser.Id, model.ROLE_SYSTEM_USER.Id+" "+model.ROLE_SYSTEM_ADMIN.Id)
|
||||
|
||||
return me
|
||||
}
|
||||
|
||||
func (me *TestHelper) CreateClient() *model.Client4 {
|
||||
return model.NewAPIv4Client("http://localhost" + utils.Cfg.ServiceSettings.ListenAddress)
|
||||
}
|
||||
|
||||
func (me *TestHelper) CreateUser() *model.User {
|
||||
id := model.NewId()
|
||||
|
||||
user := &model.User{
|
||||
Email: GenerateTestEmail(),
|
||||
Username: GenerateTestUsername(),
|
||||
Nickname: "nn_" + id,
|
||||
FirstName: "f_" + id,
|
||||
LastName: "l_" + id,
|
||||
Password: "Password1",
|
||||
}
|
||||
|
||||
utils.DisableDebugLogForTest()
|
||||
ruser, _ := me.Client.CreateUser(user)
|
||||
ruser.Password = "Password1"
|
||||
VerifyUserEmail(ruser.Id)
|
||||
utils.EnableDebugLogForTest()
|
||||
return ruser
|
||||
}
|
||||
|
||||
func (me *TestHelper) LoginBasic() {
|
||||
utils.DisableDebugLogForTest()
|
||||
me.Client.Login(me.BasicUser.Email, me.BasicUser.Password)
|
||||
utils.EnableDebugLogForTest()
|
||||
}
|
||||
|
||||
func (me *TestHelper) LoginSystemAdmin() {
|
||||
utils.DisableDebugLogForTest()
|
||||
me.Client.Login(me.SystemAdminUser.Email, me.SystemAdminUser.Password)
|
||||
utils.EnableDebugLogForTest()
|
||||
}
|
||||
|
||||
func GenerateTestEmail() string {
|
||||
return strings.ToLower("success+" + model.NewId() + "@simulator.amazonses.com")
|
||||
}
|
||||
|
||||
func GenerateTestUsername() string {
|
||||
return "n" + model.NewId()
|
||||
}
|
||||
|
||||
func VerifyUserEmail(userId string) {
|
||||
store.Must(app.Srv.Store.User().VerifyEmail(userId))
|
||||
}
|
||||
|
||||
func CheckUserSanitization(t *testing.T, user *model.User) {
|
||||
if user.Password != "" {
|
||||
t.Fatal("password wasn't blank")
|
||||
}
|
||||
|
||||
if user.AuthData != nil && *user.AuthData != "" {
|
||||
t.Fatal("auth data wasn't blank")
|
||||
}
|
||||
|
||||
if user.MfaSecret != "" {
|
||||
t.Fatal("mfa secret wasn't blank")
|
||||
}
|
||||
}
|
||||
|
||||
func CheckEtag(t *testing.T, data interface{}, resp *model.Response) {
|
||||
if !reflect.ValueOf(data).IsNil() {
|
||||
t.Fatal("etag data was not nil")
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusNotModified {
|
||||
t.Log("actual: " + strconv.Itoa(resp.StatusCode))
|
||||
t.Log("expected: " + strconv.Itoa(http.StatusNotModified))
|
||||
t.Fatal("wrong status code for etag")
|
||||
}
|
||||
}
|
||||
|
||||
func CheckNoError(t *testing.T, resp *model.Response) {
|
||||
if resp.Error != nil {
|
||||
t.Fatal(resp.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func CheckForbiddenStatus(t *testing.T, resp *model.Response) {
|
||||
if resp.Error == nil {
|
||||
t.Fatal("should have errored with status:" + strconv.Itoa(http.StatusForbidden))
|
||||
return
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusForbidden {
|
||||
t.Log("actual: " + strconv.Itoa(resp.StatusCode))
|
||||
t.Log("expected: " + strconv.Itoa(http.StatusForbidden))
|
||||
t.Fatal("wrong status code")
|
||||
}
|
||||
}
|
||||
|
||||
func CheckUnauthorizedStatus(t *testing.T, resp *model.Response) {
|
||||
if resp.Error == nil {
|
||||
t.Fatal("should have errored with status:" + strconv.Itoa(http.StatusUnauthorized))
|
||||
return
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusUnauthorized {
|
||||
t.Log("actual: " + strconv.Itoa(resp.StatusCode))
|
||||
t.Log("expected: " + strconv.Itoa(http.StatusUnauthorized))
|
||||
t.Fatal("wrong status code")
|
||||
}
|
||||
}
|
||||
|
||||
func CheckNotFoundStatus(t *testing.T, resp *model.Response) {
|
||||
if resp.Error == nil {
|
||||
t.Fatal("should have errored with status:" + strconv.Itoa(http.StatusNotFound))
|
||||
return
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusNotFound {
|
||||
t.Log("actual: " + strconv.Itoa(resp.StatusCode))
|
||||
t.Log("expected: " + strconv.Itoa(http.StatusNotFound))
|
||||
t.Fatal("wrong status code")
|
||||
}
|
||||
}
|
||||
|
||||
func CheckBadRequestStatus(t *testing.T, resp *model.Response) {
|
||||
if resp.Error == nil {
|
||||
t.Fatal("should have errored with status:" + strconv.Itoa(http.StatusBadRequest))
|
||||
return
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusBadRequest {
|
||||
t.Log("actual: " + strconv.Itoa(resp.StatusCode))
|
||||
t.Log("expected: " + strconv.Itoa(http.StatusBadRequest))
|
||||
t.Fatal("wrong status code")
|
||||
}
|
||||
}
|
||||
|
||||
func CheckErrorMessage(t *testing.T, resp *model.Response, message string) {
|
||||
if resp.Error == nil {
|
||||
t.Fatal("should have errored with message:" + message)
|
||||
return
|
||||
}
|
||||
|
||||
if resp.Error.Message != message {
|
||||
t.Log("actual: " + resp.Error.Message)
|
||||
t.Log("expected: " + message)
|
||||
t.Fatal("incorrect error message")
|
||||
}
|
||||
}
|
||||
360
api4/context.go
Обычный файл
360
api4/context.go
Обычный файл
@@ -0,0 +1,360 @@
|
||||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package api4
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
goi18n "github.com/nicksnyder/go-i18n/i18n"
|
||||
|
||||
"github.com/mattermost/platform/app"
|
||||
"github.com/mattermost/platform/einterfaces"
|
||||
"github.com/mattermost/platform/model"
|
||||
"github.com/mattermost/platform/utils"
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
Session model.Session
|
||||
Params *ApiParams
|
||||
Err *model.AppError
|
||||
T goi18n.TranslateFunc
|
||||
RequestId string
|
||||
IpAddress string
|
||||
Path string
|
||||
siteURL string
|
||||
}
|
||||
|
||||
func ApiHandler(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
|
||||
return &handler{
|
||||
handleFunc: h,
|
||||
requireSession: false,
|
||||
trustRequester: false,
|
||||
requireMfa: false,
|
||||
}
|
||||
}
|
||||
|
||||
func ApiSessionRequired(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
|
||||
return &handler{
|
||||
handleFunc: h,
|
||||
requireSession: true,
|
||||
trustRequester: false,
|
||||
requireMfa: true,
|
||||
}
|
||||
}
|
||||
|
||||
func ApiSessionRequiredMfa(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
|
||||
return &handler{
|
||||
handleFunc: h,
|
||||
requireSession: true,
|
||||
trustRequester: false,
|
||||
requireMfa: false,
|
||||
}
|
||||
}
|
||||
|
||||
func ApiHandlerTrustRequester(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
|
||||
return &handler{
|
||||
handleFunc: h,
|
||||
requireSession: false,
|
||||
trustRequester: true,
|
||||
requireMfa: false,
|
||||
}
|
||||
}
|
||||
|
||||
func ApiSessionRequiredTrustRequester(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
|
||||
return &handler{
|
||||
handleFunc: h,
|
||||
requireSession: true,
|
||||
trustRequester: true,
|
||||
requireMfa: true,
|
||||
}
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
handleFunc func(*Context, http.ResponseWriter, *http.Request)
|
||||
requireSession bool
|
||||
trustRequester bool
|
||||
requireMfa bool
|
||||
}
|
||||
|
||||
func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
now := time.Now()
|
||||
l4g.Debug("%v - %v", r.Method, r.URL.Path)
|
||||
|
||||
c := &Context{}
|
||||
c.T, _ = utils.GetTranslationsAndLocale(w, r)
|
||||
c.RequestId = model.NewId()
|
||||
c.IpAddress = utils.GetIpAddress(r)
|
||||
c.Params = ApiParamsFromRequest(r)
|
||||
|
||||
token := ""
|
||||
isTokenFromQueryString := false
|
||||
|
||||
// Attempt to parse token out of the header
|
||||
authHeader := r.Header.Get(model.HEADER_AUTH)
|
||||
if len(authHeader) > 6 && strings.ToUpper(authHeader[0:6]) == model.HEADER_BEARER {
|
||||
// Default session token
|
||||
token = authHeader[7:]
|
||||
|
||||
} else if len(authHeader) > 5 && strings.ToLower(authHeader[0:5]) == model.HEADER_TOKEN {
|
||||
// OAuth token
|
||||
token = authHeader[6:]
|
||||
}
|
||||
|
||||
// Attempt to parse the token from the cookie
|
||||
if len(token) == 0 {
|
||||
if cookie, err := r.Cookie(model.SESSION_COOKIE_TOKEN); err == nil {
|
||||
token = cookie.Value
|
||||
|
||||
if h.requireSession && !h.trustRequester {
|
||||
if r.Header.Get(model.HEADER_REQUESTED_WITH) != model.HEADER_REQUESTED_WITH_XML {
|
||||
c.Err = model.NewLocAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token+" Appears to be a CSRF attempt")
|
||||
token = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to parse token out of the query string
|
||||
if len(token) == 0 {
|
||||
token = r.URL.Query().Get("access_token")
|
||||
isTokenFromQueryString = true
|
||||
}
|
||||
|
||||
if utils.GetSiteURL() == "" {
|
||||
protocol := app.GetProtocol(r)
|
||||
c.SetSiteURL(protocol + "://" + r.Host)
|
||||
}
|
||||
|
||||
w.Header().Set(model.HEADER_REQUEST_ID, c.RequestId)
|
||||
w.Header().Set(model.HEADER_VERSION_ID, fmt.Sprintf("%v.%v.%v", model.CurrentVersion, model.BuildNumber, utils.CfgHash))
|
||||
if einterfaces.GetClusterInterface() != nil {
|
||||
w.Header().Set(model.HEADER_CLUSTER_ID, einterfaces.GetClusterInterface().GetClusterId())
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
if r.Method == "GET" {
|
||||
w.Header().Set("Expires", "0")
|
||||
}
|
||||
|
||||
if len(token) != 0 {
|
||||
session, err := app.GetSession(token)
|
||||
|
||||
if err != nil {
|
||||
l4g.Error(utils.T("api.context.invalid_session.error"), err.Error())
|
||||
c.RemoveSessionCookie(w, r)
|
||||
if h.requireSession {
|
||||
c.Err = model.NewLocAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token)
|
||||
c.Err.StatusCode = http.StatusUnauthorized
|
||||
}
|
||||
} else if !session.IsOAuth && isTokenFromQueryString {
|
||||
c.Err = model.NewLocAppError("ServeHTTP", "api.context.token_provided.app_error", nil, "token="+token)
|
||||
c.Err.StatusCode = http.StatusUnauthorized
|
||||
} else {
|
||||
c.Session = *session
|
||||
}
|
||||
}
|
||||
|
||||
c.Path = r.URL.Path
|
||||
|
||||
if c.Err == nil && h.requireSession {
|
||||
c.SessionRequired()
|
||||
}
|
||||
|
||||
if c.Err == nil && h.requireMfa {
|
||||
c.MfaRequired()
|
||||
}
|
||||
|
||||
if c.Err == nil {
|
||||
h.handleFunc(c, w, r)
|
||||
}
|
||||
|
||||
// Handle errors that have occured
|
||||
if c.Err != nil {
|
||||
c.Err.Translate(c.T)
|
||||
c.Err.RequestId = c.RequestId
|
||||
c.LogError(c.Err)
|
||||
c.Err.Where = r.URL.Path
|
||||
|
||||
// Block out detailed error when not in developer mode
|
||||
if !*utils.Cfg.ServiceSettings.EnableDeveloper {
|
||||
c.Err.DetailedError = ""
|
||||
}
|
||||
|
||||
w.WriteHeader(c.Err.StatusCode)
|
||||
w.Write([]byte(c.Err.ToJson()))
|
||||
|
||||
if einterfaces.GetMetricsInterface() != nil {
|
||||
einterfaces.GetMetricsInterface().IncrementHttpError()
|
||||
}
|
||||
}
|
||||
|
||||
if einterfaces.GetMetricsInterface() != nil {
|
||||
einterfaces.GetMetricsInterface().IncrementHttpRequest()
|
||||
|
||||
if r.URL.Path != model.API_URL_SUFFIX+"/users/websocket" {
|
||||
elapsed := float64(time.Since(now)) / float64(time.Second)
|
||||
einterfaces.GetMetricsInterface().ObserveHttpRequestDuration(elapsed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) LogAudit(extraInfo string) {
|
||||
audit := &model.Audit{UserId: c.Session.UserId, IpAddress: c.IpAddress, Action: c.Path, ExtraInfo: extraInfo, SessionId: c.Session.Id}
|
||||
if r := <-app.Srv.Store.Audit().Save(audit); r.Err != nil {
|
||||
c.LogError(r.Err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) LogAuditWithUserId(userId, extraInfo string) {
|
||||
|
||||
if len(c.Session.UserId) > 0 {
|
||||
extraInfo = strings.TrimSpace(extraInfo + " session_user=" + c.Session.UserId)
|
||||
}
|
||||
|
||||
audit := &model.Audit{UserId: userId, IpAddress: c.IpAddress, Action: c.Path, ExtraInfo: extraInfo, SessionId: c.Session.Id}
|
||||
if r := <-app.Srv.Store.Audit().Save(audit); r.Err != nil {
|
||||
c.LogError(r.Err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) LogError(err *model.AppError) {
|
||||
|
||||
// filter out endless reconnects
|
||||
if c.Path == "/api/v3/users/websocket" && err.StatusCode == 401 || err.Id == "web.check_browser_compatibility.app_error" {
|
||||
c.LogDebug(err)
|
||||
} else {
|
||||
l4g.Error(utils.T("api.context.log.error"), c.Path, err.Where, err.StatusCode,
|
||||
c.RequestId, c.Session.UserId, c.IpAddress, err.SystemMessage(utils.T), err.DetailedError)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) LogDebug(err *model.AppError) {
|
||||
l4g.Debug(utils.T("api.context.log.error"), c.Path, err.Where, err.StatusCode,
|
||||
c.RequestId, c.Session.UserId, c.IpAddress, err.SystemMessage(utils.T), err.DetailedError)
|
||||
}
|
||||
|
||||
func (c *Context) IsSystemAdmin() bool {
|
||||
return app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM)
|
||||
}
|
||||
|
||||
func (c *Context) SessionRequired() {
|
||||
if len(c.Session.UserId) == 0 {
|
||||
c.Err = model.NewLocAppError("", "api.context.session_expired.app_error", nil, "UserRequired")
|
||||
c.Err.StatusCode = http.StatusUnauthorized
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) MfaRequired() {
|
||||
// Must be licensed for MFA and have it configured for enforcement
|
||||
if !utils.IsLicensed || !*utils.License.Features.MFA || !*utils.Cfg.ServiceSettings.EnableMultifactorAuthentication || !*utils.Cfg.ServiceSettings.EnforceMultifactorAuthentication {
|
||||
return
|
||||
}
|
||||
|
||||
// OAuth integrations are excepted
|
||||
if c.Session.IsOAuth {
|
||||
return
|
||||
}
|
||||
|
||||
if user, err := app.GetUser(c.Session.UserId); err != nil {
|
||||
c.Err = model.NewLocAppError("", "api.context.session_expired.app_error", nil, "MfaRequired")
|
||||
c.Err.StatusCode = http.StatusUnauthorized
|
||||
return
|
||||
} else {
|
||||
// Only required for email and ldap accounts
|
||||
if user.AuthService != "" &&
|
||||
user.AuthService != model.USER_AUTH_SERVICE_EMAIL &&
|
||||
user.AuthService != model.USER_AUTH_SERVICE_LDAP {
|
||||
return
|
||||
}
|
||||
|
||||
if !user.MfaActive {
|
||||
c.Err = model.NewLocAppError("", "api.context.mfa_required.app_error", nil, "MfaRequired")
|
||||
c.Err.StatusCode = http.StatusUnauthorized
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) RemoveSessionCookie(w http.ResponseWriter, r *http.Request) {
|
||||
cookie := &http.Cookie{
|
||||
Name: model.SESSION_COOKIE_TOKEN,
|
||||
Value: "",
|
||||
Path: "/",
|
||||
MaxAge: -1,
|
||||
HttpOnly: true,
|
||||
}
|
||||
|
||||
http.SetCookie(w, cookie)
|
||||
}
|
||||
|
||||
func (c *Context) SetInvalidParam(parameter string) {
|
||||
c.Err = NewInvalidParamError(parameter)
|
||||
}
|
||||
|
||||
func (c *Context) SetInvalidUrlParam(parameter string) {
|
||||
c.Err = NewInvalidUrlParamError(parameter)
|
||||
}
|
||||
|
||||
func NewInvalidParamError(parameter string) *model.AppError {
|
||||
err := model.NewLocAppError("Context", "api.context.invalid_body_param.app_error", map[string]interface{}{"Name": parameter}, "")
|
||||
err.StatusCode = http.StatusBadRequest
|
||||
return err
|
||||
}
|
||||
func NewInvalidUrlParamError(parameter string) *model.AppError {
|
||||
err := model.NewLocAppError("Context", "api.context.invalid_url_param.app_error", map[string]interface{}{"Name": parameter}, "")
|
||||
err.StatusCode = http.StatusBadRequest
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Context) SetPermissionError(permission *model.Permission) {
|
||||
c.Err = model.NewLocAppError("Permissions", "api.context.permissions.app_error", nil, "userId="+c.Session.UserId+", "+"permission="+permission.Id)
|
||||
c.Err.StatusCode = http.StatusForbidden
|
||||
}
|
||||
|
||||
func (c *Context) SetSiteURL(url string) {
|
||||
c.siteURL = strings.TrimRight(url, "/")
|
||||
}
|
||||
|
||||
func (c *Context) GetSiteURL() string {
|
||||
return c.siteURL
|
||||
}
|
||||
|
||||
func (c *Context) RequireUserId() *Context {
|
||||
if c.Err != nil {
|
||||
return c
|
||||
}
|
||||
|
||||
if len(c.Params.UserId) != 26 {
|
||||
c.SetInvalidUrlParam("user_id")
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Context) RequireTeamId() *Context {
|
||||
if c.Err != nil {
|
||||
return c
|
||||
}
|
||||
|
||||
if len(c.Params.TeamId) != 26 {
|
||||
c.SetInvalidUrlParam("team_id")
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Context) RequireChannelId() *Context {
|
||||
if c.Err != nil {
|
||||
return c
|
||||
}
|
||||
|
||||
if len(c.Params.ChannelId) != 26 {
|
||||
c.SetInvalidUrlParam("channel_id")
|
||||
}
|
||||
return c
|
||||
}
|
||||
61
api4/params.go
Обычный файл
61
api4/params.go
Обычный файл
@@ -0,0 +1,61 @@
|
||||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package api4
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
type ApiParams struct {
|
||||
UserId string
|
||||
TeamId string
|
||||
ChannelId string
|
||||
PostId string
|
||||
FileId string
|
||||
CommandId string
|
||||
HookId string
|
||||
EmojiId string
|
||||
}
|
||||
|
||||
func ApiParamsFromRequest(r *http.Request) *ApiParams {
|
||||
params := &ApiParams{}
|
||||
|
||||
props := mux.Vars(r)
|
||||
|
||||
if val, ok := props["user_id"]; ok {
|
||||
params.UserId = val
|
||||
}
|
||||
|
||||
if val, ok := props["team_id"]; ok {
|
||||
params.TeamId = val
|
||||
}
|
||||
|
||||
if val, ok := props["channel_id"]; ok {
|
||||
params.ChannelId = val
|
||||
}
|
||||
|
||||
if val, ok := props["post_id"]; ok {
|
||||
params.PostId = val
|
||||
}
|
||||
|
||||
if val, ok := props["file_id"]; ok {
|
||||
params.FileId = val
|
||||
}
|
||||
|
||||
if val, ok := props["command_id"]; ok {
|
||||
params.CommandId = val
|
||||
}
|
||||
|
||||
if val, ok := props["hook_id"]; ok {
|
||||
params.HookId = val
|
||||
}
|
||||
|
||||
if val, ok := props["emoji_id"]; ok {
|
||||
params.EmojiId = val
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
169
api4/user.go
Обычный файл
169
api4/user.go
Обычный файл
@@ -0,0 +1,169 @@
|
||||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package api4
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
"github.com/mattermost/platform/app"
|
||||
"github.com/mattermost/platform/model"
|
||||
"github.com/mattermost/platform/utils"
|
||||
)
|
||||
|
||||
func InitUser() {
|
||||
l4g.Debug(utils.T("api.user.init.debug"))
|
||||
|
||||
BaseRoutes.Users.Handle("", ApiHandler(createUser)).Methods("POST")
|
||||
BaseRoutes.User.Handle("", ApiSessionRequired(getUser)).Methods("GET")
|
||||
BaseRoutes.User.Handle("", ApiSessionRequired(updateUser)).Methods("PUT")
|
||||
|
||||
BaseRoutes.Users.Handle("/login", ApiHandler(login)).Methods("POST")
|
||||
BaseRoutes.Users.Handle("/logout", ApiHandler(logout)).Methods("POST")
|
||||
|
||||
}
|
||||
|
||||
func createUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
user := model.UserFromJson(r.Body)
|
||||
if user == nil {
|
||||
c.SetInvalidParam("user")
|
||||
return
|
||||
}
|
||||
|
||||
hash := r.URL.Query().Get("h")
|
||||
inviteId := r.URL.Query().Get("iid")
|
||||
|
||||
// No permission check required
|
||||
|
||||
var ruser *model.User
|
||||
var err *model.AppError
|
||||
if len(hash) > 0 {
|
||||
ruser, err = app.CreateUserWithHash(user, hash, r.URL.Query().Get("d"))
|
||||
} else if len(inviteId) > 0 {
|
||||
ruser, err = app.CreateUserWithInviteId(user, inviteId, c.GetSiteURL())
|
||||
} else {
|
||||
ruser, err = app.CreateUserFromSignup(user, c.GetSiteURL())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Write([]byte(ruser.ToJson()))
|
||||
}
|
||||
|
||||
func getUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequireUserId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// No permission check required
|
||||
|
||||
var user *model.User
|
||||
var err *model.AppError
|
||||
|
||||
if user, err = app.GetUser(c.Params.UserId); err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
etag := user.Etag(utils.Cfg.PrivacySettings.ShowFullName, utils.Cfg.PrivacySettings.ShowEmailAddress)
|
||||
|
||||
if HandleEtag(etag, "Get User", w, r) {
|
||||
return
|
||||
} else {
|
||||
app.SanitizeProfile(user, c.IsSystemAdmin())
|
||||
w.Header().Set(model.HEADER_ETAG_SERVER, etag)
|
||||
w.Write([]byte(user.ToJson()))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func updateUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequireUserId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
user := model.UserFromJson(r.Body)
|
||||
if user == nil {
|
||||
c.SetInvalidParam("user")
|
||||
return
|
||||
}
|
||||
|
||||
if !app.SessionHasPermissionToUser(c.Session, user.Id) {
|
||||
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
|
||||
return
|
||||
}
|
||||
|
||||
if ruser, err := app.UpdateUserAsUser(user, c.GetSiteURL(), c.IsSystemAdmin()); err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
} else {
|
||||
c.LogAudit("")
|
||||
w.Write([]byte(ruser.ToJson()))
|
||||
}
|
||||
}
|
||||
|
||||
func login(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
props := model.MapFromJson(r.Body)
|
||||
|
||||
id := props["id"]
|
||||
loginId := props["login_id"]
|
||||
password := props["password"]
|
||||
mfaToken := props["token"]
|
||||
deviceId := props["device_id"]
|
||||
ldapOnly := props["ldap_only"] == "true"
|
||||
|
||||
c.LogAuditWithUserId(id, "attempt - login_id="+loginId)
|
||||
user, err := app.AuthenticateUserForLogin(id, loginId, password, mfaToken, deviceId, ldapOnly)
|
||||
if err != nil {
|
||||
c.LogAuditWithUserId(id, "failure - login_id="+loginId)
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
c.LogAuditWithUserId(user.Id, "authenticated")
|
||||
|
||||
var session *model.Session
|
||||
session, err = app.DoLogin(w, r, user, deviceId)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
c.LogAuditWithUserId(user.Id, "success")
|
||||
|
||||
c.Session = *session
|
||||
|
||||
user.Sanitize(map[string]bool{})
|
||||
|
||||
w.Write([]byte(user.ToJson()))
|
||||
}
|
||||
|
||||
func logout(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
data := make(map[string]string)
|
||||
data["user_id"] = c.Session.UserId
|
||||
|
||||
Logout(c, w, r)
|
||||
if c.Err == nil {
|
||||
w.Write([]byte(model.MapToJson(data)))
|
||||
}
|
||||
}
|
||||
|
||||
func Logout(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.LogAudit("")
|
||||
c.RemoveSessionCookie(w, r)
|
||||
if c.Session.Id != "" {
|
||||
if err := app.RevokeSessionById(c.Session.Id); err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
186
api4/user_test.go
Обычный файл
186
api4/user_test.go
Обычный файл
@@ -0,0 +1,186 @@
|
||||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package api4
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/platform/model"
|
||||
"github.com/mattermost/platform/utils"
|
||||
)
|
||||
|
||||
func TestCreateUser(t *testing.T) {
|
||||
th := Setup()
|
||||
Client := th.Client
|
||||
|
||||
user := model.User{Email: GenerateTestEmail(), Nickname: "Corey Hulen", Password: "hello1", Username: GenerateTestUsername(), Roles: model.ROLE_SYSTEM_ADMIN.Id + " " + model.ROLE_SYSTEM_USER.Id}
|
||||
|
||||
ruser, resp := Client.CreateUser(&user)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
Client.Login(user.Email, user.Password)
|
||||
|
||||
if ruser.Nickname != user.Nickname {
|
||||
t.Fatal("nickname didn't match")
|
||||
}
|
||||
|
||||
if ruser.Roles != model.ROLE_SYSTEM_USER.Id {
|
||||
t.Fatal("did not clear roles")
|
||||
}
|
||||
|
||||
CheckUserSanitization(t, ruser)
|
||||
|
||||
_, resp = Client.CreateUser(ruser)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
ruser.Id = ""
|
||||
ruser.Username = GenerateTestUsername()
|
||||
ruser.Password = "passwd1"
|
||||
_, resp = Client.CreateUser(ruser)
|
||||
CheckErrorMessage(t, resp, "An account with that email already exists.")
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
ruser.Email = GenerateTestEmail()
|
||||
ruser.Username = user.Username
|
||||
_, resp = Client.CreateUser(ruser)
|
||||
CheckErrorMessage(t, resp, "An account with that username already exists.")
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
ruser.Email = ""
|
||||
_, resp = Client.CreateUser(ruser)
|
||||
CheckErrorMessage(t, resp, "Invalid email")
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
if r, err := Client.DoApiPost("/users", "garbage"); err == nil {
|
||||
t.Fatal("should have errored")
|
||||
} else {
|
||||
if r.StatusCode != http.StatusBadRequest {
|
||||
t.Log("actual: " + strconv.Itoa(r.StatusCode))
|
||||
t.Log("expected: " + strconv.Itoa(http.StatusBadRequest))
|
||||
t.Fatal("wrong status code")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetUser(t *testing.T) {
|
||||
th := Setup().InitBasic().InitSystemAdmin()
|
||||
Client := th.Client
|
||||
|
||||
user := th.CreateUser()
|
||||
|
||||
ruser, resp := Client.GetUser(user.Id, "")
|
||||
CheckNoError(t, resp)
|
||||
CheckUserSanitization(t, ruser)
|
||||
|
||||
if ruser.Email != user.Email {
|
||||
t.Fatal("emails did not match")
|
||||
}
|
||||
|
||||
ruser, resp = Client.GetUser(user.Id, resp.Etag)
|
||||
CheckEtag(t, ruser, resp)
|
||||
|
||||
_, resp = Client.GetUser("junk", "")
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
_, resp = Client.GetUser(model.NewId(), "")
|
||||
CheckNotFoundStatus(t, resp)
|
||||
|
||||
// Check against privacy config settings
|
||||
emailPrivacy := utils.Cfg.PrivacySettings.ShowEmailAddress
|
||||
namePrivacy := utils.Cfg.PrivacySettings.ShowFullName
|
||||
defer func() {
|
||||
utils.Cfg.PrivacySettings.ShowEmailAddress = emailPrivacy
|
||||
utils.Cfg.PrivacySettings.ShowFullName = namePrivacy
|
||||
}()
|
||||
utils.Cfg.PrivacySettings.ShowEmailAddress = false
|
||||
utils.Cfg.PrivacySettings.ShowFullName = false
|
||||
|
||||
ruser, resp = Client.GetUser(user.Id, "")
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if ruser.Email != "" {
|
||||
t.Fatal("email should be blank")
|
||||
}
|
||||
if ruser.FirstName != "" {
|
||||
t.Fatal("first name should be blank")
|
||||
}
|
||||
if ruser.LastName != "" {
|
||||
t.Fatal("last name should be blank")
|
||||
}
|
||||
|
||||
Client.Logout()
|
||||
_, resp = Client.GetUser(user.Id, "")
|
||||
CheckUnauthorizedStatus(t, resp)
|
||||
|
||||
// System admins should ignore privacy settings
|
||||
th.LoginSystemAdmin()
|
||||
ruser, resp = Client.GetUser(user.Id, resp.Etag)
|
||||
if ruser.Email == "" {
|
||||
t.Fatal("email should not be blank")
|
||||
}
|
||||
if ruser.FirstName == "" {
|
||||
t.Fatal("first name should not be blank")
|
||||
}
|
||||
if ruser.LastName == "" {
|
||||
t.Fatal("last name should not be blank")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateUser(t *testing.T) {
|
||||
th := Setup().InitBasic().InitSystemAdmin()
|
||||
Client := th.Client
|
||||
|
||||
user := th.CreateUser()
|
||||
Client.Login(user.Email, user.Password)
|
||||
|
||||
user.Nickname = "Joram Wilander"
|
||||
user.Roles = model.ROLE_SYSTEM_ADMIN.Id
|
||||
user.LastPasswordUpdate = 123
|
||||
|
||||
ruser, resp := Client.UpdateUser(user)
|
||||
CheckNoError(t, resp)
|
||||
CheckUserSanitization(t, ruser)
|
||||
|
||||
if ruser.Nickname != "Joram Wilander" {
|
||||
t.Fatal("Nickname did not update properly")
|
||||
}
|
||||
if ruser.Roles != model.ROLE_SYSTEM_USER.Id {
|
||||
t.Fatal("Roles should not have updated")
|
||||
}
|
||||
if ruser.LastPasswordUpdate == 123 {
|
||||
t.Fatal("LastPasswordUpdate should not have updated")
|
||||
}
|
||||
|
||||
ruser.Id = "junk"
|
||||
_, resp = Client.UpdateUser(ruser)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
ruser.Id = model.NewId()
|
||||
_, resp = Client.UpdateUser(ruser)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
|
||||
if r, err := Client.DoApiPut("/users/"+ruser.Id, "garbage"); err == nil {
|
||||
t.Fatal("should have errored")
|
||||
} else {
|
||||
if r.StatusCode != http.StatusBadRequest {
|
||||
t.Log("actual: " + strconv.Itoa(r.StatusCode))
|
||||
t.Log("expected: " + strconv.Itoa(http.StatusBadRequest))
|
||||
t.Fatal("wrong status code")
|
||||
}
|
||||
}
|
||||
|
||||
Client.Logout()
|
||||
_, resp = Client.UpdateUser(user)
|
||||
CheckUnauthorizedStatus(t, resp)
|
||||
|
||||
th.LoginBasic()
|
||||
_, resp = Client.UpdateUser(user)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
|
||||
th.LoginSystemAdmin()
|
||||
_, resp = Client.UpdateUser(user)
|
||||
CheckNoError(t, resp)
|
||||
}
|
||||
Ссылка в новой задаче
Block a user