Merge remote-tracking branch 'origin/master' into advanced-permissions-phase-2

Этот коммит содержится в:
Martin Kraft
2018-05-11 08:05:23 -04:00
родитель 11c60ea41a 2b27e12445
Коммит 91557bbd97
55 изменённых файлов: 290 добавлений и 214 удалений

Просмотреть файл

@@ -275,6 +275,10 @@ store-mocks: ## Creates mock files.
go get github.com/vektra/mockery/... go get github.com/vektra/mockery/...
$(GOPATH)/bin/mockery -dir store -all -output store/storetest/mocks -note 'Regenerate this file using `make store-mocks`.' $(GOPATH)/bin/mockery -dir store -all -output store/storetest/mocks -note 'Regenerate this file using `make store-mocks`.'
ldap-mocks: ## Creates mock files for ldap.
go get github.com/vektra/mockery/...
GOPATH=$(shell go env GOPATH) $(shell go env GOPATH)/bin/mockery -dir enterprise/ldap -all -output enterprise/ldap/mocks -note 'Regenerate this file using `make ldap-mocks`.'
update-jira-plugin: ## Updates Jira plugin. update-jira-plugin: ## Updates Jira plugin.
go get github.com/mattermost/go-bindata/... go get github.com/mattermost/go-bindata/...
curl -s https://api.github.com/repos/mattermost/mattermost-plugin-jira/releases/latest | grep browser_download_url | grep darwin-amd64 | cut -d '"' -f 4 | wget -qi - -O plugin.tar.gz curl -s https://api.github.com/repos/mattermost/mattermost-plugin-jira/releases/latest | grep browser_download_url | grep darwin-amd64 | cut -d '"' -f 4 | wget -qi - -O plugin.tar.gz

Просмотреть файл

@@ -272,7 +272,7 @@ func TestDeleteEmoji(t *testing.T) {
func createTestEmoji(t *testing.T, a *app.App, emoji *model.Emoji, imageData []byte) *model.Emoji { func createTestEmoji(t *testing.T, a *app.App, emoji *model.Emoji, imageData []byte) *model.Emoji {
emoji = store.Must(a.Srv.Store.Emoji().Save(emoji)).(*model.Emoji) emoji = store.Must(a.Srv.Store.Emoji().Save(emoji)).(*model.Emoji)
if err := a.WriteFile(imageData, "emoji/"+emoji.Id+"/image"); err != nil { if _, err := a.WriteFile(bytes.NewReader(imageData), "emoji/"+emoji.Id+"/image"); err != nil {
store.Must(a.Srv.Store.Emoji().Delete(emoji.Id, time.Now().Unix())) store.Must(a.Srv.Store.Emoji().Delete(emoji.Id, time.Now().Unix()))
t.Fatalf("failed to write image: %v", err.Error()) t.Fatalf("failed to write image: %v", err.Error())
} }

Просмотреть файл

@@ -107,7 +107,7 @@ func login(c *Context, w http.ResponseWriter, r *http.Request) {
ldapOnly := props["ldap_only"] == "true" ldapOnly := props["ldap_only"] == "true"
c.LogAudit("attempt - user_id=" + id + " login_id=" + loginId) c.LogAudit("attempt - user_id=" + id + " login_id=" + loginId)
user, err := c.App.AuthenticateUserForLogin(id, loginId, password, mfaToken, deviceId, ldapOnly) user, err := c.App.AuthenticateUserForLogin(id, loginId, password, mfaToken, ldapOnly)
if err != nil { if err != nil {
c.LogAudit("failure - user_id=" + id + " login_id=" + loginId) c.LogAudit("failure - user_id=" + id + " login_id=" + loginId)
c.Err = err c.Err = err
@@ -1072,7 +1072,7 @@ func checkMfa(c *Context, w http.ResponseWriter, r *http.Request) {
} }
rdata := map[string]string{} rdata := map[string]string{}
if user, err := c.App.GetUserForLogin(loginId, false); err != nil { if user, err := c.App.GetUserForLogin("", loginId); err != nil {
rdata["mfa_required"] = "false" rdata["mfa_required"] = "false"
} else { } else {
rdata["mfa_required"] = strconv.FormatBool(user.MfaActive) rdata["mfa_required"] = strconv.FormatBool(user.MfaActive)

Просмотреть файл

@@ -771,7 +771,7 @@ func checkUserMfa(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
if user, err := c.App.GetUserForLogin(loginId, false); err == nil { if user, err := c.App.GetUserForLogin("", loginId); err == nil {
resp["mfa_required"] = user.MfaActive resp["mfa_required"] = user.MfaActive
} }
@@ -943,7 +943,7 @@ func login(c *Context, w http.ResponseWriter, r *http.Request) {
ldapOnly := props["ldap_only"] == "true" ldapOnly := props["ldap_only"] == "true"
c.LogAuditWithUserId(id, "attempt - login_id="+loginId) c.LogAuditWithUserId(id, "attempt - login_id="+loginId)
user, err := c.App.AuthenticateUserForLogin(id, loginId, password, mfaToken, deviceId, ldapOnly) user, err := c.App.AuthenticateUserForLogin(id, loginId, password, mfaToken, ldapOnly)
if err != nil { if err != nil {
c.LogAuditWithUserId(id, "failure - login_id="+loginId) c.LogAuditWithUserId(id, "failure - login_id="+loginId)
c.Err = err c.Err = err
@@ -1167,7 +1167,7 @@ func sendVerificationEmail(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
user, err := c.App.GetUserForLogin(email, false) user, err := c.App.GetUserForLogin("", email)
if err != nil { if err != nil {
// Don't want to leak whether the email is valid or not // Don't want to leak whether the email is valid or not
ReturnStatusOK(w) ReturnStatusOK(w)
@@ -1205,7 +1205,7 @@ func switchAccountType(c *Context, w http.ResponseWriter, r *http.Request) {
link, err = c.App.SwitchOAuthToEmail(switchRequest.Email, switchRequest.NewPassword, c.Session.UserId) link, err = c.App.SwitchOAuthToEmail(switchRequest.Email, switchRequest.NewPassword, c.Session.UserId)
} else if switchRequest.EmailToLdap() { } else if switchRequest.EmailToLdap() {
link, err = c.App.SwitchEmailToLdap(switchRequest.Email, switchRequest.Password, switchRequest.MfaCode, switchRequest.LdapId, switchRequest.NewPassword) link, err = c.App.SwitchEmailToLdap(switchRequest.Email, switchRequest.Password, switchRequest.MfaCode, switchRequest.LdapLoginId, switchRequest.NewPassword)
} else if switchRequest.LdapToEmail() { } else if switchRequest.LdapToEmail() {
link, err = c.App.SwitchLdapToEmail(switchRequest.Password, switchRequest.MfaCode, switchRequest.Email, switchRequest.NewPassword) link, err = c.App.SwitchLdapToEmail(switchRequest.Password, switchRequest.MfaCode, switchRequest.Email, switchRequest.NewPassword)
} else { } else {

Просмотреть файл

@@ -98,7 +98,7 @@ func (a *App) UploadEmojiImage(id string, imageData *multipart.FileHeader) *mode
if err := gif.EncodeAll(newbuf, resized_gif); err != nil { if err := gif.EncodeAll(newbuf, resized_gif); err != nil {
return model.NewAppError("uploadEmojiImage", "api.emoji.upload.large_image.gif_encode_error", nil, "", http.StatusBadRequest) return model.NewAppError("uploadEmojiImage", "api.emoji.upload.large_image.gif_encode_error", nil, "", http.StatusBadRequest)
} }
if err := a.WriteFile(newbuf.Bytes(), getEmojiImagePath(id)); err != nil { if _, err := a.WriteFile(newbuf, getEmojiImagePath(id)); err != nil {
return err return err
} }
} }
@@ -110,14 +110,15 @@ func (a *App) UploadEmojiImage(id string, imageData *multipart.FileHeader) *mode
if err := png.Encode(newbuf, resized_image); err != nil { if err := png.Encode(newbuf, resized_image); err != nil {
return model.NewAppError("uploadEmojiImage", "api.emoji.upload.large_image.encode_error", nil, "", http.StatusBadRequest) return model.NewAppError("uploadEmojiImage", "api.emoji.upload.large_image.encode_error", nil, "", http.StatusBadRequest)
} }
if err := a.WriteFile(newbuf.Bytes(), getEmojiImagePath(id)); err != nil { if _, err := a.WriteFile(newbuf, getEmojiImagePath(id)); err != nil {
return err return err
} }
} }
} }
} }
return a.WriteFile(buf.Bytes(), getEmojiImagePath(id)) _, appErr := a.WriteFile(buf, getEmojiImagePath(id))
return appErr
} }
func (a *App) DeleteEmoji(emoji *model.Emoji) *model.AppError { func (a *App) DeleteEmoji(emoji *model.Emoji) *model.AppError {

Просмотреть файл

@@ -78,12 +78,13 @@ func (a *App) MoveFile(oldPath, newPath string) *model.AppError {
return backend.MoveFile(oldPath, newPath) return backend.MoveFile(oldPath, newPath)
} }
func (a *App) WriteFile(f []byte, path string) *model.AppError { func (a *App) WriteFile(fr io.Reader, path string) (int64, *model.AppError) {
backend, err := a.FileBackend() backend, err := a.FileBackend()
if err != nil { if err != nil {
return err return 0, err
} }
return backend.WriteFile(f, path)
return backend.WriteFile(fr, path)
} }
func (a *App) RemoveFile(path string) *model.AppError { func (a *App) RemoveFile(path string) *model.AppError {
@@ -414,7 +415,7 @@ func (a *App) DoUploadFile(now time.Time, rawTeamId string, rawChannelId string,
info.ThumbnailPath = pathPrefix + nameWithoutExtension + "_thumb.jpg" info.ThumbnailPath = pathPrefix + nameWithoutExtension + "_thumb.jpg"
} }
if err := a.WriteFile(data, info.Path); err != nil { if _, err := a.WriteFile(bytes.NewReader(data), info.Path); err != nil {
return nil, err return nil, err
} }
@@ -531,7 +532,7 @@ func (a *App) generateThumbnailImage(img image.Image, thumbnailPath string, widt
return return
} }
if err := a.WriteFile(buf.Bytes(), thumbnailPath); err != nil { if _, err := a.WriteFile(buf, thumbnailPath); err != nil {
mlog.Error(fmt.Sprintf("Unable to upload thumbnail path=%v err=%v", thumbnailPath, err)) mlog.Error(fmt.Sprintf("Unable to upload thumbnail path=%v err=%v", thumbnailPath, err))
return return
} }
@@ -553,7 +554,7 @@ func (a *App) generatePreviewImage(img image.Image, previewPath string, width in
return return
} }
if err := a.WriteFile(buf.Bytes(), previewPath); err != nil { if _, err := a.WriteFile(buf, previewPath); err != nil {
mlog.Error(fmt.Sprintf("Unable to upload preview err=%v", err), mlog.String("path", previewPath)) mlog.Error(fmt.Sprintf("Unable to upload preview err=%v", err), mlog.String("path", previewPath))
return return
} }

Просмотреть файл

@@ -40,7 +40,7 @@ func (a *App) TestLdap() *model.AppError {
return nil return nil
} }
func (a *App) SwitchEmailToLdap(email, password, code, ldapId, ldapPassword string) (string, *model.AppError) { func (a *App) SwitchEmailToLdap(email, password, code, ldapLoginId, ldapPassword string) (string, *model.AppError) {
if a.License() != nil && !*a.Config().ServiceSettings.ExperimentalEnableAuthenticationTransfer { if a.License() != nil && !*a.Config().ServiceSettings.ExperimentalEnableAuthenticationTransfer {
return "", model.NewAppError("emailToLdap", "api.user.email_to_ldap.not_available.app_error", nil, "", http.StatusForbidden) return "", model.NewAppError("emailToLdap", "api.user.email_to_ldap.not_available.app_error", nil, "", http.StatusForbidden)
} }
@@ -63,7 +63,7 @@ func (a *App) SwitchEmailToLdap(email, password, code, ldapId, ldapPassword stri
return "", model.NewAppError("SwitchEmailToLdap", "api.user.email_to_ldap.not_available.app_error", nil, "", http.StatusNotImplemented) return "", model.NewAppError("SwitchEmailToLdap", "api.user.email_to_ldap.not_available.app_error", nil, "", http.StatusNotImplemented)
} }
if err := ldapInterface.SwitchToLdap(user.Id, ldapId, ldapPassword); err != nil { if err := ldapInterface.SwitchToLdap(user.Id, ldapLoginId, ldapPassword); err != nil {
return "", err return "", err
} }
@@ -95,7 +95,7 @@ func (a *App) SwitchLdapToEmail(ldapPassword, code, email, newPassword string) (
return "", model.NewAppError("SwitchLdapToEmail", "api.user.ldap_to_email.not_available.app_error", nil, "", http.StatusNotImplemented) return "", model.NewAppError("SwitchLdapToEmail", "api.user.ldap_to_email.not_available.app_error", nil, "", http.StatusNotImplemented)
} }
if err := ldapInterface.CheckPassword(*user.AuthData, ldapPassword); err != nil { if err := ldapInterface.CheckPasswordAuthData(*user.AuthData, ldapPassword); err != nil {
return "", err return "", err
} }

Просмотреть файл

@@ -11,47 +11,69 @@ import (
"github.com/avct/uasurfer" "github.com/avct/uasurfer"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/store"
) )
func (a *App) AuthenticateUserForLogin(id, loginId, password, mfaToken, deviceId string, ldapOnly bool) (*model.User, *model.AppError) { func (a *App) AuthenticateUserForLogin(id, loginId, password, mfaToken string, ldapOnly bool) (user *model.User, err *model.AppError) {
// Do statistics
defer func() {
if a.Metrics != nil {
if user == nil || err != nil {
a.Metrics.IncrementLoginFail()
} else {
a.Metrics.IncrementLogin()
}
}
}()
if len(password) == 0 { if len(password) == 0 {
err := model.NewAppError("AuthenticateUserForLogin", "api.user.login.blank_pwd.app_error", nil, "", http.StatusBadRequest) err := model.NewAppError("AuthenticateUserForLogin", "api.user.login.blank_pwd.app_error", nil, "", http.StatusBadRequest)
return nil, err return nil, err
} }
var user *model.User // Get the MM user we are trying to login
var err *model.AppError if user, err = a.GetUserForLogin(id, loginId); err != nil {
return nil, err
if len(id) != 0 {
if user, err = a.GetUser(id); err != nil {
err.StatusCode = http.StatusBadRequest
if a.Metrics != nil {
a.Metrics.IncrementLoginFail()
}
return nil, err
}
} else {
if user, err = a.GetUserForLogin(loginId, ldapOnly); err != nil {
if a.Metrics != nil {
a.Metrics.IncrementLoginFail()
}
return nil, err
}
} }
// and then authenticate them // and then authenticate them
if user, err = a.authenticateUser(user, password, mfaToken); err != nil { if user, err = a.authenticateUser(user, password, mfaToken); err != nil {
if a.Metrics != nil {
a.Metrics.IncrementLoginFail()
}
return nil, err return nil, err
} }
if a.Metrics != nil { return user, nil
a.Metrics.IncrementLogin() }
func (a *App) GetUserForLogin(id, loginId string) (*model.User, *model.AppError) {
enableUsername := *a.Config().EmailSettings.EnableSignInWithUsername
enableEmail := *a.Config().EmailSettings.EnableSignInWithEmail
// If we are given a userID then fail if we can't find a user with that ID
if len(id) != 0 {
if user, err := a.GetUser(id); err != nil {
if err.Id != store.MISSING_ACCOUNT_ERROR {
err.StatusCode = http.StatusInternalServerError
return nil, err
} else {
err.StatusCode = http.StatusBadRequest
return nil, err
}
} else {
return user, nil
}
} }
return user, nil // Try to get the user by username/email
if result := <-a.Srv.Store.User().GetForLogin(loginId, enableUsername, enableEmail); result.Err == nil {
return result.Data.(*model.User), nil
}
// Try to get the user with LDAP
if user, err := a.Ldap.GetUser(loginId); err == nil {
return user, nil
}
return nil, model.NewAppError("GetUserForLogin", "store.sql_user.get_for_login.app_error", nil, "", http.StatusBadRequest)
} }
func (a *App) DoLogin(w http.ResponseWriter, r *http.Request, user *model.User, deviceId string) (*model.Session, *model.AppError) { func (a *App) DoLogin(w http.ResponseWriter, r *http.Request, user *model.User, deviceId string) (*model.Session, *model.AppError) {

Просмотреть файл

@@ -1057,7 +1057,7 @@ func (a *App) SetTeamIconFromFile(teamId string, file multipart.File) *model.App
path := "teams/" + teamId + "/teamIcon.png" path := "teams/" + teamId + "/teamIcon.png"
if err := a.WriteFile(buf.Bytes(), path); err != nil { if _, err := a.WriteFile(buf, path); err != nil {
return model.NewAppError("SetTeamIcon", "api.team.set_team_icon.write_file.app_error", nil, "", http.StatusInternalServerError) return model.NewAppError("SetTeamIcon", "api.team.set_team_icon.write_file.app_error", nil, "", http.StatusInternalServerError)
} }

Просмотреть файл

@@ -382,38 +382,6 @@ func (a *App) GetUserByAuth(authData *string, authService string) (*model.User,
} }
} }
func (a *App) GetUserForLogin(loginId string, onlyLdap bool) (*model.User, *model.AppError) {
license := a.License()
ldapAvailable := *a.Config().LdapSettings.Enable && a.Ldap != nil && license != nil && *license.Features.LDAP
if result := <-a.Srv.Store.User().GetForLogin(
loginId,
*a.Config().EmailSettings.EnableSignInWithUsername && !onlyLdap,
*a.Config().EmailSettings.EnableSignInWithEmail && !onlyLdap,
ldapAvailable,
); result.Err != nil && result.Err.Id == "store.sql_user.get_for_login.multiple_users" {
// don't fall back to LDAP in this case since we already know there's an LDAP user, but that it shouldn't work
result.Err.StatusCode = http.StatusBadRequest
return nil, result.Err
} else if result.Err != nil {
if !ldapAvailable {
// failed to find user and no LDAP server to fall back on
result.Err.StatusCode = http.StatusBadRequest
return nil, result.Err
}
// fall back to LDAP server to see if we can find a user
if ldapUser, ldapErr := a.Ldap.GetUser(loginId); ldapErr != nil {
ldapErr.StatusCode = http.StatusBadRequest
return nil, ldapErr
} else {
return ldapUser, nil
}
} else {
return result.Data.(*model.User), nil
}
}
func (a *App) GetUsers(offset int, limit int) ([]*model.User, *model.AppError) { func (a *App) GetUsers(offset int, limit int) ([]*model.User, *model.AppError) {
if result := <-a.Srv.Store.User().GetAllProfiles(offset, limit); result.Err != nil { if result := <-a.Srv.Store.User().GetAllProfiles(offset, limit); result.Err != nil {
return nil, result.Err return nil, result.Err
@@ -786,7 +754,7 @@ func (a *App) GetProfileImage(user *model.User) ([]byte, bool, *model.AppError)
} }
if user.LastPictureUpdate == 0 { if user.LastPictureUpdate == 0 {
if err := a.WriteFile(img, path); err != nil { if _, err := a.WriteFile(bytes.NewReader(img), path); err != nil {
return nil, false, err return nil, false, err
} }
} }
@@ -842,7 +810,7 @@ func (a *App) SetProfileImageFromFile(userId string, file multipart.File) *model
path := "users/" + userId + "/profile.png" path := "users/" + userId + "/profile.png"
if err := a.WriteFile(buf.Bytes(), path); err != nil { if _, err := a.WriteFile(buf, path); err != nil {
return model.NewAppError("SetProfileImage", "api.user.upload_profile_user.upload_profile.app_error", nil, "", http.StatusInternalServerError) return model.NewAppError("SetProfileImage", "api.user.upload_profile_user.upload_profile.app_error", nil, "", http.StatusInternalServerError)
} }

Просмотреть файл

@@ -22,9 +22,19 @@ var LdapSyncCmd = &cobra.Command{
RunE: ldapSyncCmdF, RunE: ldapSyncCmdF,
} }
var LdapIdMigrate = &cobra.Command{
Use: "idmigrate",
Short: "Migrate LDAP IdAttribute to new value",
Long: "Migrate LDAP IdAttribute to new value. Run this utility then change the IdAttribute to the new value.",
Example: " ldap idmigrate objectGUID",
Args: cobra.ExactArgs(1),
RunE: ldapIdMigrateCmdF,
}
func init() { func init() {
LdapCmd.AddCommand( LdapCmd.AddCommand(
LdapSyncCmd, LdapSyncCmd,
LdapIdMigrate,
) )
cmd.RootCmd.AddCommand(LdapCmd) cmd.RootCmd.AddCommand(LdapCmd)
} }
@@ -47,3 +57,22 @@ func ldapSyncCmdF(command *cobra.Command, args []string) error {
return nil return nil
} }
func ldapIdMigrateCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer a.Shutdown()
toAttribute := args[0]
if ldapI := a.Ldap; ldapI != nil {
if err := ldapI.MigrateIDAttribute(toAttribute); err != nil {
cmd.CommandPrintErrorln("ERROR: AD/LDAP IdAttribute migration failed! Error: " + err.Error())
} else {
cmd.CommandPrettyPrintln("SUCCESS: AD/LDAP IdAttribute migration complete. You can now change your IdAttribute to: " + toAttribute)
}
}
return nil
}

Просмотреть файл

@@ -263,6 +263,7 @@
"NicknameAttribute": "", "NicknameAttribute": "",
"IdAttribute": "", "IdAttribute": "",
"PositionAttribute": "", "PositionAttribute": "",
"LoginIdAttribute": "",
"SyncIntervalMinutes": 60, "SyncIntervalMinutes": 60,
"SkipCertificateVerification": false, "SkipCertificateVerification": false,
"QueryTimeout": 60, "QueryTimeout": 60,

Просмотреть файл

@@ -4,8 +4,6 @@
package einterfaces package einterfaces
import ( import (
"github.com/go-ldap/ldap"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
) )
@@ -14,12 +12,11 @@ type LdapInterface interface {
GetUser(id string) (*model.User, *model.AppError) GetUser(id string) (*model.User, *model.AppError)
GetUserAttributes(id string, attributes []string) (map[string]string, *model.AppError) GetUserAttributes(id string, attributes []string) (map[string]string, *model.AppError)
CheckPassword(id string, password string) *model.AppError CheckPassword(id string, password string) *model.AppError
CheckPasswordAuthData(authData string, password string) *model.AppError
SwitchToLdap(userId, ldapId, ldapPassword string) *model.AppError SwitchToLdap(userId, ldapId, ldapPassword string) *model.AppError
ValidateFilter(filter string) *model.AppError ValidateFilter(filter string) *model.AppError
StartSynchronizeJob(waitForJobToFinish bool) (*model.Job, *model.AppError) StartSynchronizeJob(waitForJobToFinish bool) (*model.Job, *model.AppError)
RunTest() *model.AppError RunTest() *model.AppError
GetAllLdapUsers() ([]*model.User, *model.AppError) GetAllLdapUsers() ([]*model.User, *model.AppError)
UserFromLdapUser(ldapUser *ldap.Entry) *model.User MigrateIDAttribute(toAttribute string) error
UserHasUpdateFromLdap(existingUser *model.User, currentLdapUser *model.User) bool
UpdateLocalLdapUser(existingUser *model.User, currentLdapUser *model.User) *model.User
} }

Просмотреть файл

@@ -5026,6 +5026,10 @@
"id": "model.config.is_valid.ldap_id", "id": "model.config.is_valid.ldap_id",
"translation": "AD/LDAP field \"ID Attribute\" is required." "translation": "AD/LDAP field \"ID Attribute\" is required."
}, },
{
"id": "model.config.is_valid.ldap_login_id",
"translation": "AD/LDAP field \"Login ID Attribute\" is required."
},
{ {
"id": "model.config.is_valid.ldap_lastname", "id": "model.config.is_valid.ldap_lastname",
"translation": "AD/LDAP field \"Last Name Attribute\" is required." "translation": "AD/LDAP field \"Last Name Attribute\" is required."

Просмотреть файл

@@ -29,6 +29,7 @@ type Field = zapcore.Field
var Int64 = zap.Int64 var Int64 = zap.Int64
var Int = zap.Int var Int = zap.Int
var String = zap.String var String = zap.String
var Any = zap.Any
var Err = zap.Error var Err = zap.Error
type LoggerConfiguration struct { type LoggerConfiguration struct {

Просмотреть файл

@@ -1144,6 +1144,7 @@ type LdapSettings struct {
NicknameAttribute *string NicknameAttribute *string
IdAttribute *string IdAttribute *string
PositionAttribute *string PositionAttribute *string
LoginIdAttribute *string
// Synchronization // Synchronization
SyncIntervalMinutes *int SyncIntervalMinutes *int
@@ -1227,6 +1228,12 @@ func (s *LdapSettings) SetDefaults() {
s.PositionAttribute = NewString(LDAP_SETTINGS_DEFAULT_POSITION_ATTRIBUTE) s.PositionAttribute = NewString(LDAP_SETTINGS_DEFAULT_POSITION_ATTRIBUTE)
} }
// For those upgrading to the version when LoginIdAttribute was added
// they need IdAttribute == LoginIdAttribute not to break
if s.LoginIdAttribute == nil {
s.LoginIdAttribute = s.IdAttribute
}
if s.SyncIntervalMinutes == nil { if s.SyncIntervalMinutes == nil {
s.SyncIntervalMinutes = NewInt(60) s.SyncIntervalMinutes = NewInt(60)
} }
@@ -2074,6 +2081,10 @@ func (ls *LdapSettings) isValid() *AppError {
if *ls.IdAttribute == "" { if *ls.IdAttribute == "" {
return NewAppError("Config.IsValid", "model.config.is_valid.ldap_id", nil, "", http.StatusBadRequest) return NewAppError("Config.IsValid", "model.config.is_valid.ldap_id", nil, "", http.StatusBadRequest)
} }
if *ls.LoginIdAttribute == "" {
return NewAppError("Config.IsValid", "model.config.is_valid.ldap_login_id", nil, "", http.StatusBadRequest)
}
} }
return nil return nil

Просмотреть файл

@@ -15,7 +15,7 @@ type SwitchRequest struct {
Password string `json:"password"` Password string `json:"password"`
NewPassword string `json:"new_password"` NewPassword string `json:"new_password"`
MfaCode string `json:"mfa_code"` MfaCode string `json:"mfa_code"`
LdapId string `json:"ldap_id"` LdapLoginId string `json:"ldap_id"`
} }
func (o *SwitchRequest) ToJson() string { func (o *SwitchRequest) ToJson() string {

Просмотреть файл

@@ -6,24 +6,28 @@ package model
import ( import (
"encoding/json" "encoding/json"
"net/http" "net/http"
"time"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
) )
const ( const (
SOCKET_MAX_MESSAGE_SIZE_KB = 8 * 1024 // 8KB SOCKET_MAX_MESSAGE_SIZE_KB = 8 * 1024 // 8KB
PING_TIMEOUT_BUFFER_SECONDS = 5
) )
type WebSocketClient struct { type WebSocketClient struct {
Url string // The location of the server like "ws://localhost:8065" Url string // The location of the server like "ws://localhost:8065"
ApiUrl string // The api location of the server like "ws://localhost:8065/api/v3" ApiUrl string // The api location of the server like "ws://localhost:8065/api/v3"
ConnectUrl string // The websocket URL to connect to like "ws://localhost:8065/api/v3/path/to/websocket" ConnectUrl string // The websocket URL to connect to like "ws://localhost:8065/api/v3/path/to/websocket"
Conn *websocket.Conn // The WebSocket connection Conn *websocket.Conn // The WebSocket connection
AuthToken string // The token used to open the WebSocket AuthToken string // The token used to open the WebSocket
Sequence int64 // The ever-incrementing sequence attached to each WebSocket action Sequence int64 // The ever-incrementing sequence attached to each WebSocket action
EventChannel chan *WebSocketEvent PingTimeoutChannel chan bool // The channel used to signal ping timeouts
ResponseChannel chan *WebSocketResponse EventChannel chan *WebSocketEvent
ListenError *AppError ResponseChannel chan *WebSocketResponse
ListenError *AppError
pingTimeoutTimer *time.Timer
} }
// NewWebSocketClient constructs a new WebSocket client with convenience // NewWebSocketClient constructs a new WebSocket client with convenience
@@ -47,11 +51,15 @@ func NewWebSocketClientWithDialer(dialer *websocket.Dialer, url, authToken strin
conn, conn,
authToken, authToken,
1, 1,
make(chan bool, 1),
make(chan *WebSocketEvent, 100), make(chan *WebSocketEvent, 100),
make(chan *WebSocketResponse, 100), make(chan *WebSocketResponse, 100),
nil, nil,
nil,
} }
client.configurePingHandling()
client.SendMessage(WEBSOCKET_AUTHENTICATION_CHALLENGE, map[string]interface{}{"token": authToken}) client.SendMessage(WEBSOCKET_AUTHENTICATION_CHALLENGE, map[string]interface{}{"token": authToken})
return client, nil return client, nil
@@ -78,11 +86,15 @@ func NewWebSocketClient4WithDialer(dialer *websocket.Dialer, url, authToken stri
conn, conn,
authToken, authToken,
1, 1,
make(chan bool, 1),
make(chan *WebSocketEvent, 100), make(chan *WebSocketEvent, 100),
make(chan *WebSocketResponse, 100), make(chan *WebSocketResponse, 100),
nil, nil,
nil,
} }
client.configurePingHandling()
client.SendMessage(WEBSOCKET_AUTHENTICATION_CHALLENGE, map[string]interface{}{"token": authToken}) client.SendMessage(WEBSOCKET_AUTHENTICATION_CHALLENGE, map[string]interface{}{"token": authToken})
return client, nil return client, nil
@@ -99,6 +111,8 @@ func (wsc *WebSocketClient) ConnectWithDialer(dialer *websocket.Dialer) *AppErro
return NewAppError("Connect", "model.websocket_client.connect_fail.app_error", nil, err.Error(), http.StatusInternalServerError) return NewAppError("Connect", "model.websocket_client.connect_fail.app_error", nil, err.Error(), http.StatusInternalServerError)
} }
wsc.configurePingHandling()
wsc.EventChannel = make(chan *WebSocketEvent, 100) wsc.EventChannel = make(chan *WebSocketEvent, 100)
wsc.ResponseChannel = make(chan *WebSocketResponse, 100) wsc.ResponseChannel = make(chan *WebSocketResponse, 100)
@@ -181,3 +195,24 @@ func (wsc *WebSocketClient) GetStatusesByIds(userIds []string) {
} }
wsc.SendMessage("get_statuses_by_ids", data) wsc.SendMessage("get_statuses_by_ids", data)
} }
func (wsc *WebSocketClient) configurePingHandling() {
wsc.Conn.SetPingHandler(wsc.pingHandler)
wsc.pingTimeoutTimer = time.NewTimer(time.Second * (60 + PING_TIMEOUT_BUFFER_SECONDS))
go wsc.pingWatchdog()
}
func (wsc *WebSocketClient) pingHandler(appData string) error {
if !wsc.pingTimeoutTimer.Stop() {
<-wsc.pingTimeoutTimer.C
}
wsc.pingTimeoutTimer.Reset(time.Second * (60 + PING_TIMEOUT_BUFFER_SECONDS))
wsc.Conn.WriteMessage(websocket.PongMessage, []byte{})
return nil
}
func (wsc *WebSocketClient) pingWatchdog() {
<-wsc.pingTimeoutTimer.C
wsc.PingTimeoutChannel <- true
}

Просмотреть файл

@@ -819,13 +819,12 @@ func (us SqlUserStore) GetByUsername(username string) store.StoreChannel {
}) })
} }
func (us SqlUserStore) GetForLogin(loginId string, allowSignInWithUsername, allowSignInWithEmail, ldapEnabled bool) store.StoreChannel { func (us SqlUserStore) GetForLogin(loginId string, allowSignInWithUsername, allowSignInWithEmail bool) store.StoreChannel {
return store.Do(func(result *store.StoreResult) { return store.Do(func(result *store.StoreResult) {
params := map[string]interface{}{ params := map[string]interface{}{
"LoginId": loginId, "LoginId": loginId,
"AllowSignInWithUsername": allowSignInWithUsername, "AllowSignInWithUsername": allowSignInWithUsername,
"AllowSignInWithEmail": allowSignInWithEmail, "AllowSignInWithEmail": allowSignInWithEmail,
"LdapEnabled": ldapEnabled,
} }
users := []*model.User{} users := []*model.User{}
@@ -837,8 +836,7 @@ func (us SqlUserStore) GetForLogin(loginId string, allowSignInWithUsername, allo
Users Users
WHERE WHERE
(:AllowSignInWithUsername AND Username = :LoginId) (:AllowSignInWithUsername AND Username = :LoginId)
OR (:AllowSignInWithEmail AND Email = :LoginId) OR (:AllowSignInWithEmail AND Email = :LoginId)`,
OR (:LdapEnabled AND AuthService = '`+model.USER_AUTH_SERVICE_LDAP+`' AND AuthData = :LoginId)`,
params); err != nil { params); err != nil {
result.Err = model.NewAppError("SqlUserStore.GetForLogin", "store.sql_user.get_for_login.app_error", nil, err.Error(), http.StatusInternalServerError) result.Err = model.NewAppError("SqlUserStore.GetForLogin", "store.sql_user.get_for_login.app_error", nil, err.Error(), http.StatusInternalServerError)
} else if len(users) == 1 { } else if len(users) == 1 {

Просмотреть файл

@@ -231,7 +231,7 @@ type UserStore interface {
GetByAuth(authData *string, authService string) StoreChannel GetByAuth(authData *string, authService string) StoreChannel
GetAllUsingAuthService(authService string) StoreChannel GetAllUsingAuthService(authService string) StoreChannel
GetByUsername(username string) StoreChannel GetByUsername(username string) StoreChannel
GetForLogin(loginId string, allowSignInWithUsername, allowSignInWithEmail, ldapEnabled bool) StoreChannel GetForLogin(loginId string, allowSignInWithUsername, allowSignInWithEmail bool) StoreChannel
VerifyEmail(userId string) StoreChannel VerifyEmail(userId string) StoreChannel
GetEtagForAllProfiles() StoreChannel GetEtagForAllProfiles() StoreChannel
GetEtagForProfiles(teamId string) StoreChannel GetEtagForProfiles(teamId string) StoreChannel

Просмотреть файл

@@ -1,4 +1,4 @@
// Code generated by mockery v1.0.0 // Code generated by mockery v1.0.0. DO NOT EDIT.
// Regenerate this file using `make store-mocks`. // Regenerate this file using `make store-mocks`.

Просмотреть файл

@@ -1,4 +1,4 @@
// Code generated by mockery v1.0.0 // Code generated by mockery v1.0.0. DO NOT EDIT.
// Regenerate this file using `make store-mocks`. // Regenerate this file using `make store-mocks`.

Просмотреть файл

@@ -1,4 +1,4 @@
// Code generated by mockery v1.0.0 // Code generated by mockery v1.0.0. DO NOT EDIT.
// Regenerate this file using `make store-mocks`. // Regenerate this file using `make store-mocks`.

Просмотреть файл

@@ -1,4 +1,4 @@
// Code generated by mockery v1.0.0 // Code generated by mockery v1.0.0. DO NOT EDIT.
// Regenerate this file using `make store-mocks`. // Regenerate this file using `make store-mocks`.

Просмотреть файл

@@ -1,4 +1,4 @@
// Code generated by mockery v1.0.0 // Code generated by mockery v1.0.0. DO NOT EDIT.
// Regenerate this file using `make store-mocks`. // Regenerate this file using `make store-mocks`.

Просмотреть файл

@@ -1,4 +1,4 @@
// Code generated by mockery v1.0.0 // Code generated by mockery v1.0.0. DO NOT EDIT.
// Regenerate this file using `make store-mocks`. // Regenerate this file using `make store-mocks`.

Просмотреть файл

@@ -1,4 +1,4 @@
// Code generated by mockery v1.0.0 // Code generated by mockery v1.0.0. DO NOT EDIT.
// Regenerate this file using `make store-mocks`. // Regenerate this file using `make store-mocks`.

Просмотреть файл

@@ -1,4 +1,4 @@
// Code generated by mockery v1.0.0 // Code generated by mockery v1.0.0. DO NOT EDIT.
// Regenerate this file using `make store-mocks`. // Regenerate this file using `make store-mocks`.

Просмотреть файл

@@ -1,4 +1,4 @@
// Code generated by mockery v1.0.0 // Code generated by mockery v1.0.0. DO NOT EDIT.
// Regenerate this file using `make store-mocks`. // Regenerate this file using `make store-mocks`.

Просмотреть файл

@@ -1,4 +1,4 @@
// Code generated by mockery v1.0.0 // Code generated by mockery v1.0.0. DO NOT EDIT.
// Regenerate this file using `make store-mocks`. // Regenerate this file using `make store-mocks`.

Просмотреть файл

@@ -1,4 +1,4 @@
// Code generated by mockery v1.0.0 // Code generated by mockery v1.0.0. DO NOT EDIT.
// Regenerate this file using `make store-mocks`. // Regenerate this file using `make store-mocks`.

Просмотреть файл

@@ -1,4 +1,4 @@
// Code generated by mockery v1.0.0 // Code generated by mockery v1.0.0. DO NOT EDIT.
// Regenerate this file using `make store-mocks`. // Regenerate this file using `make store-mocks`.

Просмотреть файл

@@ -1,4 +1,4 @@
// Code generated by mockery v1.0.0 // Code generated by mockery v1.0.0. DO NOT EDIT.
// Regenerate this file using `make store-mocks`. // Regenerate this file using `make store-mocks`.

Просмотреть файл

@@ -1,4 +1,4 @@
// Code generated by mockery v1.0.0 // Code generated by mockery v1.0.0. DO NOT EDIT.
// Regenerate this file using `make store-mocks`. // Regenerate this file using `make store-mocks`.

Просмотреть файл

@@ -1,4 +1,4 @@
// Code generated by mockery v1.0.0 // Code generated by mockery v1.0.0. DO NOT EDIT.
// Regenerate this file using `make store-mocks`. // Regenerate this file using `make store-mocks`.

Просмотреть файл

@@ -1,4 +1,4 @@
// Code generated by mockery v1.0.0 // Code generated by mockery v1.0.0. DO NOT EDIT.
// Regenerate this file using `make store-mocks`. // Regenerate this file using `make store-mocks`.

Просмотреть файл

@@ -1,4 +1,4 @@
// Code generated by mockery v1.0.0 // Code generated by mockery v1.0.0. DO NOT EDIT.
// Regenerate this file using `make store-mocks`. // Regenerate this file using `make store-mocks`.

Просмотреть файл

@@ -1,4 +1,4 @@
// Code generated by mockery v1.0.0 // Code generated by mockery v1.0.0. DO NOT EDIT.
// Regenerate this file using `make store-mocks`. // Regenerate this file using `make store-mocks`.

Просмотреть файл

@@ -1,4 +1,4 @@
// Code generated by mockery v1.0.0 // Code generated by mockery v1.0.0. DO NOT EDIT.
// Regenerate this file using `make store-mocks`. // Regenerate this file using `make store-mocks`.

Просмотреть файл

@@ -1,4 +1,4 @@
// Code generated by mockery v1.0.0 // Code generated by mockery v1.0.0. DO NOT EDIT.
// Regenerate this file using `make store-mocks`. // Regenerate this file using `make store-mocks`.

Просмотреть файл

@@ -1,4 +1,4 @@
// Code generated by mockery v1.0.0 // Code generated by mockery v1.0.0. DO NOT EDIT.
// Regenerate this file using `make store-mocks`. // Regenerate this file using `make store-mocks`.

Просмотреть файл

@@ -1,4 +1,4 @@
// Code generated by mockery v1.0.0 // Code generated by mockery v1.0.0. DO NOT EDIT.
// Regenerate this file using `make store-mocks`. // Regenerate this file using `make store-mocks`.

Просмотреть файл

@@ -1,4 +1,4 @@
// Code generated by mockery v1.0.0 // Code generated by mockery v1.0.0. DO NOT EDIT.
// Regenerate this file using `make store-mocks`. // Regenerate this file using `make store-mocks`.

Просмотреть файл

@@ -1,4 +1,4 @@
// Code generated by mockery v1.0.0 // Code generated by mockery v1.0.0. DO NOT EDIT.
// Regenerate this file using `make store-mocks`. // Regenerate this file using `make store-mocks`.

Просмотреть файл

@@ -1,4 +1,4 @@
// Code generated by mockery v1.0.0 // Code generated by mockery v1.0.0. DO NOT EDIT.
// Regenerate this file using `make store-mocks`. // Regenerate this file using `make store-mocks`.

Просмотреть файл

@@ -1,4 +1,4 @@
// Code generated by mockery v1.0.0 // Code generated by mockery v1.0.0. DO NOT EDIT.
// Regenerate this file using `make store-mocks`. // Regenerate this file using `make store-mocks`.

Просмотреть файл

@@ -1,4 +1,4 @@
// Code generated by mockery v1.0.0 // Code generated by mockery v1.0.0. DO NOT EDIT.
// Regenerate this file using `make store-mocks`. // Regenerate this file using `make store-mocks`.

Просмотреть файл

@@ -1,4 +1,4 @@
// Code generated by mockery v1.0.0 // Code generated by mockery v1.0.0. DO NOT EDIT.
// Regenerate this file using `make store-mocks`. // Regenerate this file using `make store-mocks`.
@@ -258,13 +258,13 @@ func (_m *UserStore) GetEtagForProfilesNotInTeam(teamId string) store.StoreChann
return r0 return r0
} }
// GetForLogin provides a mock function with given fields: loginId, allowSignInWithUsername, allowSignInWithEmail, ldapEnabled // GetForLogin provides a mock function with given fields: loginId, allowSignInWithUsername, allowSignInWithEmail
func (_m *UserStore) GetForLogin(loginId string, allowSignInWithUsername bool, allowSignInWithEmail bool, ldapEnabled bool) store.StoreChannel { func (_m *UserStore) GetForLogin(loginId string, allowSignInWithUsername bool, allowSignInWithEmail bool) store.StoreChannel {
ret := _m.Called(loginId, allowSignInWithUsername, allowSignInWithEmail, ldapEnabled) ret := _m.Called(loginId, allowSignInWithUsername, allowSignInWithEmail)
var r0 store.StoreChannel var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(string, bool, bool, bool) store.StoreChannel); ok { if rf, ok := ret.Get(0).(func(string, bool, bool) store.StoreChannel); ok {
r0 = rf(loginId, allowSignInWithUsername, allowSignInWithEmail, ldapEnabled) r0 = rf(loginId, allowSignInWithUsername, allowSignInWithEmail)
} else { } else {
if ret.Get(0) != nil { if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel) r0 = ret.Get(0).(store.StoreChannel)

Просмотреть файл

@@ -1,4 +1,4 @@
// Code generated by mockery v1.0.0 // Code generated by mockery v1.0.0. DO NOT EDIT.
// Regenerate this file using `make store-mocks`. // Regenerate this file using `make store-mocks`.

Просмотреть файл

@@ -1091,64 +1091,26 @@ func testUserStoreGetForLogin(t *testing.T, ss store.Store) {
} }
store.Must(ss.User().Save(u2)) store.Must(ss.User().Save(u2))
if result := <-ss.User().GetForLogin(u1.Username, true, true, true); result.Err != nil { if result := <-ss.User().GetForLogin(u1.Username, true, true); result.Err != nil {
t.Fatal("Should have gotten user by username", result.Err) t.Fatal("Should have gotten user by username", result.Err)
} else if result.Data.(*model.User).Id != u1.Id { } else if result.Data.(*model.User).Id != u1.Id {
t.Fatal("Should have gotten user1 by username") t.Fatal("Should have gotten user1 by username")
} }
if result := <-ss.User().GetForLogin(u1.Email, true, true, true); result.Err != nil { if result := <-ss.User().GetForLogin(u1.Email, true, true); result.Err != nil {
t.Fatal("Should have gotten user by email", result.Err) t.Fatal("Should have gotten user by email", result.Err)
} else if result.Data.(*model.User).Id != u1.Id { } else if result.Data.(*model.User).Id != u1.Id {
t.Fatal("Should have gotten user1 by email") t.Fatal("Should have gotten user1 by email")
} }
if result := <-ss.User().GetForLogin(*u2.AuthData, true, true, true); result.Err != nil {
t.Fatal("Should have gotten user by AD/LDAP AuthData", result.Err)
} else if result.Data.(*model.User).Id != u2.Id {
t.Fatal("Should have gotten user2 by AD/LDAP AuthData")
}
// prevent getting user by AuthData when they're not an LDAP user
if result := <-ss.User().GetForLogin(*u1.AuthData, true, true, true); result.Err == nil {
t.Fatal("Should not have gotten user by non-AD/LDAP AuthData")
}
// prevent getting user when different login methods are disabled // prevent getting user when different login methods are disabled
if result := <-ss.User().GetForLogin(u1.Username, false, true, true); result.Err == nil { if result := <-ss.User().GetForLogin(u1.Username, false, true); result.Err == nil {
t.Fatal("Should have failed to get user1 by username") t.Fatal("Should have failed to get user1 by username")
} }
if result := <-ss.User().GetForLogin(u1.Email, true, false, true); result.Err == nil { if result := <-ss.User().GetForLogin(u1.Email, true, false); result.Err == nil {
t.Fatal("Should have failed to get user1 by email") t.Fatal("Should have failed to get user1 by email")
} }
if result := <-ss.User().GetForLogin(*u2.AuthData, true, true, false); result.Err == nil {
t.Fatal("Should have failed to get user3 by AD/LDAP AuthData")
}
auth3 := model.NewId()
// test a special case where two users will have conflicting login information so we throw a special error
u3 := &model.User{
Email: model.NewId(),
Username: model.NewId(),
AuthService: model.USER_AUTH_SERVICE_LDAP,
AuthData: &auth3,
}
store.Must(ss.User().Save(u3))
u4 := &model.User{
Email: model.NewId(),
Username: model.NewId(),
AuthService: model.USER_AUTH_SERVICE_LDAP,
AuthData: &u3.Username,
}
store.Must(ss.User().Save(u4))
if err := (<-ss.User().GetForLogin(u3.Username, true, true, true)).Err; err == nil {
t.Fatal("Should have failed to get users with conflicting login information")
}
} }
func testUserStoreUpdatePassword(t *testing.T, ss store.Store) { func testUserStoreUpdatePassword(t *testing.T, ss store.Store) {

Просмотреть файл

@@ -4,6 +4,7 @@
package utils package utils
import ( import (
"io"
"net/http" "net/http"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
@@ -15,7 +16,7 @@ type FileBackend interface {
ReadFile(path string) ([]byte, *model.AppError) ReadFile(path string) ([]byte, *model.AppError)
CopyFile(oldPath, newPath string) *model.AppError CopyFile(oldPath, newPath string) *model.AppError
MoveFile(oldPath, newPath string) *model.AppError MoveFile(oldPath, newPath string) *model.AppError
WriteFile(f []byte, path string) *model.AppError WriteFile(fr io.Reader, path string) (int64, *model.AppError)
RemoveFile(path string) *model.AppError RemoveFile(path string) *model.AppError
ListDirectory(path string) (*[]string, *model.AppError) ListDirectory(path string) (*[]string, *model.AppError)

Просмотреть файл

@@ -4,6 +4,8 @@
package utils package utils
import ( import (
"bytes"
"io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os" "os"
@@ -22,8 +24,8 @@ type LocalFileBackend struct {
} }
func (b *LocalFileBackend) TestConnection() *model.AppError { func (b *LocalFileBackend) TestConnection() *model.AppError {
f := []byte("testingwrite") f := bytes.NewReader([]byte("testingwrite"))
if err := writeFileLocally(f, filepath.Join(b.directory, TEST_FILE_PATH)); err != nil { if _, err := writeFileLocally(f, filepath.Join(b.directory, TEST_FILE_PATH)); err != nil {
return model.NewAppError("TestFileConnection", "Don't have permissions to write to local path specified or other error.", nil, err.Error(), http.StatusInternalServerError) return model.NewAppError("TestFileConnection", "Don't have permissions to write to local path specified or other error.", nil, err.Error(), http.StatusInternalServerError)
} }
os.Remove(filepath.Join(b.directory, TEST_FILE_PATH)) os.Remove(filepath.Join(b.directory, TEST_FILE_PATH))
@@ -58,21 +60,25 @@ func (b *LocalFileBackend) MoveFile(oldPath, newPath string) *model.AppError {
return nil return nil
} }
func (b *LocalFileBackend) WriteFile(f []byte, path string) *model.AppError { func (b *LocalFileBackend) WriteFile(fr io.Reader, path string) (int64, *model.AppError) {
return writeFileLocally(f, filepath.Join(b.directory, path)) return writeFileLocally(fr, filepath.Join(b.directory, path))
} }
func writeFileLocally(f []byte, path string) *model.AppError { func writeFileLocally(fr io.Reader, path string) (int64, *model.AppError) {
if err := os.MkdirAll(filepath.Dir(path), 0774); err != nil { if err := os.MkdirAll(filepath.Dir(path), 0774); err != nil {
directory, _ := filepath.Abs(filepath.Dir(path)) directory, _ := filepath.Abs(filepath.Dir(path))
return model.NewAppError("WriteFile", "api.file.write_file_locally.create_dir.app_error", nil, "directory="+directory+", err="+err.Error(), http.StatusInternalServerError) return 0, model.NewAppError("WriteFile", "api.file.write_file_locally.create_dir.app_error", nil, "directory="+directory+", err="+err.Error(), http.StatusInternalServerError)
} }
fw, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err := ioutil.WriteFile(path, f, 0644); err != nil { if err != nil {
return model.NewAppError("WriteFile", "api.file.write_file_locally.writing.app_error", nil, err.Error(), http.StatusInternalServerError) return 0, model.NewAppError("WriteFile", "api.file.write_file_locally.writing.app_error", nil, err.Error(), http.StatusInternalServerError)
} }
defer fw.Close()
return nil written, err := io.Copy(fw, fr)
if err != nil {
return written, model.NewAppError("WriteFile", "api.file.write_file_locally.writing.app_error", nil, err.Error(), http.StatusInternalServerError)
}
return written, nil
} }
func (b *LocalFileBackend) RemoveFile(path string) *model.AppError { func (b *LocalFileBackend) RemoveFile(path string) *model.AppError {

Просмотреть файл

@@ -4,7 +4,7 @@
package utils package utils
import ( import (
"bytes" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os" "os"
@@ -136,10 +136,10 @@ func (b *S3FileBackend) MoveFile(oldPath, newPath string) *model.AppError {
return nil return nil
} }
func (b *S3FileBackend) WriteFile(f []byte, path string) *model.AppError { func (b *S3FileBackend) WriteFile(fr io.Reader, path string) (int64, *model.AppError) {
s3Clnt, err := b.s3New() s3Clnt, err := b.s3New()
if err != nil { if err != nil {
return model.NewAppError("WriteFile", "api.file.write_file.s3.app_error", nil, err.Error(), http.StatusInternalServerError) return 0, model.NewAppError("WriteFile", "api.file.write_file.s3.app_error", nil, err.Error(), http.StatusInternalServerError)
} }
var contentType string var contentType string
@@ -150,12 +150,12 @@ func (b *S3FileBackend) WriteFile(f []byte, path string) *model.AppError {
} }
options := s3PutOptions(b.encrypt, contentType) options := s3PutOptions(b.encrypt, contentType)
written, err := s3Clnt.PutObject(b.bucket, path, fr, -1, options)
if _, err = s3Clnt.PutObject(b.bucket, path, bytes.NewReader(f), -1, options); err != nil { if err != nil {
return model.NewAppError("WriteFile", "api.file.write_file.s3.app_error", nil, err.Error(), http.StatusInternalServerError) return written, model.NewAppError("WriteFile", "api.file.write_file.s3.app_error", nil, err.Error(), http.StatusInternalServerError)
} }
return nil return written, nil
} }
func (b *S3FileBackend) RemoveFile(path string) *model.AppError { func (b *S3FileBackend) RemoveFile(path string) *model.AppError {

Просмотреть файл

@@ -4,6 +4,7 @@
package utils package utils
import ( import (
"bytes"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
@@ -95,7 +96,9 @@ func (s *FileBackendTestSuite) TestReadWriteFile() {
b := []byte("test") b := []byte("test")
path := "tests/" + model.NewId() path := "tests/" + model.NewId()
s.Nil(s.backend.WriteFile(b, path)) written, err := s.backend.WriteFile(bytes.NewReader(b), path)
s.Nil(err)
s.EqualValues(len(b), written, "expected given number of bytes to have been written")
defer s.backend.RemoveFile(path) defer s.backend.RemoveFile(path)
read, err := s.backend.ReadFile(path) read, err := s.backend.ReadFile(path)
@@ -109,7 +112,9 @@ func (s *FileBackendTestSuite) TestReadWriteFileImage() {
b := []byte("testimage") b := []byte("testimage")
path := "tests/" + model.NewId() + ".png" path := "tests/" + model.NewId() + ".png"
s.Nil(s.backend.WriteFile(b, path)) written, err := s.backend.WriteFile(bytes.NewReader(b), path)
s.Nil(err)
s.EqualValues(len(b), written, "expected given number of bytes to have been written")
defer s.backend.RemoveFile(path) defer s.backend.RemoveFile(path)
read, err := s.backend.ReadFile(path) read, err := s.backend.ReadFile(path)
@@ -124,8 +129,9 @@ func (s *FileBackendTestSuite) TestCopyFile() {
path1 := "tests/" + model.NewId() path1 := "tests/" + model.NewId()
path2 := "tests/" + model.NewId() path2 := "tests/" + model.NewId()
err := s.backend.WriteFile(b, path1) written, err := s.backend.WriteFile(bytes.NewReader(b), path1)
s.Nil(err) s.Nil(err)
s.EqualValues(len(b), written, "expected given number of bytes to have been written")
defer s.backend.RemoveFile(path1) defer s.backend.RemoveFile(path1)
err = s.backend.CopyFile(path1, path2) err = s.backend.CopyFile(path1, path2)
@@ -144,8 +150,9 @@ func (s *FileBackendTestSuite) TestCopyFileToDirectoryThatDoesntExist() {
path1 := "tests/" + model.NewId() path1 := "tests/" + model.NewId()
path2 := "tests/newdirectory/" + model.NewId() path2 := "tests/newdirectory/" + model.NewId()
err := s.backend.WriteFile(b, path1) written, err := s.backend.WriteFile(bytes.NewReader(b), path1)
s.Nil(err) s.Nil(err)
s.EqualValues(len(b), written, "expected given number of bytes to have been written")
defer s.backend.RemoveFile(path1) defer s.backend.RemoveFile(path1)
err = s.backend.CopyFile(path1, path2) err = s.backend.CopyFile(path1, path2)
@@ -164,13 +171,15 @@ func (s *FileBackendTestSuite) TestMoveFile() {
path1 := "tests/" + model.NewId() path1 := "tests/" + model.NewId()
path2 := "tests/" + model.NewId() path2 := "tests/" + model.NewId()
s.Nil(s.backend.WriteFile(b, path1)) written, err := s.backend.WriteFile(bytes.NewReader(b), path1)
s.Nil(err)
s.EqualValues(len(b), written, "expected given number of bytes to have been written")
defer s.backend.RemoveFile(path1) defer s.backend.RemoveFile(path1)
s.Nil(s.backend.MoveFile(path1, path2)) s.Nil(s.backend.MoveFile(path1, path2))
defer s.backend.RemoveFile(path2) defer s.backend.RemoveFile(path2)
_, err := s.backend.ReadFile(path1) _, err = s.backend.ReadFile(path1)
s.Error(err) s.Error(err)
_, err = s.backend.ReadFile(path2) _, err = s.backend.ReadFile(path2)
@@ -181,15 +190,26 @@ func (s *FileBackendTestSuite) TestRemoveFile() {
b := []byte("test") b := []byte("test")
path := "tests/" + model.NewId() path := "tests/" + model.NewId()
s.Nil(s.backend.WriteFile(b, path)) written, err := s.backend.WriteFile(bytes.NewReader(b), path)
s.Nil(err)
s.EqualValues(len(b), written, "expected given number of bytes to have been written")
s.Nil(s.backend.RemoveFile(path)) s.Nil(s.backend.RemoveFile(path))
_, err := s.backend.ReadFile(path) _, err = s.backend.ReadFile(path)
s.Error(err) s.Error(err)
s.Nil(s.backend.WriteFile(b, "tests2/foo")) written, err = s.backend.WriteFile(bytes.NewReader(b), "tests2/foo")
s.Nil(s.backend.WriteFile(b, "tests2/bar")) s.Nil(err)
s.Nil(s.backend.WriteFile(b, "tests2/asdf")) s.EqualValues(len(b), written, "expected given number of bytes to have been written")
written, err = s.backend.WriteFile(bytes.NewReader(b), "tests2/bar")
s.Nil(err)
s.EqualValues(len(b), written, "expected given number of bytes to have been written")
written, err = s.backend.WriteFile(bytes.NewReader(b), "tests2/asdf")
s.Nil(err)
s.EqualValues(len(b), written, "expected given number of bytes to have been written")
s.Nil(s.backend.RemoveDirectory("tests2")) s.Nil(s.backend.RemoveDirectory("tests2"))
} }
@@ -198,9 +218,14 @@ func (s *FileBackendTestSuite) TestListDirectory() {
path1 := "19700101/" + model.NewId() path1 := "19700101/" + model.NewId()
path2 := "19800101/" + model.NewId() path2 := "19800101/" + model.NewId()
s.Nil(s.backend.WriteFile(b, path1)) written, err := s.backend.WriteFile(bytes.NewReader(b), path1)
s.Nil(err)
s.EqualValues(len(b), written, "expected given number of bytes to have been written")
defer s.backend.RemoveFile(path1) defer s.backend.RemoveFile(path1)
s.Nil(s.backend.WriteFile(b, path2))
written, err = s.backend.WriteFile(bytes.NewReader(b), path2)
s.Nil(err)
s.EqualValues(len(b), written, "expected given number of bytes to have been written")
defer s.backend.RemoveFile(path2) defer s.backend.RemoveFile(path2)
paths, err := s.backend.ListDirectory("") paths, err := s.backend.ListDirectory("")
@@ -222,13 +247,21 @@ func (s *FileBackendTestSuite) TestListDirectory() {
func (s *FileBackendTestSuite) TestRemoveDirectory() { func (s *FileBackendTestSuite) TestRemoveDirectory() {
b := []byte("test") b := []byte("test")
s.Nil(s.backend.WriteFile(b, "tests2/foo")) written, err := s.backend.WriteFile(bytes.NewReader(b), "tests2/foo")
s.Nil(s.backend.WriteFile(b, "tests2/bar")) s.Nil(err)
s.Nil(s.backend.WriteFile(b, "tests2/aaa")) s.EqualValues(len(b), written, "expected given number of bytes to have been written")
written, err = s.backend.WriteFile(bytes.NewReader(b), "tests2/bar")
s.Nil(err)
s.EqualValues(len(b), written, "expected given number of bytes to have been written")
written, err = s.backend.WriteFile(bytes.NewReader(b), "tests2/aaa")
s.Nil(err)
s.EqualValues(len(b), written, "expected given number of bytes to have been written")
s.Nil(s.backend.RemoveDirectory("tests2")) s.Nil(s.backend.RemoveDirectory("tests2"))
_, err := s.backend.ReadFile("tests2/foo") _, err = s.backend.ReadFile("tests2/foo")
s.Error(err) s.Error(err)
_, err = s.backend.ReadFile("tests2/bar") _, err = s.backend.ReadFile("tests2/bar")
s.Error(err) s.Error(err)

Просмотреть файл

@@ -150,8 +150,10 @@ func TestSendMailUsingConfig(t *testing.T) {
filePath2 := fmt.Sprintf("test2/%s", fileName) filePath2 := fmt.Sprintf("test2/%s", fileName)
fileContents1 := []byte("hello world") fileContents1 := []byte("hello world")
fileContents2 := []byte("foo bar") fileContents2 := []byte("foo bar")
assert.Nil(t, fileBackend.WriteFile(fileContents1, filePath1)) _, err := fileBackend.WriteFile(bytes.NewReader(fileContents1), filePath1)
assert.Nil(t, fileBackend.WriteFile(fileContents2, filePath2)) assert.Nil(t, err)
_, err := fileBackend.WriteFile(bytes.NewReader(fileContents2), filePath2)
assert.Nil(t, err)
defer fileBackend.RemoveFile(filePath1) defer fileBackend.RemoveFile(filePath1)
defer fileBackend.RemoveFile(filePath2) defer fileBackend.RemoveFile(filePath2)