Implement APIv4 infrastructure (#5191)
* Implement APIv4 infrastructure * Update parameter requirement functions per feedback
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
3e2f879b77
Коммит
c01d9ad6cf
4
Makefile
4
Makefile
@@ -203,6 +203,7 @@ test-server: start-docker prepare-enterprise
|
||||
echo "mode: count" > cover.out
|
||||
|
||||
$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=650s -covermode=count -coverprofile=capi.out ./api || exit 1
|
||||
$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=650s -covermode=count -coverprofile=capi4.out ./api4 || exit 1
|
||||
$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=60s -covermode=count -coverprofile=capp.out ./app || exit 1
|
||||
$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=60s -covermode=count -coverprofile=cmodel.out ./model || exit 1
|
||||
$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=180s -covermode=count -coverprofile=cstore.out ./store || exit 1
|
||||
@@ -210,12 +211,13 @@ test-server: start-docker prepare-enterprise
|
||||
$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=120s -covermode=count -coverprofile=cweb.out ./web || exit 1
|
||||
|
||||
tail -n +2 capi.out >> cover.out
|
||||
tail -n +2 capi4.out >> cover.out
|
||||
tail -n +2 capp.out >> cover.out
|
||||
tail -n +2 cmodel.out >> cover.out
|
||||
tail -n +2 cstore.out >> cover.out
|
||||
tail -n +2 cutils.out >> cover.out
|
||||
tail -n +2 cweb.out >> cover.out
|
||||
rm -f capi.out capp.out cmodel.out cstore.out cutils.out cweb.out
|
||||
rm -f capi.out capi4.out capp.out cmodel.out cstore.out cutils.out cweb.out
|
||||
|
||||
ifeq ($(BUILD_ENTERPRISE_READY),true)
|
||||
@echo Running Enterprise tests
|
||||
|
||||
@@ -67,7 +67,7 @@ func InitRouter() {
|
||||
func InitApi() {
|
||||
BaseRoutes = &Routes{}
|
||||
BaseRoutes.Root = app.Srv.Router
|
||||
BaseRoutes.ApiRoot = app.Srv.Router.PathPrefix(model.API_URL_SUFFIX).Subrouter()
|
||||
BaseRoutes.ApiRoot = app.Srv.Router.PathPrefix(model.API_URL_SUFFIX_V3).Subrouter()
|
||||
BaseRoutes.Users = BaseRoutes.ApiRoot.PathPrefix("/users").Subrouter()
|
||||
BaseRoutes.NeedUser = BaseRoutes.Users.PathPrefix("/{user_id:[A-Za-z0-9]+}").Subrouter()
|
||||
BaseRoutes.Teams = BaseRoutes.ApiRoot.PathPrefix("/teams").Subrouter()
|
||||
|
||||
@@ -239,7 +239,7 @@ func TestTestCommand(t *testing.T) {
|
||||
*utils.Cfg.ServiceSettings.EnableCommands = true
|
||||
|
||||
cmd1 := &model.Command{
|
||||
URL: "http://localhost" + utils.Cfg.ServiceSettings.ListenAddress + model.API_URL_SUFFIX + "/teams/command_test",
|
||||
URL: "http://localhost" + utils.Cfg.ServiceSettings.ListenAddress + model.API_URL_SUFFIX_V3 + "/teams/command_test",
|
||||
Method: model.COMMAND_METHOD_POST,
|
||||
Trigger: "test",
|
||||
}
|
||||
@@ -259,7 +259,7 @@ func TestTestCommand(t *testing.T) {
|
||||
}
|
||||
|
||||
cmd2 := &model.Command{
|
||||
URL: "http://localhost" + utils.Cfg.ServiceSettings.ListenAddress + model.API_URL_SUFFIX + "/teams/command_test",
|
||||
URL: "http://localhost" + utils.Cfg.ServiceSettings.ListenAddress + model.API_URL_SUFFIX_V3 + "/teams/command_test",
|
||||
Method: model.COMMAND_METHOD_GET,
|
||||
Trigger: "test2",
|
||||
}
|
||||
|
||||
@@ -21,17 +21,18 @@ import (
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
Session model.Session
|
||||
RequestId string
|
||||
IpAddress string
|
||||
Path string
|
||||
Err *model.AppError
|
||||
siteURL string
|
||||
teamURLValid bool
|
||||
teamURL string
|
||||
T goi18n.TranslateFunc
|
||||
Locale string
|
||||
TeamId string
|
||||
Session model.Session
|
||||
RequestId string
|
||||
IpAddress string
|
||||
Path string
|
||||
Err *model.AppError
|
||||
siteURL string
|
||||
teamURLValid bool
|
||||
teamURL string
|
||||
T goi18n.TranslateFunc
|
||||
Locale string
|
||||
TeamId string
|
||||
isSystemAdmin bool
|
||||
}
|
||||
|
||||
func ApiAppHandler(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
|
||||
@@ -142,7 +143,7 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if utils.GetSiteURL() == "" {
|
||||
protocol := GetProtocol(r)
|
||||
protocol := app.GetProtocol(r)
|
||||
c.SetSiteURL(protocol + "://" + r.Host)
|
||||
} else {
|
||||
c.SetSiteURL(utils.GetSiteURL())
|
||||
@@ -251,21 +252,13 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if h.isApi && einterfaces.GetMetricsInterface() != nil {
|
||||
einterfaces.GetMetricsInterface().IncrementHttpRequest()
|
||||
|
||||
if r.URL.Path != model.API_URL_SUFFIX+"/users/websocket" {
|
||||
if r.URL.Path != model.API_URL_SUFFIX_V3+"/users/websocket" {
|
||||
elapsed := float64(time.Since(now)) / float64(time.Second)
|
||||
einterfaces.GetMetricsInterface().ObserveHttpRequestDuration(elapsed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetProtocol(r *http.Request) string {
|
||||
if r.Header.Get(model.HEADER_FORWARDED_PROTO) == "https" {
|
||||
return "https"
|
||||
} else {
|
||||
return "http"
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -347,13 +340,17 @@ func (c *Context) SystemAdminRequired() {
|
||||
c.Err = model.NewLocAppError("", "api.context.session_expired.app_error", nil, "SystemAdminRequired")
|
||||
c.Err.StatusCode = http.StatusUnauthorized
|
||||
return
|
||||
} else if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) {
|
||||
} else if !c.IsSystemAdmin() {
|
||||
c.Err = model.NewLocAppError("", "api.context.permissions.app_error", nil, "AdminRequired")
|
||||
c.Err.StatusCode = http.StatusForbidden
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) IsSystemAdmin() bool {
|
||||
return app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM)
|
||||
}
|
||||
|
||||
func (c *Context) RemoveSessionCookie(w http.ResponseWriter, r *http.Request) {
|
||||
cookie := &http.Cookie{
|
||||
Name: model.SESSION_COOKIE_TOKEN,
|
||||
|
||||
@@ -406,6 +406,7 @@ func TestGetPublicFile(t *testing.T) {
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
if resp, err := http.Get(link); err != nil || resp.StatusCode != http.StatusOK {
|
||||
t.Log(link)
|
||||
t.Fatal("failed to get image with public link", err)
|
||||
}
|
||||
|
||||
@@ -509,7 +510,7 @@ func TestGetPublicFileOld(t *testing.T) {
|
||||
|
||||
func generatePublicLinkOld(siteURL, teamId, channelId, userId, filename string) string {
|
||||
hash := app.GeneratePublicLinkHash(filename, *utils.Cfg.FileSettings.PublicLinkSalt)
|
||||
return fmt.Sprintf("%s%s/public/files/get/%s/%s/%s/%s?h=%s", siteURL, model.API_URL_SUFFIX, teamId, channelId, userId, filename, hash)
|
||||
return fmt.Sprintf("%s%s/public/files/get/%s/%s/%s/%s?h=%s", siteURL, model.API_URL_SUFFIX_V3, teamId, channelId, userId, filename, hash)
|
||||
}
|
||||
|
||||
func TestGetPublicLink(t *testing.T) {
|
||||
|
||||
10
api/oauth.go
10
api/oauth.go
@@ -291,7 +291,7 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
doLogin(c, w, r, user, "")
|
||||
}
|
||||
if c.Err == nil {
|
||||
http.Redirect(w, r, GetProtocol(r)+"://"+r.Host, http.StatusTemporaryRedirect)
|
||||
http.Redirect(w, r, app.GetProtocol(r)+"://"+r.Host, http.StatusTemporaryRedirect)
|
||||
}
|
||||
break
|
||||
case model.OAUTH_ACTION_LOGIN:
|
||||
@@ -304,25 +304,25 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, c.GetSiteURL()+val, http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, GetProtocol(r)+"://"+r.Host, http.StatusTemporaryRedirect)
|
||||
http.Redirect(w, r, app.GetProtocol(r)+"://"+r.Host, http.StatusTemporaryRedirect)
|
||||
}
|
||||
break
|
||||
case model.OAUTH_ACTION_EMAIL_TO_SSO:
|
||||
CompleteSwitchWithOAuth(c, w, r, service, body, props["email"])
|
||||
if c.Err == nil {
|
||||
http.Redirect(w, r, GetProtocol(r)+"://"+r.Host+"/login?extra=signin_change", http.StatusTemporaryRedirect)
|
||||
http.Redirect(w, r, app.GetProtocol(r)+"://"+r.Host+"/login?extra=signin_change", http.StatusTemporaryRedirect)
|
||||
}
|
||||
break
|
||||
case model.OAUTH_ACTION_SSO_TO_EMAIL:
|
||||
LoginByOAuth(c, w, r, service, body)
|
||||
if c.Err == nil {
|
||||
http.Redirect(w, r, GetProtocol(r)+"://"+r.Host+"/claim?email="+url.QueryEscape(props["email"]), http.StatusTemporaryRedirect)
|
||||
http.Redirect(w, r, app.GetProtocol(r)+"://"+r.Host+"/claim?email="+url.QueryEscape(props["email"]), http.StatusTemporaryRedirect)
|
||||
}
|
||||
break
|
||||
default:
|
||||
LoginByOAuth(c, w, r, service, body)
|
||||
if c.Err == nil {
|
||||
http.Redirect(w, r, GetProtocol(r)+"://"+r.Host, http.StatusTemporaryRedirect)
|
||||
http.Redirect(w, r, app.GetProtocol(r)+"://"+r.Host, http.StatusTemporaryRedirect)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
229
api/user.go
229
api/user.go
@@ -20,7 +20,6 @@ import (
|
||||
"github.com/mattermost/platform/model"
|
||||
"github.com/mattermost/platform/store"
|
||||
"github.com/mattermost/platform/utils"
|
||||
"github.com/mssola/user_agent"
|
||||
)
|
||||
|
||||
func InitUser() {
|
||||
@@ -77,12 +76,6 @@ func InitUser() {
|
||||
}
|
||||
|
||||
func createUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !utils.Cfg.EmailSettings.EnableSignUpWithEmail || !utils.Cfg.TeamSettings.EnableUserCreation {
|
||||
c.Err = model.NewLocAppError("createUser", "api.user.create_user.signup_email_disabled.app_error", nil, "")
|
||||
c.Err.StatusCode = http.StatusNotImplemented
|
||||
return
|
||||
}
|
||||
|
||||
user := model.UserFromJson(r.Body)
|
||||
|
||||
if user == nil {
|
||||
@@ -90,45 +83,25 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
user.EmailVerified = false
|
||||
|
||||
hash := r.URL.Query().Get("h")
|
||||
inviteId := r.URL.Query().Get("iid")
|
||||
|
||||
var ruser *model.User
|
||||
var err *model.AppError
|
||||
if len(hash) > 0 {
|
||||
data := r.URL.Query().Get("d")
|
||||
ruser, err = app.CreateUserWithHash(user, hash, data)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
ruser, err = app.CreateUserWithHash(user, hash, r.URL.Query().Get("d"))
|
||||
} else if len(inviteId) > 0 {
|
||||
ruser, err = app.CreateUserWithInviteId(user, inviteId, c.GetSiteURL())
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if !app.IsFirstUserAccount() && !*utils.Cfg.TeamSettings.EnableOpenServer {
|
||||
c.Err = model.NewLocAppError("createUser", "api.user.create_user.no_open_server", nil, "email="+user.Email)
|
||||
return
|
||||
}
|
||||
ruser, err = app.CreateUserFromSignup(user, c.GetSiteURL())
|
||||
}
|
||||
|
||||
ruser, err = app.CreateUser(user)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
if err := app.SendWelcomeEmail(ruser.Id, ruser.Email, ruser.EmailVerified, ruser.Locale, c.GetSiteURL()); err != nil {
|
||||
l4g.Error(err.Error())
|
||||
}
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
w.Write([]byte(ruser.ToJson()))
|
||||
|
||||
}
|
||||
|
||||
func login(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
@@ -141,56 +114,15 @@ func login(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
deviceId := props["device_id"]
|
||||
ldapOnly := props["ldap_only"] == "true"
|
||||
|
||||
if len(password) == 0 {
|
||||
c.Err = model.NewLocAppError("login", "api.user.login.blank_pwd.app_error", nil, "")
|
||||
c.Err.StatusCode = http.StatusBadRequest
|
||||
return
|
||||
}
|
||||
|
||||
var user *model.User
|
||||
var err *model.AppError
|
||||
|
||||
if len(id) != 0 {
|
||||
c.LogAuditWithUserId(id, "attempt")
|
||||
|
||||
if user, err = app.GetUser(id); err != nil {
|
||||
c.LogAuditWithUserId(id, "failure")
|
||||
c.Err = err
|
||||
c.Err.StatusCode = http.StatusBadRequest
|
||||
if einterfaces.GetMetricsInterface() != nil {
|
||||
einterfaces.GetMetricsInterface().IncrementLoginFail()
|
||||
}
|
||||
return
|
||||
}
|
||||
} else {
|
||||
c.LogAudit("attempt")
|
||||
|
||||
if user, err = app.GetUserForLogin(loginId, ldapOnly); err != nil {
|
||||
c.LogAudit("failure")
|
||||
c.Err = err
|
||||
if einterfaces.GetMetricsInterface() != nil {
|
||||
einterfaces.GetMetricsInterface().IncrementLoginFail()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.LogAuditWithUserId(user.Id, "attempt")
|
||||
}
|
||||
|
||||
// and then authenticate them
|
||||
if user, err = authenticateUser(user, password, mfaToken); err != nil {
|
||||
c.LogAuditWithUserId(user.Id, "failure")
|
||||
c.LogAudit("attempt - user_id=" + id + " login_id=" + loginId)
|
||||
user, err := app.AuthenticateUserForLogin(id, loginId, password, mfaToken, deviceId, ldapOnly)
|
||||
if err != nil {
|
||||
c.LogAudit("failure - user_id=" + id + " login_id=" + loginId)
|
||||
c.Err = err
|
||||
if einterfaces.GetMetricsInterface() != nil {
|
||||
einterfaces.GetMetricsInterface().IncrementLoginFail()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.LogAuditWithUserId(user.Id, "success")
|
||||
if einterfaces.GetMetricsInterface() != nil {
|
||||
einterfaces.GetMetricsInterface().IncrementLogin()
|
||||
}
|
||||
|
||||
doLogin(c, w, r, user, deviceId)
|
||||
if c.Err != nil {
|
||||
@@ -244,77 +176,12 @@ func LoginByOAuth(c *Context, w http.ResponseWriter, r *http.Request, service st
|
||||
|
||||
// User MUST be authenticated completely before calling Login
|
||||
func doLogin(c *Context, w http.ResponseWriter, r *http.Request, user *model.User, deviceId string) {
|
||||
|
||||
session := &model.Session{UserId: user.Id, Roles: user.GetRawRoles(), DeviceId: deviceId, IsOAuth: false}
|
||||
|
||||
maxAge := *utils.Cfg.ServiceSettings.SessionLengthWebInDays * 60 * 60 * 24
|
||||
|
||||
if len(deviceId) > 0 {
|
||||
session.SetExpireInDays(*utils.Cfg.ServiceSettings.SessionLengthMobileInDays)
|
||||
maxAge = *utils.Cfg.ServiceSettings.SessionLengthMobileInDays * 60 * 60 * 24
|
||||
|
||||
// A special case where we logout of all other sessions with the same Id
|
||||
if err := app.RevokeSessionsForDeviceId(user.Id, deviceId, ""); err != nil {
|
||||
c.Err = err
|
||||
c.Err.StatusCode = http.StatusInternalServerError
|
||||
return
|
||||
}
|
||||
} else {
|
||||
session.SetExpireInDays(*utils.Cfg.ServiceSettings.SessionLengthWebInDays)
|
||||
}
|
||||
|
||||
ua := user_agent.New(r.UserAgent())
|
||||
|
||||
plat := ua.Platform()
|
||||
if plat == "" {
|
||||
plat = "unknown"
|
||||
}
|
||||
|
||||
os := ua.OS()
|
||||
if os == "" {
|
||||
os = "unknown"
|
||||
}
|
||||
|
||||
bname, bversion := ua.Browser()
|
||||
if bname == "" {
|
||||
bname = "unknown"
|
||||
}
|
||||
|
||||
if bversion == "" {
|
||||
bversion = "0.0"
|
||||
}
|
||||
|
||||
session.AddProp(model.SESSION_PROP_PLATFORM, plat)
|
||||
session.AddProp(model.SESSION_PROP_OS, os)
|
||||
session.AddProp(model.SESSION_PROP_BROWSER, fmt.Sprintf("%v/%v", bname, bversion))
|
||||
|
||||
var err *model.AppError
|
||||
if session, err = app.CreateSession(session); err != nil {
|
||||
session, err := app.DoLogin(w, r, user, deviceId)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
c.Err.StatusCode = http.StatusInternalServerError
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set(model.HEADER_TOKEN, session.Token)
|
||||
|
||||
secure := false
|
||||
if GetProtocol(r) == "https" {
|
||||
secure = true
|
||||
}
|
||||
|
||||
expiresAt := time.Unix(model.GetMillis()/1000+int64(maxAge), 0)
|
||||
sessionCookie := &http.Cookie{
|
||||
Name: model.SESSION_COOKIE_TOKEN,
|
||||
Value: session.Token,
|
||||
Path: "/",
|
||||
MaxAge: maxAge,
|
||||
Expires: expiresAt,
|
||||
HttpOnly: true,
|
||||
Secure: secure,
|
||||
}
|
||||
|
||||
http.SetCookie(w, sessionCookie)
|
||||
|
||||
c.Session = *session
|
||||
}
|
||||
|
||||
@@ -357,7 +224,7 @@ func attachDeviceId(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
maxAge := *utils.Cfg.ServiceSettings.SessionLengthMobileInDays * 60 * 60 * 24
|
||||
|
||||
secure := false
|
||||
if GetProtocol(r) == "https" {
|
||||
if app.GetProtocol(r) == "https" {
|
||||
secure = true
|
||||
}
|
||||
|
||||
@@ -502,12 +369,15 @@ func getUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if user, err = app.GetUser(id); err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
} else if HandleEtag(user.Etag(utils.Cfg.PrivacySettings.ShowFullName, utils.Cfg.PrivacySettings.ShowEmailAddress), "Get User", w, r) {
|
||||
}
|
||||
|
||||
etag := user.Etag(utils.Cfg.PrivacySettings.ShowFullName, utils.Cfg.PrivacySettings.ShowEmailAddress)
|
||||
|
||||
if HandleEtag(etag, "Get User", w, r) {
|
||||
return
|
||||
} else {
|
||||
sanitizeProfile(c, user)
|
||||
|
||||
w.Header().Set(model.HEADER_ETAG_SERVER, user.Etag(utils.Cfg.PrivacySettings.ShowFullName, utils.Cfg.PrivacySettings.ShowEmailAddress))
|
||||
app.SanitizeProfile(user, c.IsSystemAdmin())
|
||||
w.Header().Set(model.HEADER_ETAG_SERVER, etag)
|
||||
w.Write([]byte(user.ToJson()))
|
||||
return
|
||||
}
|
||||
@@ -829,24 +699,11 @@ func updateUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if ruser, err := app.UpdateUser(user, c.GetSiteURL()); err != nil {
|
||||
if ruser, err := app.UpdateUserAsUser(user, c.GetSiteURL(), c.IsSystemAdmin()); err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
} else {
|
||||
c.LogAudit("")
|
||||
|
||||
updatedUser := ruser
|
||||
updatedUser = sanitizeProfile(c, updatedUser)
|
||||
|
||||
omitUsers := make(map[string]bool, 1)
|
||||
omitUsers[user.Id] = true
|
||||
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_UPDATED, "", "", "", omitUsers)
|
||||
message.Add("user", updatedUser)
|
||||
go app.Publish(message)
|
||||
|
||||
ruser.Password = ""
|
||||
ruser.AuthData = new(string)
|
||||
*ruser.AuthData = ""
|
||||
w.Write([]byte(ruser.ToJson()))
|
||||
}
|
||||
}
|
||||
@@ -875,38 +732,8 @@ func updatePassword(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
var user *model.User
|
||||
var err *model.AppError
|
||||
|
||||
if user, err = app.GetUser(userId); err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
c.Err = model.NewLocAppError("updatePassword", "api.user.update_password.valid_account.app_error", nil, "")
|
||||
c.Err.StatusCode = http.StatusBadRequest
|
||||
return
|
||||
}
|
||||
|
||||
if user.AuthData != nil && *user.AuthData != "" {
|
||||
c.LogAudit("failed - tried to update user password who was logged in through oauth")
|
||||
c.Err = model.NewLocAppError("updatePassword", "api.user.update_password.oauth.app_error", nil, "auth_service="+user.AuthService)
|
||||
c.Err.StatusCode = http.StatusBadRequest
|
||||
return
|
||||
}
|
||||
|
||||
if err := doubleCheckPassword(user, currentPassword); err != nil {
|
||||
if err.Id == "api.user.check_user_password.invalid.app_error" {
|
||||
c.Err = model.NewLocAppError("updatePassword", "api.user.update_password.incorrect.app_error", nil, "")
|
||||
} else {
|
||||
c.Err = err
|
||||
}
|
||||
c.Err.StatusCode = http.StatusForbidden
|
||||
return
|
||||
}
|
||||
|
||||
if err := app.UpdatePasswordSendEmail(user, newPassword, c.T("api.user.update_password.menu"), c.GetSiteURL()); err != nil {
|
||||
if err := app.UpdatePasswordAsUser(userId, currentPassword, newPassword, c.GetSiteURL()); err != nil {
|
||||
c.LogAudit("failed")
|
||||
c.Err = err
|
||||
return
|
||||
} else {
|
||||
@@ -1110,7 +937,7 @@ func emailToOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := checkPasswordAndAllCriteria(user, password, mfaToken); err != nil {
|
||||
if err := app.CheckPasswordAndAllCriteria(user, password, mfaToken); err != nil {
|
||||
c.LogAuditWithUserId(user.Id, "failed - bad authentication")
|
||||
c.Err = err
|
||||
return
|
||||
@@ -1238,7 +1065,7 @@ func emailToLdap(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := checkPasswordAndAllCriteria(user, emailPassword, token); err != nil {
|
||||
if err := app.CheckPasswordAndAllCriteria(user, emailPassword, token); err != nil {
|
||||
c.LogAuditWithUserId(user.Id, "failed - bad authentication")
|
||||
c.Err = err
|
||||
return
|
||||
@@ -1332,7 +1159,7 @@ func ldapToEmail(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := checkUserMfa(user, token); err != nil {
|
||||
if err := app.CheckUserMfa(user, token); err != nil {
|
||||
c.LogAuditWithUserId(user.Id, "fail - mfa token failed")
|
||||
c.Err = err
|
||||
return
|
||||
@@ -1600,7 +1427,7 @@ func completeSaml(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.Err.StatusCode = http.StatusFound
|
||||
return
|
||||
} else {
|
||||
if err := checkUserAdditionalAuthenticationCriteria(user, ""); err != nil {
|
||||
if err := app.CheckUserAdditionalAuthenticationCriteria(user, ""); err != nil {
|
||||
c.Err = err
|
||||
c.Err.StatusCode = http.StatusFound
|
||||
return
|
||||
@@ -1635,7 +1462,7 @@ func completeSaml(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, c.GetSiteURL()+val, http.StatusFound)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, GetProtocol(r)+"://"+r.Host, http.StatusFound)
|
||||
http.Redirect(w, r, app.GetProtocol(r)+"://"+r.Host, http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -634,7 +634,7 @@ func TestGetAudits(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
|
||||
if len(result.Data.(model.Audits)) != 2 {
|
||||
if len(result.Data.(model.Audits)) != 1 {
|
||||
t.Fatal(result.Data.(model.Audits))
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package api
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/mattermost/platform/app"
|
||||
"github.com/mattermost/platform/einterfaces"
|
||||
"github.com/mattermost/platform/model"
|
||||
"github.com/mattermost/platform/utils"
|
||||
@@ -13,8 +12,8 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func checkPasswordAndAllCriteria(user *model.User, password string, mfaToken string) *model.AppError {
|
||||
if err := checkUserAdditionalAuthenticationCriteria(user, mfaToken); err != nil {
|
||||
func CheckPasswordAndAllCriteria(user *model.User, password string, mfaToken string) *model.AppError {
|
||||
if err := CheckUserAdditionalAuthenticationCriteria(user, mfaToken); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -40,13 +39,13 @@ func doubleCheckPassword(user *model.User, password string) *model.AppError {
|
||||
|
||||
func checkUserPassword(user *model.User, password string) *model.AppError {
|
||||
if !model.ComparePassword(user.Password, password) {
|
||||
if result := <-app.Srv.Store.User().UpdateFailedPasswordAttempts(user.Id, user.FailedAttempts+1); result.Err != nil {
|
||||
if result := <-Srv.Store.User().UpdateFailedPasswordAttempts(user.Id, user.FailedAttempts+1); result.Err != nil {
|
||||
return result.Err
|
||||
}
|
||||
|
||||
return model.NewLocAppError("checkUserPassword", "api.user.check_user_password.invalid.app_error", nil, "user_id="+user.Id)
|
||||
} else {
|
||||
if result := <-app.Srv.Store.User().UpdateFailedPasswordAttempts(user.Id, 0); result.Err != nil {
|
||||
if result := <-Srv.Store.User().UpdateFailedPasswordAttempts(user.Id, 0); result.Err != nil {
|
||||
return result.Err
|
||||
}
|
||||
|
||||
@@ -71,7 +70,7 @@ func checkLdapUserPasswordAndAllCriteria(ldapId *string, password string, mfaTok
|
||||
user = ldapUser
|
||||
}
|
||||
|
||||
if err := checkUserMfa(user, mfaToken); err != nil {
|
||||
if err := CheckUserMfa(user, mfaToken); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -83,8 +82,8 @@ func checkLdapUserPasswordAndAllCriteria(ldapId *string, password string, mfaTok
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func checkUserAdditionalAuthenticationCriteria(user *model.User, mfaToken string) *model.AppError {
|
||||
if err := checkUserMfa(user, mfaToken); err != nil {
|
||||
func CheckUserAdditionalAuthenticationCriteria(user *model.User, mfaToken string) *model.AppError {
|
||||
if err := CheckUserMfa(user, mfaToken); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -103,7 +102,7 @@ func checkUserAdditionalAuthenticationCriteria(user *model.User, mfaToken string
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkUserMfa(user *model.User, token string) *model.AppError {
|
||||
func CheckUserMfa(user *model.User, token string) *model.AppError {
|
||||
if !user.MfaActive || !utils.IsLicensed || !*utils.License.Features.MFA || !*utils.Cfg.ServiceSettings.EnableMultifactorAuthentication {
|
||||
return nil
|
||||
}
|
||||
@@ -168,7 +167,7 @@ func authenticateUser(user *model.User, password, mfaToken string) (*model.User,
|
||||
err.StatusCode = http.StatusBadRequest
|
||||
return user, err
|
||||
} else {
|
||||
if err := checkPasswordAndAllCriteria(user, password, mfaToken); err != nil {
|
||||
if err := CheckPasswordAndAllCriteria(user, password, mfaToken); err != nil {
|
||||
err.StatusCode = http.StatusUnauthorized
|
||||
return user, err
|
||||
} else {
|
||||
@@ -5,13 +5,13 @@ package app
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "image/gif"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
_ "image/gif"
|
||||
"image/jpeg"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -24,9 +24,9 @@ import (
|
||||
"sync"
|
||||
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/mattermost/platform/model"
|
||||
"github.com/mattermost/platform/utils"
|
||||
"github.com/disintegration/imaging"
|
||||
s3 "github.com/minio/minio-go"
|
||||
"github.com/rwcarlsen/goexif/exif"
|
||||
_ "golang.org/x/image/bmp"
|
||||
@@ -359,7 +359,7 @@ func MigrateFilenamesToFileInfos(post *model.Post) []*model.FileInfo {
|
||||
|
||||
func GeneratePublicLink(siteURL string, info *model.FileInfo) string {
|
||||
hash := GeneratePublicLinkHash(info.Id, *utils.Cfg.FileSettings.PublicLinkSalt)
|
||||
return fmt.Sprintf("%s%s/public/files/%v/get?h=%s", siteURL, model.API_URL_SUFFIX, info.Id, hash)
|
||||
return fmt.Sprintf("%s%s/public/files/%v/get?h=%s", siteURL, model.API_URL_SUFFIX_V3, info.Id, hash)
|
||||
}
|
||||
|
||||
func GeneratePublicLinkHash(fileId, salt string) string {
|
||||
|
||||
136
app/login.go
Обычный файл
136
app/login.go
Обычный файл
@@ -0,0 +1,136 @@
|
||||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/platform/einterfaces"
|
||||
"github.com/mattermost/platform/model"
|
||||
"github.com/mattermost/platform/utils"
|
||||
"github.com/mssola/user_agent"
|
||||
)
|
||||
|
||||
func AuthenticateUserForLogin(id, loginId, password, mfaToken, deviceId string, ldapOnly bool) (*model.User, *model.AppError) {
|
||||
if len(password) == 0 {
|
||||
err := model.NewLocAppError("AuthenticateUserForLogin", "api.user.login.blank_pwd.app_error", nil, "")
|
||||
err.StatusCode = http.StatusBadRequest
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var user *model.User
|
||||
var err *model.AppError
|
||||
|
||||
if len(id) != 0 {
|
||||
if user, err = GetUser(id); err != nil {
|
||||
err.StatusCode = http.StatusBadRequest
|
||||
if einterfaces.GetMetricsInterface() != nil {
|
||||
einterfaces.GetMetricsInterface().IncrementLoginFail()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if user, err = GetUserForLogin(loginId, ldapOnly); err != nil {
|
||||
if einterfaces.GetMetricsInterface() != nil {
|
||||
einterfaces.GetMetricsInterface().IncrementLoginFail()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// and then authenticate them
|
||||
if user, err = authenticateUser(user, password, mfaToken); err != nil {
|
||||
if einterfaces.GetMetricsInterface() != nil {
|
||||
einterfaces.GetMetricsInterface().IncrementLoginFail()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if einterfaces.GetMetricsInterface() != nil {
|
||||
einterfaces.GetMetricsInterface().IncrementLogin()
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func DoLogin(w http.ResponseWriter, r *http.Request, user *model.User, deviceId string) (*model.Session, *model.AppError) {
|
||||
session := &model.Session{UserId: user.Id, Roles: user.GetRawRoles(), DeviceId: deviceId, IsOAuth: false}
|
||||
|
||||
maxAge := *utils.Cfg.ServiceSettings.SessionLengthWebInDays * 60 * 60 * 24
|
||||
|
||||
if len(deviceId) > 0 {
|
||||
session.SetExpireInDays(*utils.Cfg.ServiceSettings.SessionLengthMobileInDays)
|
||||
|
||||
// A special case where we logout of all other sessions with the same Id
|
||||
if err := RevokeSessionsForDeviceId(user.Id, deviceId, ""); err != nil {
|
||||
err.StatusCode = http.StatusInternalServerError
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
session.SetExpireInDays(*utils.Cfg.ServiceSettings.SessionLengthWebInDays)
|
||||
}
|
||||
|
||||
ua := user_agent.New(r.UserAgent())
|
||||
|
||||
plat := ua.Platform()
|
||||
if plat == "" {
|
||||
plat = "unknown"
|
||||
}
|
||||
|
||||
os := ua.OS()
|
||||
if os == "" {
|
||||
os = "unknown"
|
||||
}
|
||||
|
||||
bname, bversion := ua.Browser()
|
||||
if bname == "" {
|
||||
bname = "unknown"
|
||||
}
|
||||
|
||||
if bversion == "" {
|
||||
bversion = "0.0"
|
||||
}
|
||||
|
||||
session.AddProp(model.SESSION_PROP_PLATFORM, plat)
|
||||
session.AddProp(model.SESSION_PROP_OS, os)
|
||||
session.AddProp(model.SESSION_PROP_BROWSER, fmt.Sprintf("%v/%v", bname, bversion))
|
||||
|
||||
var err *model.AppError
|
||||
if session, err = CreateSession(session); err != nil {
|
||||
err.StatusCode = http.StatusInternalServerError
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w.Header().Set(model.HEADER_TOKEN, session.Token)
|
||||
|
||||
secure := false
|
||||
if GetProtocol(r) == "https" {
|
||||
secure = true
|
||||
}
|
||||
|
||||
expiresAt := time.Unix(model.GetMillis()/1000+int64(maxAge), 0)
|
||||
sessionCookie := &http.Cookie{
|
||||
Name: model.SESSION_COOKIE_TOKEN,
|
||||
Value: session.Token,
|
||||
Path: "/",
|
||||
MaxAge: maxAge,
|
||||
Expires: expiresAt,
|
||||
HttpOnly: true,
|
||||
Secure: secure,
|
||||
}
|
||||
|
||||
http.SetCookie(w, sessionCookie)
|
||||
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func GetProtocol(r *http.Request) string {
|
||||
if r.Header.Get(model.HEADER_FORWARDED_PROTO) == "https" {
|
||||
return "https"
|
||||
} else {
|
||||
return "http"
|
||||
}
|
||||
}
|
||||
108
app/user.go
108
app/user.go
@@ -31,6 +31,10 @@ import (
|
||||
)
|
||||
|
||||
func CreateUserWithHash(user *model.User, hash string, data string) (*model.User, *model.AppError) {
|
||||
if err := IsUserSignUpAllowed(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
props := model.MapFromJson(strings.NewReader(data))
|
||||
|
||||
if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt)) {
|
||||
@@ -69,6 +73,10 @@ func CreateUserWithHash(user *model.User, hash string, data string) (*model.User
|
||||
}
|
||||
|
||||
func CreateUserWithInviteId(user *model.User, inviteId string, siteURL string) (*model.User, *model.AppError) {
|
||||
if err := IsUserSignUpAllowed(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var team *model.Team
|
||||
if result := <-Srv.Store.Team().GetByInviteId(inviteId); result.Err != nil {
|
||||
return nil, result.Err
|
||||
@@ -76,6 +84,8 @@ func CreateUserWithInviteId(user *model.User, inviteId string, siteURL string) (
|
||||
team = result.Data.(*model.Team)
|
||||
}
|
||||
|
||||
user.EmailVerified = false
|
||||
|
||||
var ruser *model.User
|
||||
var err *model.AppError
|
||||
if ruser, err = CreateUser(user); err != nil {
|
||||
@@ -95,6 +105,40 @@ func CreateUserWithInviteId(user *model.User, inviteId string, siteURL string) (
|
||||
return ruser, nil
|
||||
}
|
||||
|
||||
func CreateUserFromSignup(user *model.User, siteURL string) (*model.User, *model.AppError) {
|
||||
if err := IsUserSignUpAllowed(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !IsFirstUserAccount() && !*utils.Cfg.TeamSettings.EnableOpenServer {
|
||||
err := model.NewLocAppError("CreateUserFromSignup", "api.user.create_user.no_open_server", nil, "email="+user.Email)
|
||||
err.StatusCode = http.StatusForbidden
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user.EmailVerified = false
|
||||
|
||||
ruser, err := CreateUser(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := SendWelcomeEmail(ruser.Id, ruser.Email, ruser.EmailVerified, ruser.Locale, siteURL); err != nil {
|
||||
l4g.Error(err.Error())
|
||||
}
|
||||
|
||||
return ruser, nil
|
||||
}
|
||||
|
||||
func IsUserSignUpAllowed() *model.AppError {
|
||||
if !utils.Cfg.EmailSettings.EnableSignUpWithEmail || !utils.Cfg.TeamSettings.EnableUserCreation {
|
||||
err := model.NewLocAppError("IsUserSignUpAllowed", "api.user.create_user.signup_email_disabled.app_error", nil, "")
|
||||
err.StatusCode = http.StatusNotImplemented
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsFirstUserAccount() bool {
|
||||
if SessionCacheLength() == 0 {
|
||||
if cr := <-Srv.Store.User().GetTotalUsersCount(); cr.Err != nil {
|
||||
@@ -575,6 +619,43 @@ func SetProfileImage(userId string, imageData *multipart.FileHeader) *model.AppE
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpdatePasswordAsUser(userId, currentPassword, newPassword, siteURL string) *model.AppError {
|
||||
var user *model.User
|
||||
var err *model.AppError
|
||||
|
||||
if user, err = GetUser(userId); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
err = model.NewLocAppError("updatePassword", "api.user.update_password.valid_account.app_error", nil, "")
|
||||
err.StatusCode = http.StatusBadRequest
|
||||
return err
|
||||
}
|
||||
|
||||
if user.AuthData != nil && *user.AuthData != "" {
|
||||
err = model.NewLocAppError("updatePassword", "api.user.update_password.oauth.app_error", nil, "auth_service="+user.AuthService)
|
||||
err.StatusCode = http.StatusBadRequest
|
||||
return err
|
||||
}
|
||||
|
||||
if err := doubleCheckPassword(user, currentPassword); err != nil {
|
||||
if err.Id == "api.user.check_user_password.invalid.app_error" {
|
||||
err = model.NewLocAppError("updatePassword", "api.user.update_password.incorrect.app_error", nil, "")
|
||||
}
|
||||
err.StatusCode = http.StatusForbidden
|
||||
return err
|
||||
}
|
||||
|
||||
T := utils.GetUserTranslations(user.Locale)
|
||||
|
||||
if err := UpdatePasswordSendEmail(user, newPassword, T("api.user.update_password.menu"), siteURL); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpdateActiveNoLdap(userId string, active bool) (*model.User, *model.AppError) {
|
||||
var user *model.User
|
||||
var err *model.AppError
|
||||
@@ -624,6 +705,33 @@ func UpdateActive(user *model.User, active bool) (*model.User, *model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
func SanitizeProfile(user *model.User, asAdmin bool) {
|
||||
options := utils.Cfg.GetSanitizeOptions()
|
||||
if asAdmin {
|
||||
options["email"] = true
|
||||
options["fullname"] = true
|
||||
options["authservice"] = true
|
||||
}
|
||||
user.SanitizeProfile(options)
|
||||
}
|
||||
|
||||
func UpdateUserAsUser(user *model.User, siteURL string, asAdmin bool) (*model.User, *model.AppError) {
|
||||
updatedUser, err := UpdateUser(user, siteURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
SanitizeProfile(updatedUser, asAdmin)
|
||||
|
||||
omitUsers := make(map[string]bool, 1)
|
||||
omitUsers[updatedUser.Id] = true
|
||||
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_UPDATED, "", "", "", omitUsers)
|
||||
message.Add("user", updatedUser)
|
||||
go Publish(message)
|
||||
|
||||
return updatedUser, nil
|
||||
}
|
||||
|
||||
func UpdateUser(user *model.User, siteURL string) (*model.User, *model.AppError) {
|
||||
if result := <-Srv.Store.User().Update(user, false); result.Err != nil {
|
||||
return nil, result.Err
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
"github.com/mattermost/platform/api"
|
||||
"github.com/mattermost/platform/api4"
|
||||
"github.com/mattermost/platform/app"
|
||||
"github.com/mattermost/platform/einterfaces"
|
||||
"github.com/mattermost/platform/manualtesting"
|
||||
@@ -73,6 +74,7 @@ func runServer(configFileLocation string) {
|
||||
app.NewServer()
|
||||
app.InitStores()
|
||||
api.InitRouter()
|
||||
api4.InitApi(false)
|
||||
api.InitApi()
|
||||
web.InitWeb()
|
||||
|
||||
|
||||
2
glide.lock
сгенерированный
2
glide.lock
сгенерированный
@@ -40,7 +40,7 @@ imports:
|
||||
- name: github.com/gorilla/handlers
|
||||
version: e1b2144f2167de0e1042d1d35e5cba5119d4fb5d
|
||||
- name: github.com/gorilla/mux
|
||||
version: 757bef944d0f21880861c2dd9c871ca543023cba
|
||||
version: 34dda716af12ba79ed40ce1f66b102ff75dc3411
|
||||
- name: github.com/gorilla/websocket
|
||||
version: e8f0f8aaa98dfb6586cbdf2978d511e3199a960a
|
||||
- name: github.com/hashicorp/golang-lru
|
||||
|
||||
@@ -24,7 +24,7 @@ import:
|
||||
- package: github.com/gorilla/handlers
|
||||
version: e1b2144f2167de0e1042d1d35e5cba5119d4fb5d
|
||||
- package: github.com/gorilla/mux
|
||||
version: 757bef944d0f21880861c2dd9c871ca543023cba
|
||||
version: 34dda716af12ba79ed40ce1f66b102ff75dc3411
|
||||
- package: github.com/gorilla/websocket
|
||||
version: e8f0f8aaa98dfb6586cbdf2978d511e3199a960a
|
||||
- package: github.com/lib/pq
|
||||
|
||||
10
i18n/en.json
10
i18n/en.json
@@ -799,6 +799,14 @@
|
||||
"id": "api.context.invalid_param.app_error",
|
||||
"translation": "Invalid {{.Name}} parameter"
|
||||
},
|
||||
{
|
||||
"id": "api.context.invalid_body_param.app_error",
|
||||
"translation": "Invalid or missing {{.Name}} in request body"
|
||||
},
|
||||
{
|
||||
"id": "api.context.invalid_url_param.app_error",
|
||||
"translation": "Invalid or missing {{.Name}} parameter in request URL"
|
||||
},
|
||||
{
|
||||
"id": "api.context.invalid_session.error",
|
||||
"translation": "Invalid session err=%v"
|
||||
@@ -5245,7 +5253,7 @@
|
||||
},
|
||||
{
|
||||
"id": "store.sql_user.missing_account.const",
|
||||
"translation": "We couldn't find an existing account matching your email address for this team. This team may require an invite from the team owner to join."
|
||||
"translation": "We couldn't find the user."
|
||||
},
|
||||
{
|
||||
"id": "store.sql_user.permanent_delete.app_error",
|
||||
|
||||
@@ -40,7 +40,8 @@ const (
|
||||
|
||||
API_URL_SUFFIX_V1 = "/api/v1"
|
||||
API_URL_SUFFIX_V3 = "/api/v3"
|
||||
API_URL_SUFFIX = API_URL_SUFFIX_V3
|
||||
API_URL_SUFFIX_V4 = "/api/v4"
|
||||
API_URL_SUFFIX = API_URL_SUFFIX_V4
|
||||
)
|
||||
|
||||
type Result struct {
|
||||
@@ -71,7 +72,7 @@ type Client struct {
|
||||
// NewClient constructs a new client with convienence methods for talking to
|
||||
// the server.
|
||||
func NewClient(url string) *Client {
|
||||
return &Client{url, url + API_URL_SUFFIX, &http.Client{}, "", "", "", "", "", ""}
|
||||
return &Client{url, url + API_URL_SUFFIX_V3, &http.Client{}, "", "", "", "", "", ""}
|
||||
}
|
||||
|
||||
func closeBody(r *http.Response) {
|
||||
|
||||
222
model/client4.go
Обычный файл
222
model/client4.go
Обычный файл
@@ -0,0 +1,222 @@
|
||||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
StatusCode int
|
||||
Error *AppError
|
||||
RequestId string
|
||||
Etag string
|
||||
ServerVersion string
|
||||
}
|
||||
|
||||
type Client4 struct {
|
||||
Url string // The location of the server, for example "http://localhost:8065"
|
||||
ApiUrl string // The api location of the server, for example "http://localhost:8065/api/v4"
|
||||
HttpClient *http.Client // The http client
|
||||
AuthToken string
|
||||
AuthType string
|
||||
}
|
||||
|
||||
func NewAPIv4Client(url string) *Client4 {
|
||||
return &Client4{url, url + API_URL_SUFFIX, &http.Client{}, "", ""}
|
||||
}
|
||||
|
||||
func BuildResponse(r *http.Response) *Response {
|
||||
return &Response{
|
||||
StatusCode: r.StatusCode,
|
||||
RequestId: r.Header.Get(HEADER_REQUEST_ID),
|
||||
Etag: r.Header.Get(HEADER_ETAG_SERVER),
|
||||
ServerVersion: r.Header.Get(HEADER_VERSION_ID),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client4) SetOAuthToken(token string) {
|
||||
c.AuthToken = token
|
||||
c.AuthType = HEADER_TOKEN
|
||||
}
|
||||
|
||||
func (c *Client4) ClearOAuthToken() {
|
||||
c.AuthToken = ""
|
||||
c.AuthType = HEADER_BEARER
|
||||
}
|
||||
|
||||
func (c *Client4) GetUsersRoute() string {
|
||||
return fmt.Sprintf("/users")
|
||||
}
|
||||
|
||||
func (c *Client4) GetUserRoute(userId string) string {
|
||||
return fmt.Sprintf(c.GetUsersRoute()+"/%v", userId)
|
||||
}
|
||||
|
||||
func (c *Client4) GetTeamsRoute() string {
|
||||
return fmt.Sprintf("/teams")
|
||||
}
|
||||
|
||||
func (c *Client4) GetTeamRoute(teamId string) string {
|
||||
return fmt.Sprintf(c.GetTeamsRoute()+"/%v", teamId)
|
||||
}
|
||||
|
||||
func (c *Client4) DoApiGet(url string, etag string) (*http.Response, *AppError) {
|
||||
return c.DoApiRequest(http.MethodGet, url, "", etag)
|
||||
}
|
||||
|
||||
func (c *Client4) DoApiPost(url string, data string) (*http.Response, *AppError) {
|
||||
return c.DoApiRequest(http.MethodPost, url, data, "")
|
||||
}
|
||||
|
||||
func (c *Client4) DoApiPut(url string, data string) (*http.Response, *AppError) {
|
||||
return c.DoApiRequest(http.MethodPut, url, data, "")
|
||||
}
|
||||
|
||||
func (c *Client4) DoApiDelete(url string, data string) (*http.Response, *AppError) {
|
||||
return c.DoApiRequest(http.MethodDelete, url, "", "")
|
||||
}
|
||||
|
||||
func (c *Client4) DoApiRequest(method, url, data, etag string) (*http.Response, *AppError) {
|
||||
rq, _ := http.NewRequest(method, c.ApiUrl+url, strings.NewReader(data))
|
||||
rq.Close = true
|
||||
|
||||
if len(etag) > 0 {
|
||||
rq.Header.Set(HEADER_ETAG_CLIENT, etag)
|
||||
}
|
||||
|
||||
if len(c.AuthToken) > 0 {
|
||||
rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken)
|
||||
}
|
||||
|
||||
if rp, err := c.HttpClient.Do(rq); err != nil {
|
||||
return nil, NewLocAppError(url, "model.client.connecting.app_error", nil, err.Error())
|
||||
} else if rp.StatusCode == 304 {
|
||||
return rp, nil
|
||||
} else if rp.StatusCode >= 300 {
|
||||
defer closeBody(rp)
|
||||
return rp, AppErrorFromJson(rp.Body)
|
||||
} else {
|
||||
return rp, nil
|
||||
}
|
||||
}
|
||||
|
||||
// CheckStatusOK is a convenience function for checking the standard OK response
|
||||
// from the web service.
|
||||
func CheckStatusOK(r *http.Response) bool {
|
||||
m := MapFromJson(r.Body)
|
||||
defer closeBody(r)
|
||||
|
||||
if m != nil && m[STATUS] == STATUS_OK {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Authentication Section
|
||||
|
||||
// LoginById authenticates a user by user id and password.
|
||||
func (c *Client4) LoginById(id string, password string) (*User, *Response) {
|
||||
m := make(map[string]string)
|
||||
m["id"] = id
|
||||
m["password"] = password
|
||||
return c.login(m)
|
||||
}
|
||||
|
||||
// Login authenticates a user by login id, which can be username, email or some sort
|
||||
// of SSO identifier based on server configuration, and a password.
|
||||
func (c *Client4) Login(loginId string, password string) (*User, *Response) {
|
||||
m := make(map[string]string)
|
||||
m["login_id"] = loginId
|
||||
m["password"] = password
|
||||
return c.login(m)
|
||||
}
|
||||
|
||||
// LoginByLdap authenticates a user by LDAP id and password.
|
||||
func (c *Client4) LoginByLdap(loginId string, password string) (*User, *Response) {
|
||||
m := make(map[string]string)
|
||||
m["login_id"] = loginId
|
||||
m["password"] = password
|
||||
m["ldap_only"] = "true"
|
||||
return c.login(m)
|
||||
}
|
||||
|
||||
// LoginWithDevice authenticates a user by login id (username, email or some sort
|
||||
// of SSO identifier based on configuration), password and attaches a device id to
|
||||
// the session.
|
||||
func (c *Client4) LoginWithDevice(loginId string, password string, deviceId string) (*User, *Response) {
|
||||
m := make(map[string]string)
|
||||
m["login_id"] = loginId
|
||||
m["password"] = password
|
||||
m["device_id"] = deviceId
|
||||
return c.login(m)
|
||||
}
|
||||
|
||||
func (c *Client4) login(m map[string]string) (*User, *Response) {
|
||||
if r, err := c.DoApiPost("/users/login", MapToJson(m)); err != nil {
|
||||
return nil, &Response{StatusCode: r.StatusCode, Error: err}
|
||||
} else {
|
||||
c.AuthToken = r.Header.Get(HEADER_TOKEN)
|
||||
c.AuthType = HEADER_BEARER
|
||||
defer closeBody(r)
|
||||
return UserFromJson(r.Body), BuildResponse(r)
|
||||
}
|
||||
}
|
||||
|
||||
// Logout terminates the current user's session.
|
||||
func (c *Client4) Logout() (bool, *Response) {
|
||||
if r, err := c.DoApiPost("/users/logout", ""); err != nil {
|
||||
return false, &Response{StatusCode: r.StatusCode, Error: err}
|
||||
} else {
|
||||
c.AuthToken = ""
|
||||
c.AuthType = HEADER_BEARER
|
||||
|
||||
defer closeBody(r)
|
||||
return CheckStatusOK(r), BuildResponse(r)
|
||||
}
|
||||
}
|
||||
|
||||
// User Section
|
||||
|
||||
// CreateUser creates a user in the system based on the provided user struct.
|
||||
func (c *Client4) CreateUser(user *User) (*User, *Response) {
|
||||
if r, err := c.DoApiPost(c.GetUsersRoute(), user.ToJson()); err != nil {
|
||||
return nil, &Response{StatusCode: r.StatusCode, Error: err}
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return UserFromJson(r.Body), BuildResponse(r)
|
||||
}
|
||||
}
|
||||
|
||||
// GetUser returns a user based on the provided user id string.
|
||||
func (c *Client4) GetUser(userId, etag string) (*User, *Response) {
|
||||
if r, err := c.DoApiGet(c.GetUserRoute(userId), etag); err != nil {
|
||||
return nil, &Response{StatusCode: r.StatusCode, Error: err}
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return UserFromJson(r.Body), BuildResponse(r)
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateUser updates a user in the system based on the provided user struct.
|
||||
func (c *Client4) UpdateUser(user *User) (*User, *Response) {
|
||||
if r, err := c.DoApiPut(c.GetUserRoute(user.Id), user.ToJson()); err != nil {
|
||||
return nil, &Response{StatusCode: r.StatusCode, Error: err}
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return UserFromJson(r.Body), BuildResponse(r)
|
||||
}
|
||||
}
|
||||
|
||||
// Team Section
|
||||
// to be filled in..
|
||||
|
||||
// Channel Section
|
||||
// to be filled in..
|
||||
|
||||
// Post Section
|
||||
// to be filled in..
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
@@ -56,51 +57,51 @@ type User struct {
|
||||
func (u *User) IsValid() *AppError {
|
||||
|
||||
if len(u.Id) != 26 {
|
||||
return NewLocAppError("User.IsValid", "model.user.is_valid.id.app_error", nil, "")
|
||||
return NewAppError("User.IsValid", "model.user.is_valid.id.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if u.CreateAt == 0 {
|
||||
return NewLocAppError("User.IsValid", "model.user.is_valid.create_at.app_error", nil, "user_id="+u.Id)
|
||||
return NewAppError("User.IsValid", "model.user.is_valid.create_at.app_error", nil, "user_id="+u.Id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if u.UpdateAt == 0 {
|
||||
return NewLocAppError("User.IsValid", "model.user.is_valid.update_at.app_error", nil, "user_id="+u.Id)
|
||||
return NewAppError("User.IsValid", "model.user.is_valid.update_at.app_error", nil, "user_id="+u.Id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if !IsValidUsername(u.Username) {
|
||||
return NewLocAppError("User.IsValid", "model.user.is_valid.username.app_error", nil, "user_id="+u.Id)
|
||||
return NewAppError("User.IsValid", "model.user.is_valid.username.app_error", nil, "user_id="+u.Id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if len(u.Email) > 128 || len(u.Email) == 0 {
|
||||
return NewLocAppError("User.IsValid", "model.user.is_valid.email.app_error", nil, "user_id="+u.Id)
|
||||
return NewAppError("User.IsValid", "model.user.is_valid.email.app_error", nil, "user_id="+u.Id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(u.Nickname) > 64 {
|
||||
return NewLocAppError("User.IsValid", "model.user.is_valid.nickname.app_error", nil, "user_id="+u.Id)
|
||||
return NewAppError("User.IsValid", "model.user.is_valid.nickname.app_error", nil, "user_id="+u.Id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(u.Position) > 35 {
|
||||
return NewLocAppError("User.IsValid", "model.user.is_valid.position.app_error", nil, "user_id="+u.Id)
|
||||
return NewAppError("User.IsValid", "model.user.is_valid.position.app_error", nil, "user_id="+u.Id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(u.FirstName) > 64 {
|
||||
return NewLocAppError("User.IsValid", "model.user.is_valid.first_name.app_error", nil, "user_id="+u.Id)
|
||||
return NewAppError("User.IsValid", "model.user.is_valid.first_name.app_error", nil, "user_id="+u.Id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(u.LastName) > 64 {
|
||||
return NewLocAppError("User.IsValid", "model.user.is_valid.last_name.app_error", nil, "user_id="+u.Id)
|
||||
return NewAppError("User.IsValid", "model.user.is_valid.last_name.app_error", nil, "user_id="+u.Id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if u.AuthData != nil && len(*u.AuthData) > 128 {
|
||||
return NewLocAppError("User.IsValid", "model.user.is_valid.auth_data.app_error", nil, "user_id="+u.Id)
|
||||
return NewAppError("User.IsValid", "model.user.is_valid.auth_data.app_error", nil, "user_id="+u.Id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if u.AuthData != nil && len(*u.AuthData) > 0 && len(u.AuthService) == 0 {
|
||||
return NewLocAppError("User.IsValid", "model.user.is_valid.auth_data_type.app_error", nil, "user_id="+u.Id)
|
||||
return NewAppError("User.IsValid", "model.user.is_valid.auth_data_type.app_error", nil, "user_id="+u.Id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if len(u.Password) > 0 && u.AuthData != nil && len(*u.AuthData) > 0 {
|
||||
return NewLocAppError("User.IsValid", "model.user.is_valid.auth_data_pwd.app_error", nil, "user_id="+u.Id)
|
||||
return NewAppError("User.IsValid", "model.user.is_valid.auth_data_pwd.app_error", nil, "user_id="+u.Id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -93,6 +93,18 @@ func AppErrorFromJson(data io.Reader) *AppError {
|
||||
}
|
||||
}
|
||||
|
||||
func NewAppError(where string, id string, params map[string]interface{}, details string, status int) *AppError {
|
||||
ap := &AppError{}
|
||||
ap.Id = id
|
||||
ap.params = params
|
||||
ap.Message = id
|
||||
ap.Where = where
|
||||
ap.DetailedError = details
|
||||
ap.StatusCode = status
|
||||
ap.IsOAuth = false
|
||||
return ap
|
||||
}
|
||||
|
||||
func NewLocAppError(where string, id string, params map[string]interface{}, details string) *AppError {
|
||||
ap := &AppError{}
|
||||
ap.Id = id
|
||||
|
||||
@@ -26,14 +26,14 @@ type WebSocketClient struct {
|
||||
// NewWebSocketClient constructs a new WebSocket client with convienence
|
||||
// methods for talking to the server.
|
||||
func NewWebSocketClient(url, authToken string) (*WebSocketClient, *AppError) {
|
||||
conn, _, err := websocket.DefaultDialer.Dial(url+API_URL_SUFFIX+"/users/websocket", nil)
|
||||
conn, _, err := websocket.DefaultDialer.Dial(url+API_URL_SUFFIX_V3+"/users/websocket", nil)
|
||||
if err != nil {
|
||||
return nil, NewLocAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error())
|
||||
}
|
||||
|
||||
client := &WebSocketClient{
|
||||
url,
|
||||
url + API_URL_SUFFIX,
|
||||
url + API_URL_SUFFIX_V3,
|
||||
conn,
|
||||
authToken,
|
||||
1,
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"crypto/md5"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -108,9 +109,9 @@ func (us SqlUserStore) Save(user *model.User) StoreChannel {
|
||||
|
||||
if err := us.GetMaster().Insert(user); err != nil {
|
||||
if IsUniqueConstraintError(err.Error(), []string{"Email", "users_email_key", "idx_users_email_unique"}) {
|
||||
result.Err = model.NewLocAppError("SqlUserStore.Save", "store.sql_user.save.email_exists.app_error", nil, "user_id="+user.Id+", "+err.Error())
|
||||
result.Err = model.NewAppError("SqlUserStore.Save", "store.sql_user.save.email_exists.app_error", nil, "user_id="+user.Id+", "+err.Error(), http.StatusBadRequest)
|
||||
} else if IsUniqueConstraintError(err.Error(), []string{"Username", "users_username_key", "idx_users_username_unique"}) {
|
||||
result.Err = model.NewLocAppError("SqlUserStore.Save", "store.sql_user.save.username_exists.app_error", nil, "user_id="+user.Id+", "+err.Error())
|
||||
result.Err = model.NewAppError("SqlUserStore.Save", "store.sql_user.save.username_exists.app_error", nil, "user_id="+user.Id+", "+err.Error(), http.StatusBadRequest)
|
||||
} else {
|
||||
result.Err = model.NewLocAppError("SqlUserStore.Save", "store.sql_user.save.app_error", nil, "user_id="+user.Id+", "+err.Error())
|
||||
}
|
||||
@@ -389,6 +390,7 @@ func (us SqlUserStore) Get(id string) StoreChannel {
|
||||
result.Err = model.NewLocAppError("SqlUserStore.Get", "store.sql_user.get.app_error", nil, "user_id="+id+", "+err.Error())
|
||||
} else if obj == nil {
|
||||
result.Err = model.NewLocAppError("SqlUserStore.Get", MISSING_ACCOUNT_ERROR, nil, "user_id="+id)
|
||||
result.Err.StatusCode = http.StatusNotFound
|
||||
} else {
|
||||
result.Data = obj.(*model.User)
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"github.com/mattermost/platform/model"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/platform/model"
|
||||
)
|
||||
|
||||
func IsPasswordValid(password string) *model.AppError {
|
||||
@@ -57,7 +59,7 @@ func IsPasswordValid(password string) *model.AppError {
|
||||
}
|
||||
|
||||
if isError {
|
||||
return model.NewLocAppError("User.IsValid", id+".app_error", map[string]interface{}{"Min": min}, "")
|
||||
return model.NewAppError("User.IsValid", id+".app_error", map[string]interface{}{"Min": min}, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
58
vendor/github.com/gorilla/mux/mux_test.go
сгенерированный
поставляемый
58
vendor/github.com/gorilla/mux/mux_test.go
сгенерированный
поставляемый
@@ -1017,6 +1017,9 @@ func TestBuildVarsFunc(t *testing.T) {
|
||||
func TestSubRouter(t *testing.T) {
|
||||
subrouter1 := new(Route).Host("{v1:[a-z]+}.google.com").Subrouter()
|
||||
subrouter2 := new(Route).PathPrefix("/foo/{v1}").Subrouter()
|
||||
subrouter3 := new(Route).PathPrefix("/foo").Subrouter()
|
||||
subrouter4 := new(Route).PathPrefix("/foo/bar").Subrouter()
|
||||
subrouter5 := new(Route).PathPrefix("/{category}").Subrouter()
|
||||
|
||||
tests := []routeTest{
|
||||
{
|
||||
@@ -1057,6 +1060,61 @@ func TestSubRouter(t *testing.T) {
|
||||
pathTemplate: `/foo/{v1}/baz/{v2}`,
|
||||
shouldMatch: false,
|
||||
},
|
||||
{
|
||||
route: subrouter3.Path("/"),
|
||||
request: newRequest("GET", "http://localhost/foo/"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "/foo/",
|
||||
pathTemplate: `/foo/`,
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
route: subrouter3.Path(""),
|
||||
request: newRequest("GET", "http://localhost/foo"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "/foo",
|
||||
pathTemplate: `/foo`,
|
||||
shouldMatch: true,
|
||||
},
|
||||
|
||||
{
|
||||
route: subrouter4.Path("/"),
|
||||
request: newRequest("GET", "http://localhost/foo/bar/"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "/foo/bar/",
|
||||
pathTemplate: `/foo/bar/`,
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
route: subrouter4.Path(""),
|
||||
request: newRequest("GET", "http://localhost/foo/bar"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "/foo/bar",
|
||||
pathTemplate: `/foo/bar`,
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
route: subrouter5.Path("/"),
|
||||
request: newRequest("GET", "http://localhost/baz/"),
|
||||
vars: map[string]string{"category": "baz"},
|
||||
host: "",
|
||||
path: "/baz/",
|
||||
pathTemplate: `/{category}/`,
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
route: subrouter5.Path(""),
|
||||
request: newRequest("GET", "http://localhost/baz"),
|
||||
vars: map[string]string{"category": "baz"},
|
||||
host: "",
|
||||
path: "/baz",
|
||||
pathTemplate: `/{category}`,
|
||||
shouldMatch: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
2
vendor/github.com/gorilla/mux/route.go
сгенерированный
поставляемый
2
vendor/github.com/gorilla/mux/route.go
сгенерированный
поставляемый
@@ -153,7 +153,7 @@ func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery
|
||||
}
|
||||
r.regexp = r.getRegexpGroup()
|
||||
if !matchHost && !matchQuery {
|
||||
if len(tpl) == 0 || tpl[0] != '/' {
|
||||
if tpl == "/" && (len(tpl) == 0 || tpl[0] != '/') {
|
||||
return fmt.Errorf("mux: path must start with a slash, got %q", tpl)
|
||||
}
|
||||
if r.regexp.path != nil {
|
||||
|
||||
Ссылка в новой задаче
Block a user