user service: add oauth and profile images (#17784)
* users: add cache to service * reflect review comments * add oauth * move profile picture genaration * reflect review comments * move default bot image to users package * add missing wraps and apply suggestions * add comment for app.GetSession
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
79d4e9e9a9
Коммит
f69b28610a
@@ -328,8 +328,6 @@ type AppIface interface {
|
|||||||
// the member's group memberships and the configuration of those groups to the syncable. This method should only
|
// the member's group memberships and the configuration of those groups to the syncable. This method should only
|
||||||
// be invoked on group-synced (aka group-constrained) syncables.
|
// be invoked on group-synced (aka group-constrained) syncables.
|
||||||
SyncSyncableRoles(syncableID string, syncableType model.GroupSyncableType) *model.AppError
|
SyncSyncableRoles(syncableID string, syncableType model.GroupSyncableType) *model.AppError
|
||||||
// TODO: migrate this after the user service implementation is completed
|
|
||||||
GetSanitizeOptions(asAdmin bool) map[string]bool
|
|
||||||
// TeamMembersMinusGroupMembers returns the set of users on the given team minus the set of users in the given
|
// TeamMembersMinusGroupMembers returns the set of users on the given team minus the set of users in the given
|
||||||
// groups.
|
// groups.
|
||||||
//
|
//
|
||||||
@@ -721,6 +719,7 @@ type AppIface interface {
|
|||||||
GetSamlCertificateStatus() *model.SamlCertificateStatus
|
GetSamlCertificateStatus() *model.SamlCertificateStatus
|
||||||
GetSamlMetadata() (string, *model.AppError)
|
GetSamlMetadata() (string, *model.AppError)
|
||||||
GetSamlMetadataFromIdp(idpMetadataUrl string) (*model.SamlMetadataResponse, *model.AppError)
|
GetSamlMetadataFromIdp(idpMetadataUrl string) (*model.SamlMetadataResponse, *model.AppError)
|
||||||
|
GetSanitizeOptions(asAdmin bool) map[string]bool
|
||||||
GetScheme(id string) (*model.Scheme, *model.AppError)
|
GetScheme(id string) (*model.Scheme, *model.AppError)
|
||||||
GetSchemeByName(name string) (*model.Scheme, *model.AppError)
|
GetSchemeByName(name string) (*model.Scheme, *model.AppError)
|
||||||
GetSchemeRolesForTeam(teamID string) (string, string, string, *model.AppError)
|
GetSchemeRolesForTeam(teamID string) (string, string, string, *model.AppError)
|
||||||
@@ -1072,7 +1071,6 @@ type AppIface interface {
|
|||||||
UpdateRemoteClusterTopics(remoteClusterId string, topics string) (*model.RemoteCluster, *model.AppError)
|
UpdateRemoteClusterTopics(remoteClusterId string, topics string) (*model.RemoteCluster, *model.AppError)
|
||||||
UpdateRole(role *model.Role) (*model.Role, *model.AppError)
|
UpdateRole(role *model.Role) (*model.Role, *model.AppError)
|
||||||
UpdateScheme(scheme *model.Scheme) (*model.Scheme, *model.AppError)
|
UpdateScheme(scheme *model.Scheme) (*model.Scheme, *model.AppError)
|
||||||
UpdateSessionsIsGuest(userID string, isGuest bool)
|
|
||||||
UpdateSharedChannel(sc *model.SharedChannel) (*model.SharedChannel, error)
|
UpdateSharedChannel(sc *model.SharedChannel) (*model.SharedChannel, error)
|
||||||
UpdateSharedChannelRemoteCursor(id string, cursor model.GetPostsSinceForSyncCursor) error
|
UpdateSharedChannelRemoteCursor(id string, cursor model.GetPostsSinceForSyncCursor) error
|
||||||
UpdateSidebarCategories(userID, teamID string, categories []*model.SidebarCategoryWithChannels) ([]*model.SidebarCategoryWithChannels, *model.AppError)
|
UpdateSidebarCategories(userID, teamID string, categories []*model.SidebarCategoryWithChannels) ([]*model.SidebarCategoryWithChannels, *model.AppError)
|
||||||
|
|||||||
@@ -1970,10 +1970,12 @@ func TestMarkChannelsAsViewedPanic(t *testing.T) {
|
|||||||
}
|
}
|
||||||
mockChannelStore.On("UpdateLastViewedAt", []string{"channelID"}, "userID", false).Return(times, nil)
|
mockChannelStore.On("UpdateLastViewedAt", []string{"channelID"}, "userID", false).Return(times, nil)
|
||||||
mockSessionStore := mocks.SessionStore{}
|
mockSessionStore := mocks.SessionStore{}
|
||||||
|
mockOAuthStore := mocks.OAuthStore{}
|
||||||
var err error
|
var err error
|
||||||
th.App.srv.userService, err = users.New(users.ServiceInitializer{
|
th.App.srv.userService, err = users.New(users.ServiceConfig{
|
||||||
UserStore: &mockUserStore,
|
UserStore: &mockUserStore,
|
||||||
SessionStore: &mockSessionStore,
|
SessionStore: &mockSessionStore,
|
||||||
|
OAuthStore: &mockOAuthStore,
|
||||||
ConfigFn: th.App.srv.Config,
|
ConfigFn: th.App.srv.Config,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ func (a *App) DoLogin(c *request.Context, w http.ResponseWriter, r *http.Request
|
|||||||
session.GenerateCSRF()
|
session.GenerateCSRF()
|
||||||
|
|
||||||
if deviceID != "" {
|
if deviceID != "" {
|
||||||
a.SetSessionExpireInDays(session, *a.Config().ServiceSettings.SessionLengthMobileInDays)
|
a.srv.userService.SetSessionExpireInDays(session, *a.Config().ServiceSettings.SessionLengthMobileInDays)
|
||||||
|
|
||||||
// A special case where we logout of all other sessions with the same Id
|
// A special case where we logout of all other sessions with the same Id
|
||||||
if err := a.RevokeSessionsForDeviceId(user.Id, deviceID, ""); err != nil {
|
if err := a.RevokeSessionsForDeviceId(user.Id, deviceID, ""); err != nil {
|
||||||
@@ -185,11 +185,11 @@ func (a *App) DoLogin(c *request.Context, w http.ResponseWriter, r *http.Request
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else if isMobile {
|
} else if isMobile {
|
||||||
a.SetSessionExpireInDays(session, *a.Config().ServiceSettings.SessionLengthMobileInDays)
|
a.srv.userService.SetSessionExpireInDays(session, *a.Config().ServiceSettings.SessionLengthMobileInDays)
|
||||||
} else if isOAuthUser || isSaml {
|
} else if isOAuthUser || isSaml {
|
||||||
a.SetSessionExpireInDays(session, *a.Config().ServiceSettings.SessionLengthSSOInDays)
|
a.srv.userService.SetSessionExpireInDays(session, *a.Config().ServiceSettings.SessionLengthSSOInDays)
|
||||||
} else {
|
} else {
|
||||||
a.SetSessionExpireInDays(session, *a.Config().ServiceSettings.SessionLengthWebInDays)
|
a.srv.userService.SetSessionExpireInDays(session, *a.Config().ServiceSettings.SessionLengthWebInDays)
|
||||||
}
|
}
|
||||||
|
|
||||||
ua := uasurfer.Parse(r.UserAgent())
|
ua := uasurfer.Parse(r.UserAgent())
|
||||||
|
|||||||
28
app/oauth.go
28
app/oauth.go
@@ -20,6 +20,7 @@ import (
|
|||||||
"github.com/mattermost/mattermost-server/v5/app/request"
|
"github.com/mattermost/mattermost-server/v5/app/request"
|
||||||
"github.com/mattermost/mattermost-server/v5/einterfaces"
|
"github.com/mattermost/mattermost-server/v5/einterfaces"
|
||||||
"github.com/mattermost/mattermost-server/v5/model"
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
|
"github.com/mattermost/mattermost-server/v5/services/users"
|
||||||
"github.com/mattermost/mattermost-server/v5/shared/i18n"
|
"github.com/mattermost/mattermost-server/v5/shared/i18n"
|
||||||
"github.com/mattermost/mattermost-server/v5/shared/mlog"
|
"github.com/mattermost/mattermost-server/v5/shared/mlog"
|
||||||
"github.com/mattermost/mattermost-server/v5/store"
|
"github.com/mattermost/mattermost-server/v5/store"
|
||||||
@@ -369,7 +370,7 @@ func (a *App) newSession(appName string, user *model.User) (*model.Session, *mod
|
|||||||
// Set new token an session
|
// Set new token an session
|
||||||
session := &model.Session{UserId: user.Id, Roles: user.Roles, IsOAuth: true}
|
session := &model.Session{UserId: user.Id, Roles: user.Roles, IsOAuth: true}
|
||||||
session.GenerateCSRF()
|
session.GenerateCSRF()
|
||||||
a.SetSessionExpireInDays(session, *a.Config().ServiceSettings.SessionLengthSSOInDays)
|
a.srv.userService.SetSessionExpireInDays(session, *a.Config().ServiceSettings.SessionLengthSSOInDays)
|
||||||
session.AddProp(model.SESSION_PROP_PLATFORM, appName)
|
session.AddProp(model.SESSION_PROP_PLATFORM, appName)
|
||||||
session.AddProp(model.SESSION_PROP_OS, "OAuth2")
|
session.AddProp(model.SESSION_PROP_OS, "OAuth2")
|
||||||
session.AddProp(model.SESSION_PROP_BROWSER, "OAuth2")
|
session.AddProp(model.SESSION_PROP_BROWSER, "OAuth2")
|
||||||
@@ -518,30 +519,15 @@ func (a *App) RegenerateOAuthAppSecret(app *model.OAuthApp) (*model.OAuthApp, *m
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) RevokeAccessToken(token string) *model.AppError {
|
func (a *App) RevokeAccessToken(token string) *model.AppError {
|
||||||
session, _ := a.GetSession(token)
|
if err := a.srv.userService.RevokeAccessToken(token); err != nil {
|
||||||
|
switch {
|
||||||
defer a.srv.userService.ReturnSessionToPool(session)
|
case errors.Is(err, users.GetTokenError):
|
||||||
|
|
||||||
schan := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
schan <- a.Srv().Store.Session().Remove(token)
|
|
||||||
close(schan)
|
|
||||||
}()
|
|
||||||
|
|
||||||
if _, err := a.Srv().Store.OAuth().GetAccessData(token); err != nil {
|
|
||||||
return model.NewAppError("RevokeAccessToken", "api.oauth.revoke_access_token.get.app_error", nil, err.Error(), http.StatusBadRequest)
|
return model.NewAppError("RevokeAccessToken", "api.oauth.revoke_access_token.get.app_error", nil, err.Error(), http.StatusBadRequest)
|
||||||
}
|
case errors.Is(err, users.DeleteTokenError):
|
||||||
|
|
||||||
if err := a.Srv().Store.OAuth().RemoveAccessData(token); err != nil {
|
|
||||||
return model.NewAppError("RevokeAccessToken", "api.oauth.revoke_access_token.del_token.app_error", nil, err.Error(), http.StatusInternalServerError)
|
return model.NewAppError("RevokeAccessToken", "api.oauth.revoke_access_token.del_token.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
}
|
case errors.Is(err, users.DeleteSessionError):
|
||||||
|
|
||||||
if err := <-schan; err != nil {
|
|
||||||
return model.NewAppError("RevokeAccessToken", "api.oauth.revoke_access_token.del_session.app_error", nil, err.Error(), http.StatusInternalServerError)
|
return model.NewAppError("RevokeAccessToken", "api.oauth.revoke_access_token.del_session.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
if session != nil {
|
|
||||||
a.ClearSessionCacheForUser(session.UserId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -70,9 +70,6 @@ func TestOAuthRevokeAccessToken(t *testing.T) {
|
|||||||
th := Setup(t)
|
th := Setup(t)
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
err := th.App.RevokeAccessToken(model.NewRandomString(16))
|
|
||||||
require.NotNil(t, err, "Should have failed bad token")
|
|
||||||
|
|
||||||
session := &model.Session{}
|
session := &model.Session{}
|
||||||
session.CreateAt = model.GetMillis()
|
session.CreateAt = model.GetMillis()
|
||||||
session.UserId = model.NewId()
|
session.UserId = model.NewId()
|
||||||
@@ -80,22 +77,12 @@ func TestOAuthRevokeAccessToken(t *testing.T) {
|
|||||||
session.Roles = model.SYSTEM_USER_ROLE_ID
|
session.Roles = model.SYSTEM_USER_ROLE_ID
|
||||||
th.App.SetSessionExpireInDays(session, 1)
|
th.App.SetSessionExpireInDays(session, 1)
|
||||||
|
|
||||||
session, _ = th.App.CreateSession(session)
|
var err *model.AppError
|
||||||
|
session, err = th.App.CreateSession(session)
|
||||||
|
require.Nil(t, err)
|
||||||
err = th.App.RevokeAccessToken(session.Token)
|
err = th.App.RevokeAccessToken(session.Token)
|
||||||
require.NotNil(t, err, "Should have failed does not have an access token")
|
require.NotNil(t, err, "Should have failed does not have an access token")
|
||||||
|
require.Equal(t, http.StatusBadRequest, err.StatusCode)
|
||||||
accessData := &model.AccessData{}
|
|
||||||
accessData.Token = session.Token
|
|
||||||
accessData.UserId = session.UserId
|
|
||||||
accessData.RedirectUri = "http://example.com"
|
|
||||||
accessData.ClientId = model.NewId()
|
|
||||||
accessData.ExpiresAt = session.ExpiresAt
|
|
||||||
|
|
||||||
_, nErr := th.App.Srv().Store.OAuth().SaveAccessData(accessData)
|
|
||||||
require.NoError(t, nErr)
|
|
||||||
|
|
||||||
err = th.App.RevokeAccessToken(accessData.Token)
|
|
||||||
require.Nil(t, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOAuthDeleteApp(t *testing.T) {
|
func TestOAuthDeleteApp(t *testing.T) {
|
||||||
@@ -120,7 +107,7 @@ func TestOAuthDeleteApp(t *testing.T) {
|
|||||||
session.Token = model.NewId()
|
session.Token = model.NewId()
|
||||||
session.Roles = model.SYSTEM_USER_ROLE_ID
|
session.Roles = model.SYSTEM_USER_ROLE_ID
|
||||||
session.IsOAuth = true
|
session.IsOAuth = true
|
||||||
th.App.SetSessionExpireInDays(session, 1)
|
th.App.srv.userService.SetSessionExpireInDays(session, 1)
|
||||||
|
|
||||||
session, _ = th.App.CreateSession(session)
|
session, _ = th.App.CreateSession(session)
|
||||||
|
|
||||||
|
|||||||
@@ -16469,21 +16469,6 @@ func (a *OpenTracingAppLayer) UpdateScheme(scheme *model.Scheme) (*model.Scheme,
|
|||||||
return resultVar0, resultVar1
|
return resultVar0, resultVar1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *OpenTracingAppLayer) UpdateSessionsIsGuest(userID string, isGuest bool) {
|
|
||||||
origCtx := a.ctx
|
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UpdateSessionsIsGuest")
|
|
||||||
|
|
||||||
a.ctx = newCtx
|
|
||||||
a.app.Srv().Store.SetContext(newCtx)
|
|
||||||
defer func() {
|
|
||||||
a.app.Srv().Store.SetContext(origCtx)
|
|
||||||
a.ctx = origCtx
|
|
||||||
}()
|
|
||||||
|
|
||||||
defer span.Finish()
|
|
||||||
a.app.UpdateSessionsIsGuest(userID, isGuest)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *OpenTracingAppLayer) UpdateSharedChannel(sc *model.SharedChannel) (*model.SharedChannel, error) {
|
func (a *OpenTracingAppLayer) UpdateSharedChannel(sc *model.SharedChannel) (*model.SharedChannel, error) {
|
||||||
origCtx := a.ctx
|
origCtx := a.ctx
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UpdateSharedChannel")
|
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UpdateSharedChannel")
|
||||||
|
|||||||
@@ -409,9 +409,10 @@ func NewServer(options ...Option) (*Server, error) {
|
|||||||
return nil, errors.Wrap(err, "cannot create store")
|
return nil, errors.Wrap(err, "cannot create store")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.userService, err = users.New(users.ServiceInitializer{
|
s.userService, err = users.New(users.ServiceConfig{
|
||||||
UserStore: s.Store.User(),
|
UserStore: s.Store.User(),
|
||||||
SessionStore: s.Store.Session(),
|
SessionStore: s.Store.Session(),
|
||||||
|
OAuthStore: s.Store.OAuth(),
|
||||||
ConfigFn: s.Config,
|
ConfigFn: s.Config,
|
||||||
Metrics: s.Metrics,
|
Metrics: s.Metrics,
|
||||||
Cluster: s.Cluster,
|
Cluster: s.Cluster,
|
||||||
@@ -2264,18 +2265,18 @@ func (s *Server) GetProfileImage(user *model.User) ([]byte, bool, *model.AppErro
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) GetDefaultProfileImage(user *model.User) ([]byte, *model.AppError) {
|
func (s *Server) GetDefaultProfileImage(user *model.User) ([]byte, *model.AppError) {
|
||||||
var img []byte
|
img, err := s.userService.GetDefaultProfileImage(user)
|
||||||
var appErr *model.AppError
|
if err != nil {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, users.DefaultFontError):
|
||||||
|
return nil, model.NewAppError("GetDefaultProfileImage", "api.user.create_profile_image.default_font.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
case errors.Is(err, users.UserInitialsError):
|
||||||
|
return nil, model.NewAppError("GetDefaultProfileImage", "api.user.create_profile_image.initial.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
default:
|
||||||
|
return nil, model.NewAppError("GetDefaultProfileImage", "api.user.create_profile_image.encode.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if user.IsBot {
|
|
||||||
img = model.BotDefaultImage
|
|
||||||
appErr = nil
|
|
||||||
} else {
|
|
||||||
img, appErr = CreateProfileImage(user.Username, user.Id, *s.Config().FileSettings.InitialFont)
|
|
||||||
}
|
|
||||||
if appErr != nil {
|
|
||||||
return nil, appErr
|
|
||||||
}
|
|
||||||
return img, nil
|
return img, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
116
app/session.go
116
app/session.go
@@ -6,16 +6,15 @@ package app
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v5/audit"
|
"github.com/mattermost/mattermost-server/v5/audit"
|
||||||
"github.com/mattermost/mattermost-server/v5/model"
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
|
"github.com/mattermost/mattermost-server/v5/services/users"
|
||||||
"github.com/mattermost/mattermost-server/v5/shared/mlog"
|
"github.com/mattermost/mattermost-server/v5/shared/mlog"
|
||||||
"github.com/mattermost/mattermost-server/v5/store"
|
"github.com/mattermost/mattermost-server/v5/store"
|
||||||
"github.com/mattermost/mattermost-server/v5/store/sqlstore"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *App) CreateSession(session *model.Session) (*model.Session, *model.AppError) {
|
func (a *App) CreateSession(session *model.Session) (*model.Session, *model.AppError) {
|
||||||
@@ -64,15 +63,10 @@ func (a *App) GetRemoteClusterSession(token string, remoteId string) (*model.Ses
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) GetSession(token string) (*model.Session, *model.AppError) {
|
func (a *App) GetSession(token string) (*model.Session, *model.AppError) {
|
||||||
session, err := a.srv.userService.GetSession(token)
|
var session *model.Session
|
||||||
if err != nil {
|
// We intentionally skip the error check here, we only want to check if the token is valid.
|
||||||
return nil, model.NewAppError("GetSession", "app.session.get.app_error", nil, err.Error(), http.StatusInternalServerError)
|
// If we don't have the session we are going to create one with the token eventually.
|
||||||
}
|
if session, _ = a.srv.userService.GetSession(token); session != nil {
|
||||||
|
|
||||||
if session.Id == "" {
|
|
||||||
var nErr error
|
|
||||||
if session, nErr = a.Srv().Store.Session().Get(sqlstore.WithMaster(context.Background()), token); nErr == nil {
|
|
||||||
if session != nil {
|
|
||||||
if session.Token != token {
|
if session.Token != token {
|
||||||
return nil, model.NewAppError("GetSession", "api.context.invalid_token.error", map[string]interface{}{"Token": token, "Error": ""}, "session token is different from the one in DB", http.StatusUnauthorized)
|
return nil, model.NewAppError("GetSession", "api.context.invalid_token.error", map[string]interface{}{"Token": token, "Error": ""}, "session token is different from the one in DB", http.StatusUnauthorized)
|
||||||
}
|
}
|
||||||
@@ -81,10 +75,6 @@ func (a *App) GetSession(token string) (*model.Session, *model.AppError) {
|
|||||||
a.srv.userService.AddSessionToCache(session)
|
a.srv.userService.AddSessionToCache(session)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if nfErr := new(store.ErrNotFound); !errors.As(nErr, &nfErr) {
|
|
||||||
return nil, model.NewAppError("GetSession", "app.session.get.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var appErr *model.AppError
|
var appErr *model.AppError
|
||||||
if session == nil || session.Id == "" {
|
if session == nil || session.Id == "" {
|
||||||
@@ -134,7 +124,7 @@ func (a *App) GetSession(token string) (*model.Session, *model.AppError) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) GetSessions(userID string) ([]*model.Session, *model.AppError) {
|
func (a *App) GetSessions(userID string) ([]*model.Session, *model.AppError) {
|
||||||
sessions, err := a.Srv().Store.Session().GetSessions(userID)
|
sessions, err := a.srv.userService.GetSessions(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, model.NewAppError("GetSessions", "app.session.get_sessions.app_error", nil, err.Error(), http.StatusInternalServerError)
|
return nil, model.NewAppError("GetSessions", "app.session.get_sessions.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
@@ -142,40 +132,17 @@ func (a *App) GetSessions(userID string) ([]*model.Session, *model.AppError) {
|
|||||||
return sessions, nil
|
return sessions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) UpdateSessionsIsGuest(userID string, isGuest bool) {
|
|
||||||
sessions, err := a.Srv().Store.Session().GetSessions(userID)
|
|
||||||
if err != nil {
|
|
||||||
mlog.Error("Unable to get user sessions", mlog.String("user_id", userID), mlog.Err(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, session := range sessions {
|
|
||||||
session.AddProp(model.SESSION_PROP_IS_GUEST, fmt.Sprintf("%t", isGuest))
|
|
||||||
err := a.Srv().Store.Session().UpdateProps(session)
|
|
||||||
if err != nil {
|
|
||||||
mlog.Warn("Unable to update isGuest session", mlog.Err(err))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
a.srv.userService.AddSessionToCache(session)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) RevokeAllSessions(userID string) *model.AppError {
|
func (a *App) RevokeAllSessions(userID string) *model.AppError {
|
||||||
sessions, err := a.Srv().Store.Session().GetSessions(userID)
|
if err := a.srv.userService.RevokeAllSessions(userID); err != nil {
|
||||||
if err != nil {
|
switch {
|
||||||
|
case errors.Is(err, users.GetSessionError):
|
||||||
return model.NewAppError("RevokeAllSessions", "app.session.get_sessions.app_error", nil, err.Error(), http.StatusInternalServerError)
|
return model.NewAppError("RevokeAllSessions", "app.session.get_sessions.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
}
|
case errors.Is(err, users.DeleteSessionError):
|
||||||
for _, session := range sessions {
|
return model.NewAppError("RevokeAllSessions", "app.session.remove.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
if session.IsOAuth {
|
default:
|
||||||
a.RevokeAccessToken(session.Token)
|
|
||||||
} else {
|
|
||||||
if err := a.Srv().Store.Session().Remove(session.Id); err != nil {
|
|
||||||
return model.NewAppError("RevokeAllSessions", "app.session.remove.app_error", nil, err.Error(), http.StatusInternalServerError)
|
return model.NewAppError("RevokeAllSessions", "app.session.remove.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
a.ClearSessionCacheForUser(userID)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -187,16 +154,14 @@ func (a *App) AddSessionToCache(session *model.Session) {
|
|||||||
// RevokeSessionsFromAllUsers will go through all the sessions active
|
// RevokeSessionsFromAllUsers will go through all the sessions active
|
||||||
// in the server and revoke them
|
// in the server and revoke them
|
||||||
func (a *App) RevokeSessionsFromAllUsers() *model.AppError {
|
func (a *App) RevokeSessionsFromAllUsers() *model.AppError {
|
||||||
// revoke tokens before sessions so they can't be used to relogin
|
if err := a.srv.userService.RevokeSessionsFromAllUsers(); err != nil {
|
||||||
nErr := a.Srv().Store.OAuth().RemoveAllAccessData()
|
switch {
|
||||||
if nErr != nil {
|
case errors.Is(err, users.DeleteAllAccessDataError):
|
||||||
return model.NewAppError("RevokeSessionsFromAllUsers", "app.oauth.remove_access_data.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
return model.NewAppError("RevokeSessionsFromAllUsers", "app.oauth.remove_access_data.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
}
|
default:
|
||||||
err := a.Srv().Store.Session().RemoveAllSessions()
|
|
||||||
if err != nil {
|
|
||||||
return model.NewAppError("RevokeSessionsFromAllUsers", "app.session.remove_all_sessions_for_team.app_error", nil, err.Error(), http.StatusInternalServerError)
|
return model.NewAppError("RevokeSessionsFromAllUsers", "app.session.remove_all_sessions_for_team.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
a.ClearSessionCacheForAllUsers()
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -222,24 +187,15 @@ func (a *App) ClearSessionCacheForAllUsersSkipClusterSend() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) RevokeSessionsForDeviceId(userID string, deviceID string, currentSessionId string) *model.AppError {
|
func (a *App) RevokeSessionsForDeviceId(userID string, deviceID string, currentSessionId string) *model.AppError {
|
||||||
sessions, err := a.Srv().Store.Session().GetSessions(userID)
|
if err := a.srv.userService.RevokeSessionsForDeviceId(userID, deviceID, currentSessionId); err != nil {
|
||||||
if err != nil {
|
|
||||||
return model.NewAppError("RevokeSessionsForDeviceId", "app.session.get_sessions.app_error", nil, err.Error(), http.StatusInternalServerError)
|
return model.NewAppError("RevokeSessionsForDeviceId", "app.session.get_sessions.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
for _, session := range sessions {
|
|
||||||
if session.DeviceId == deviceID && session.Id != currentSessionId {
|
|
||||||
mlog.Debug("Revoking sessionId for userId. Re-login with the same device Id", mlog.String("session_id", session.Id), mlog.String("user_id", userID))
|
|
||||||
if err := a.RevokeSession(session); err != nil {
|
|
||||||
mlog.Warn("Could not revoke session for device", mlog.String("device_id", deviceID), mlog.Err(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) GetSessionById(sessionID string) (*model.Session, *model.AppError) {
|
func (a *App) GetSessionById(sessionID string) (*model.Session, *model.AppError) {
|
||||||
session, err := a.Srv().Store.Session().Get(context.Background(), sessionID)
|
session, err := a.srv.userService.GetSessionByID(sessionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, model.NewAppError("GetSessionById", "app.session.get.app_error", nil, err.Error(), http.StatusBadRequest)
|
return nil, model.NewAppError("GetSessionById", "app.session.get.app_error", nil, err.Error(), http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
@@ -248,7 +204,7 @@ func (a *App) GetSessionById(sessionID string) (*model.Session, *model.AppError)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) RevokeSessionById(sessionID string) *model.AppError {
|
func (a *App) RevokeSessionById(sessionID string) *model.AppError {
|
||||||
session, err := a.Srv().Store.Session().Get(context.Background(), sessionID)
|
session, err := a.GetSessionById(sessionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return model.NewAppError("RevokeSessionById", "app.session.get.app_error", nil, err.Error(), http.StatusBadRequest)
|
return model.NewAppError("RevokeSessionById", "app.session.get.app_error", nil, err.Error(), http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
@@ -257,18 +213,15 @@ func (a *App) RevokeSessionById(sessionID string) *model.AppError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) RevokeSession(session *model.Session) *model.AppError {
|
func (a *App) RevokeSession(session *model.Session) *model.AppError {
|
||||||
if session.IsOAuth {
|
if err := a.srv.userService.RevokeSession(session); err != nil {
|
||||||
if err := a.RevokeAccessToken(session.Token); err != nil {
|
switch {
|
||||||
return err
|
case errors.Is(err, users.DeleteSessionError):
|
||||||
}
|
return model.NewAppError("RevokeSession", "app.session.remove.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
} else {
|
default:
|
||||||
if err := a.Srv().Store.Session().Remove(session.Id); err != nil {
|
|
||||||
return model.NewAppError("RevokeSession", "app.session.remove.app_error", nil, err.Error(), http.StatusInternalServerError)
|
return model.NewAppError("RevokeSession", "app.session.remove.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a.ClearSessionCacheForUser(session.UserId)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,16 +328,11 @@ func (a *App) GetSessionLengthInMillis(session *model.Session) int64 {
|
|||||||
// relative to either the session creation date or the current time, depending
|
// relative to either the session creation date or the current time, depending
|
||||||
// on the `ExtendSessionOnActivity` config setting.
|
// on the `ExtendSessionOnActivity` config setting.
|
||||||
func (a *App) SetSessionExpireInDays(session *model.Session, days int) {
|
func (a *App) SetSessionExpireInDays(session *model.Session, days int) {
|
||||||
if session.CreateAt == 0 || *a.Config().ServiceSettings.ExtendSessionLengthWithActivity {
|
a.srv.userService.SetSessionExpireInDays(session, days)
|
||||||
session.ExpiresAt = model.GetMillis() + (1000 * 60 * 60 * 24 * int64(days))
|
|
||||||
} else {
|
|
||||||
session.ExpiresAt = session.CreateAt + (1000 * 60 * 60 * 24 * int64(days))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) CreateUserAccessToken(token *model.UserAccessToken) (*model.UserAccessToken, *model.AppError) {
|
func (a *App) CreateUserAccessToken(token *model.UserAccessToken) (*model.UserAccessToken, *model.AppError) {
|
||||||
|
user, nErr := a.srv.userService.GetUser(token.UserId)
|
||||||
user, nErr := a.Srv().Store.User().Get(context.Background(), token.UserId)
|
|
||||||
if nErr != nil {
|
if nErr != nil {
|
||||||
var nfErr *store.ErrNotFound
|
var nfErr *store.ErrNotFound
|
||||||
switch {
|
switch {
|
||||||
@@ -469,7 +417,7 @@ func (a *App) createSessionForUserAccessToken(tokenString string) (*model.Sessio
|
|||||||
} else {
|
} else {
|
||||||
session.AddProp(model.SESSION_PROP_IS_GUEST, "false")
|
session.AddProp(model.SESSION_PROP_IS_GUEST, "false")
|
||||||
}
|
}
|
||||||
a.SetSessionExpireInDays(session, model.SESSION_USER_ACCESS_TOKEN_EXPIRY)
|
a.srv.userService.SetSessionExpireInDays(session, model.SESSION_USER_ACCESS_TOKEN_EXPIRY)
|
||||||
|
|
||||||
session, nErr = a.Srv().Store.Session().Save(session)
|
session, nErr = a.Srv().Store.Session().Save(session)
|
||||||
if nErr != nil {
|
if nErr != nil {
|
||||||
@@ -490,7 +438,7 @@ func (a *App) createSessionForUserAccessToken(tokenString string) (*model.Sessio
|
|||||||
|
|
||||||
func (a *App) RevokeUserAccessToken(token *model.UserAccessToken) *model.AppError {
|
func (a *App) RevokeUserAccessToken(token *model.UserAccessToken) *model.AppError {
|
||||||
var session *model.Session
|
var session *model.Session
|
||||||
session, _ = a.Srv().Store.Session().Get(context.Background(), token.Token)
|
session, _ = a.srv.userService.GetSessionContext(context.Background(), token.Token)
|
||||||
|
|
||||||
if err := a.Srv().Store.UserAccessToken().Delete(token.Id); err != nil {
|
if err := a.Srv().Store.UserAccessToken().Delete(token.Id); err != nil {
|
||||||
return model.NewAppError("RevokeUserAccessToken", "app.user_access_token.delete.app_error", nil, err.Error(), http.StatusInternalServerError)
|
return model.NewAppError("RevokeUserAccessToken", "app.user_access_token.delete.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
@@ -505,7 +453,7 @@ func (a *App) RevokeUserAccessToken(token *model.UserAccessToken) *model.AppErro
|
|||||||
|
|
||||||
func (a *App) DisableUserAccessToken(token *model.UserAccessToken) *model.AppError {
|
func (a *App) DisableUserAccessToken(token *model.UserAccessToken) *model.AppError {
|
||||||
var session *model.Session
|
var session *model.Session
|
||||||
session, _ = a.Srv().Store.Session().Get(context.Background(), token.Token)
|
session, _ = a.srv.userService.GetSessionContext(context.Background(), token.Token)
|
||||||
|
|
||||||
if err := a.Srv().Store.UserAccessToken().UpdateTokenDisable(token.Id); err != nil {
|
if err := a.Srv().Store.UserAccessToken().UpdateTokenDisable(token.Id); err != nil {
|
||||||
return model.NewAppError("DisableUserAccessToken", "app.user_access_token.update_token_disable.app_error", nil, err.Error(), http.StatusInternalServerError)
|
return model.NewAppError("DisableUserAccessToken", "app.user_access_token.update_token_disable.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
@@ -520,7 +468,7 @@ func (a *App) DisableUserAccessToken(token *model.UserAccessToken) *model.AppErr
|
|||||||
|
|
||||||
func (a *App) EnableUserAccessToken(token *model.UserAccessToken) *model.AppError {
|
func (a *App) EnableUserAccessToken(token *model.UserAccessToken) *model.AppError {
|
||||||
var session *model.Session
|
var session *model.Session
|
||||||
session, _ = a.Srv().Store.Session().Get(context.Background(), token.Token)
|
session, _ = a.srv.userService.GetSessionContext(context.Background(), token.Token)
|
||||||
|
|
||||||
err := a.Srv().Store.UserAccessToken().UpdateTokenEnable(token.Id)
|
err := a.Srv().Store.UserAccessToken().UpdateTokenEnable(token.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -329,58 +329,6 @@ func TestApp_ExtendExpiryIfNeeded(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
dayInMillis = 86400000
|
|
||||||
grace = 5 * 1000
|
|
||||||
thirtyDays = dayInMillis * 30
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestApp_SetSessionExpireInDays(t *testing.T) {
|
|
||||||
th := Setup(t)
|
|
||||||
defer th.TearDown()
|
|
||||||
|
|
||||||
now := model.GetMillis()
|
|
||||||
createAt := now - (dayInMillis * 20)
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
extend bool
|
|
||||||
create bool
|
|
||||||
days int
|
|
||||||
want int64
|
|
||||||
}{
|
|
||||||
{name: "zero days, extend", extend: true, create: true, days: 0, want: now},
|
|
||||||
{name: "zero days, extend", extend: true, create: false, days: 0, want: now},
|
|
||||||
{name: "zero days, no extend", extend: false, create: true, days: 0, want: createAt},
|
|
||||||
{name: "zero days, no extend", extend: false, create: false, days: 0, want: now},
|
|
||||||
{name: "thirty days, extend", extend: true, create: true, days: 30, want: now + thirtyDays},
|
|
||||||
{name: "thirty days, extend", extend: true, create: false, days: 30, want: now + thirtyDays},
|
|
||||||
{name: "thirty days, no extend", extend: false, create: true, days: 30, want: createAt + thirtyDays},
|
|
||||||
{name: "thirty days, no extend", extend: false, create: false, days: 30, want: now + thirtyDays},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
||||||
*cfg.ServiceSettings.ExtendSessionLengthWithActivity = tt.extend
|
|
||||||
})
|
|
||||||
var create int64
|
|
||||||
if tt.create {
|
|
||||||
create = createAt
|
|
||||||
}
|
|
||||||
|
|
||||||
session := &model.Session{
|
|
||||||
CreateAt: create,
|
|
||||||
ExpiresAt: model.GetMillis() + dayInMillis,
|
|
||||||
}
|
|
||||||
th.App.SetSessionExpireInDays(session, tt.days)
|
|
||||||
|
|
||||||
// must be within 5 seconds of expected time.
|
|
||||||
require.GreaterOrEqual(t, session.ExpiresAt, tt.want-grace)
|
|
||||||
require.LessOrEqual(t, session.ExpiresAt, tt.want+grace)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetCloudSession(t *testing.T) {
|
func TestGetCloudSession(t *testing.T) {
|
||||||
th := Setup(t)
|
th := Setup(t)
|
||||||
defer func() {
|
defer func() {
|
||||||
|
|||||||
162
app/user.go
162
app/user.go
@@ -6,26 +6,15 @@ package app
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
b64 "encoding/base64"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash/fnv"
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"image/draw"
|
|
||||||
"image/png"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/golang/freetype"
|
|
||||||
"github.com/golang/freetype/truetype"
|
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v5/app/imaging"
|
"github.com/mattermost/mattermost-server/v5/app/imaging"
|
||||||
"github.com/mattermost/mattermost-server/v5/app/request"
|
"github.com/mattermost/mattermost-server/v5/app/request"
|
||||||
"github.com/mattermost/mattermost-server/v5/einterfaces"
|
"github.com/mattermost/mattermost-server/v5/einterfaces"
|
||||||
@@ -36,7 +25,6 @@ import (
|
|||||||
"github.com/mattermost/mattermost-server/v5/shared/mfa"
|
"github.com/mattermost/mattermost-server/v5/shared/mfa"
|
||||||
"github.com/mattermost/mattermost-server/v5/shared/mlog"
|
"github.com/mattermost/mattermost-server/v5/shared/mlog"
|
||||||
"github.com/mattermost/mattermost-server/v5/store"
|
"github.com/mattermost/mattermost-server/v5/store"
|
||||||
"github.com/mattermost/mattermost-server/v5/utils/fileutils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -320,12 +308,12 @@ func (a *App) CreateOAuthUser(c *request.Context, service string, userData io.Re
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
userByAuth, _ := a.Srv().Store.User().GetByAuth(user.AuthData, service)
|
userByAuth, _ := a.srv.userService.GetUserByAuth(user.AuthData, service)
|
||||||
if userByAuth != nil {
|
if userByAuth != nil {
|
||||||
return userByAuth, nil
|
return userByAuth, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
userByEmail, _ := a.Srv().Store.User().GetByEmail(user.Email)
|
userByEmail, _ := a.srv.userService.GetUserByEmail(user.Email)
|
||||||
if userByEmail != nil {
|
if userByEmail != nil {
|
||||||
if userByEmail.AuthService == "" {
|
if userByEmail.AuthService == "" {
|
||||||
return nil, model.NewAppError("CreateOAuthUser", "api.user.create_oauth_user.already_attached.app_error", map[string]interface{}{"Service": service, "Auth": model.USER_AUTH_SERVICE_EMAIL}, "email="+user.Email, http.StatusBadRequest)
|
return nil, model.NewAppError("CreateOAuthUser", "api.user.create_oauth_user.already_attached.app_error", map[string]interface{}{"Service": service, "Auth": model.USER_AUTH_SERVICE_EMAIL}, "email="+user.Email, http.StatusBadRequest)
|
||||||
@@ -633,7 +621,7 @@ func (a *App) GetUsersByGroupChannelIds(c *request.Context, channelIDs []string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) GetUsersByUsernames(usernames []string, asAdmin bool, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) {
|
func (a *App) GetUsersByUsernames(usernames []string, asAdmin bool, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) {
|
||||||
users, err := a.Srv().Store.User().GetProfilesByUsernames(usernames, viewRestrictions)
|
users, err := a.srv.userService.GetUsersByUsernames(usernames, &model.UserGetOptions{ViewRestrictions: viewRestrictions})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, model.NewAppError("GetUsersByUsernames", "app.user.get_profiles.app_error", nil, err.Error(), http.StatusInternalServerError)
|
return nil, model.NewAppError("GetUsersByUsernames", "app.user.get_profiles.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
@@ -658,28 +646,18 @@ func (a *App) GenerateMfaSecret(userID string) (*model.MfaSecret, *model.AppErro
|
|||||||
return nil, model.NewAppError("GenerateMfaSecret", "mfa.mfa_disabled.app_error", nil, "", http.StatusNotImplemented)
|
return nil, model.NewAppError("GenerateMfaSecret", "mfa.mfa_disabled.app_error", nil, "", http.StatusNotImplemented)
|
||||||
}
|
}
|
||||||
|
|
||||||
secret, img, err := mfa.New(a.Srv().Store.User()).GenerateSecret(*a.Config().ServiceSettings.SiteURL, user.Email, user.Id)
|
mfaSecret, err := a.srv.userService.GenerateMfaSecret(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, model.NewAppError("GenerateMfaSecret", "mfa.generate_qr_code.create_code.app_error", nil, err.Error(), http.StatusInternalServerError)
|
return nil, model.NewAppError("GenerateMfaSecret", "mfa.generate_qr_code.create_code.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the old secret is not cached on any cluster nodes.
|
|
||||||
a.InvalidateCacheForUser(user.Id)
|
|
||||||
|
|
||||||
mfaSecret := &model.MfaSecret{Secret: secret, QRCode: b64.StdEncoding.EncodeToString(img)}
|
|
||||||
return mfaSecret, nil
|
return mfaSecret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) ActivateMfa(userID, token string) *model.AppError {
|
func (a *App) ActivateMfa(userID, token string) *model.AppError {
|
||||||
user, err := a.Srv().Store.User().Get(context.Background(), userID)
|
user, appErr := a.GetUser(userID)
|
||||||
if err != nil {
|
if appErr != nil {
|
||||||
var nfErr *store.ErrNotFound
|
return appErr
|
||||||
switch {
|
|
||||||
case errors.As(err, &nfErr):
|
|
||||||
return model.NewAppError("ActivateMfa", MissingAccountError, nil, nfErr.Error(), http.StatusNotFound)
|
|
||||||
default:
|
|
||||||
return model.NewAppError("ActivateMfa", "app.user.get.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.AuthService != "" && user.AuthService != model.USER_AUTH_SERVICE_LDAP {
|
if user.AuthService != "" && user.AuthService != model.USER_AUTH_SERVICE_LDAP {
|
||||||
@@ -690,7 +668,7 @@ func (a *App) ActivateMfa(userID, token string) *model.AppError {
|
|||||||
return model.NewAppError("ActivateMfa", "mfa.mfa_disabled.app_error", nil, "", http.StatusNotImplemented)
|
return model.NewAppError("ActivateMfa", "mfa.mfa_disabled.app_error", nil, "", http.StatusNotImplemented)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := mfa.New(a.Srv().Store.User()).Activate(user.MfaSecret, user.Id, token); err != nil {
|
if err := a.srv.userService.ActivateMfa(user, token); err != nil {
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, mfa.InvalidToken):
|
case errors.Is(err, mfa.InvalidToken):
|
||||||
return model.NewAppError("ActivateMfa", "mfa.activate.bad_token.app_error", nil, "", http.StatusUnauthorized)
|
return model.NewAppError("ActivateMfa", "mfa.activate.bad_token.app_error", nil, "", http.StatusUnauthorized)
|
||||||
@@ -706,7 +684,12 @@ func (a *App) ActivateMfa(userID, token string) *model.AppError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) DeactivateMfa(userID string) *model.AppError {
|
func (a *App) DeactivateMfa(userID string) *model.AppError {
|
||||||
if err := mfa.New(a.Srv().Store.User()).Deactivate(userID); err != nil {
|
user, appErr := a.GetUser(userID)
|
||||||
|
if appErr != nil {
|
||||||
|
return appErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.srv.userService.DeactivateMfa(user); err != nil {
|
||||||
return model.NewAppError("DeactivateMfa", "mfa.deactivate.app_error", nil, err.Error(), http.StatusInternalServerError)
|
return model.NewAppError("DeactivateMfa", "mfa.deactivate.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -716,89 +699,6 @@ func (a *App) DeactivateMfa(userID string) *model.AppError {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateProfileImage(username string, userID string, initialFont string) ([]byte, *model.AppError) {
|
|
||||||
colors := []color.NRGBA{
|
|
||||||
{197, 8, 126, 255},
|
|
||||||
{227, 207, 18, 255},
|
|
||||||
{28, 181, 105, 255},
|
|
||||||
{35, 188, 224, 255},
|
|
||||||
{116, 49, 196, 255},
|
|
||||||
{197, 8, 126, 255},
|
|
||||||
{197, 19, 19, 255},
|
|
||||||
{250, 134, 6, 255},
|
|
||||||
{227, 207, 18, 255},
|
|
||||||
{123, 201, 71, 255},
|
|
||||||
{28, 181, 105, 255},
|
|
||||||
{35, 188, 224, 255},
|
|
||||||
{116, 49, 196, 255},
|
|
||||||
{197, 8, 126, 255},
|
|
||||||
{197, 19, 19, 255},
|
|
||||||
{250, 134, 6, 255},
|
|
||||||
{227, 207, 18, 255},
|
|
||||||
{123, 201, 71, 255},
|
|
||||||
{28, 181, 105, 255},
|
|
||||||
{35, 188, 224, 255},
|
|
||||||
{116, 49, 196, 255},
|
|
||||||
{197, 8, 126, 255},
|
|
||||||
{197, 19, 19, 255},
|
|
||||||
{250, 134, 6, 255},
|
|
||||||
{227, 207, 18, 255},
|
|
||||||
{123, 201, 71, 255},
|
|
||||||
}
|
|
||||||
|
|
||||||
h := fnv.New32a()
|
|
||||||
h.Write([]byte(userID))
|
|
||||||
seed := h.Sum32()
|
|
||||||
|
|
||||||
initial := string(strings.ToUpper(username)[0])
|
|
||||||
|
|
||||||
font, err := getFont(initialFont)
|
|
||||||
if err != nil {
|
|
||||||
return nil, model.NewAppError("CreateProfileImage", "api.user.create_profile_image.default_font.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
color := colors[int64(seed)%int64(len(colors))]
|
|
||||||
dstImg := image.NewRGBA(image.Rect(0, 0, ImageProfilePixelDimension, ImageProfilePixelDimension))
|
|
||||||
srcImg := image.White
|
|
||||||
draw.Draw(dstImg, dstImg.Bounds(), &image.Uniform{color}, image.Point{}, draw.Src)
|
|
||||||
size := float64(ImageProfilePixelDimension / 2)
|
|
||||||
|
|
||||||
c := freetype.NewContext()
|
|
||||||
c.SetFont(font)
|
|
||||||
c.SetFontSize(size)
|
|
||||||
c.SetClip(dstImg.Bounds())
|
|
||||||
c.SetDst(dstImg)
|
|
||||||
c.SetSrc(srcImg)
|
|
||||||
|
|
||||||
pt := freetype.Pt(ImageProfilePixelDimension/5, ImageProfilePixelDimension*2/3)
|
|
||||||
_, err = c.DrawString(initial, pt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, model.NewAppError("CreateProfileImage", "api.user.create_profile_image.initial.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
|
|
||||||
if imgErr := png.Encode(buf, dstImg); imgErr != nil {
|
|
||||||
return nil, model.NewAppError("CreateProfileImage", "api.user.create_profile_image.encode.app_error", nil, imgErr.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFont(initialFont string) (*truetype.Font, error) {
|
|
||||||
// Some people have the old default font still set, so just treat that as if they're using the new default
|
|
||||||
if initialFont == "luximbi.ttf" {
|
|
||||||
initialFont = "nunito-bold.ttf"
|
|
||||||
}
|
|
||||||
|
|
||||||
fontDir, _ := fileutils.FindDir("fonts")
|
|
||||||
fontBytes, err := ioutil.ReadFile(filepath.Join(fontDir, initialFont))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return freetype.ParseFont(fontBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) GetProfileImage(user *model.User) ([]byte, bool, *model.AppError) {
|
func (a *App) GetProfileImage(user *model.User) ([]byte, bool, *model.AppError) {
|
||||||
return a.srv.GetProfileImage(user)
|
return a.srv.GetProfileImage(user)
|
||||||
}
|
}
|
||||||
@@ -929,10 +829,6 @@ func (a *App) UpdatePasswordAsUser(userID, currentPassword, newPassword string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) userDeactivated(c *request.Context, userID string) *model.AppError {
|
func (a *App) userDeactivated(c *request.Context, userID string) *model.AppError {
|
||||||
if err := a.RevokeAllSessions(userID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
a.SetStatusOffline(userID, false)
|
a.SetStatusOffline(userID, false)
|
||||||
|
|
||||||
user, err := a.GetUser(userID)
|
user, err := a.GetUser(userID)
|
||||||
@@ -982,7 +878,7 @@ func (a *App) UpdateActive(c *request.Context, user *model.User, active bool) (*
|
|||||||
user.DeleteAt = user.UpdateAt
|
user.DeleteAt = user.UpdateAt
|
||||||
}
|
}
|
||||||
|
|
||||||
userUpdate, err := a.Srv().Store.User().Update(user, true)
|
userUpdate, err := a.srv.userService.UpdateUser(user, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var appErr *model.AppError
|
var appErr *model.AppError
|
||||||
var invErr *store.ErrInvalidInput
|
var invErr *store.ErrInvalidInput
|
||||||
@@ -998,6 +894,9 @@ func (a *App) UpdateActive(c *request.Context, user *model.User, active bool) (*
|
|||||||
ruser := userUpdate.New
|
ruser := userUpdate.New
|
||||||
|
|
||||||
if !active {
|
if !active {
|
||||||
|
if err := a.RevokeAllSessions(ruser.Id); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if err := a.userDeactivated(c, ruser.Id); err != nil {
|
if err := a.userDeactivated(c, ruser.Id); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -1012,7 +911,7 @@ func (a *App) UpdateActive(c *request.Context, user *model.User, active bool) (*
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) DeactivateGuests(c *request.Context) *model.AppError {
|
func (a *App) DeactivateGuests(c *request.Context) *model.AppError {
|
||||||
userIDs, err := a.Srv().Store.User().DeactivateGuests()
|
userIDs, err := a.srv.userService.DeactivateAllGuests()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return model.NewAppError("DeactivateGuests", "app.user.update_active_for_multiple_users.updating.app_error", nil, err.Error(), http.StatusInternalServerError)
|
return model.NewAppError("DeactivateGuests", "app.user.update_active_for_multiple_users.updating.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
@@ -1032,19 +931,12 @@ func (a *App) DeactivateGuests(c *request.Context) *model.AppError {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: migrate this after the user service implementation is completed
|
|
||||||
func (a *App) GetSanitizeOptions(asAdmin bool) map[string]bool {
|
func (a *App) GetSanitizeOptions(asAdmin bool) map[string]bool {
|
||||||
options := a.Config().GetSanitizeOptions()
|
return a.srv.userService.GetSanitizeOptions(asAdmin)
|
||||||
if asAdmin {
|
|
||||||
options["email"] = true
|
|
||||||
options["fullname"] = true
|
|
||||||
options["authservice"] = true
|
|
||||||
}
|
|
||||||
return options
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) SanitizeProfile(user *model.User, asAdmin bool) {
|
func (a *App) SanitizeProfile(user *model.User, asAdmin bool) {
|
||||||
options := a.GetSanitizeOptions(asAdmin)
|
options := a.srv.userService.GetSanitizeOptions(asAdmin)
|
||||||
|
|
||||||
user.SanitizeProfile(options)
|
user.SanitizeProfile(options)
|
||||||
}
|
}
|
||||||
@@ -1136,7 +1028,7 @@ func (a *App) sendUpdatedUserEvent(user model.User) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) UpdateUser(user *model.User, sendNotifications bool) (*model.User, *model.AppError) {
|
func (a *App) UpdateUser(user *model.User, sendNotifications bool) (*model.User, *model.AppError) {
|
||||||
prev, err := a.Srv().Store.User().Get(context.Background(), user.Id)
|
prev, err := a.srv.userService.GetUser(user.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var nfErr *store.ErrNotFound
|
var nfErr *store.ErrNotFound
|
||||||
switch {
|
switch {
|
||||||
@@ -1179,7 +1071,7 @@ func (a *App) UpdateUser(user *model.User, sendNotifications bool) (*model.User,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
userUpdate, err := a.Srv().Store.User().Update(user, false)
|
userUpdate, err := a.srv.userService.UpdateUser(user, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var appErr *model.AppError
|
var appErr *model.AppError
|
||||||
var invErr *store.ErrInvalidInput
|
var invErr *store.ErrInvalidInput
|
||||||
@@ -2120,7 +2012,9 @@ func (a *App) PromoteGuestToUser(c *request.Context, user *model.User, requestor
|
|||||||
mlog.Warn("Failed to get user on promote guest to user", mlog.Err(err))
|
mlog.Warn("Failed to get user on promote guest to user", mlog.Err(err))
|
||||||
} else {
|
} else {
|
||||||
a.sendUpdatedUserEvent(*promotedUser)
|
a.sendUpdatedUserEvent(*promotedUser)
|
||||||
a.UpdateSessionsIsGuest(promotedUser.Id, promotedUser.IsGuest())
|
if uErr := a.srv.userService.UpdateSessionsIsGuest(promotedUser.Id, promotedUser.IsGuest()); uErr != nil {
|
||||||
|
mlog.Warn("Unable to update user sessions", mlog.String("user_id", promotedUser.Id), mlog.Err(uErr))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
teamMembers, err := a.GetTeamMembersForUser(user.Id)
|
teamMembers, err := a.GetTeamMembersForUser(user.Id)
|
||||||
@@ -2159,7 +2053,9 @@ func (a *App) DemoteUserToGuest(user *model.User) *model.AppError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
a.sendUpdatedUserEvent(*demotedUser)
|
a.sendUpdatedUserEvent(*demotedUser)
|
||||||
a.UpdateSessionsIsGuest(demotedUser.Id, demotedUser.IsGuest())
|
if uErr := a.srv.userService.UpdateSessionsIsGuest(demotedUser.Id, demotedUser.IsGuest()); uErr != nil {
|
||||||
|
mlog.Warn("Unable to update user sessions", mlog.String("user_id", demotedUser.Id), mlog.Err(uErr))
|
||||||
|
}
|
||||||
|
|
||||||
teamMembers, err := a.GetTeamMembersForUser(user.Id)
|
teamMembers, err := a.GetTeamMembersForUser(user.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -82,19 +80,6 @@ func TestCreateOAuthUser(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateProfileImage(t *testing.T) {
|
|
||||||
b, err := CreateProfileImage("Corey Hulen", "eo1zkdr96pdj98pjmq8zy35wba", "nunito-bold.ttf")
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
rdr := bytes.NewReader(b)
|
|
||||||
img, _, err2 := image.Decode(rdr)
|
|
||||||
require.NoError(t, err2)
|
|
||||||
|
|
||||||
colorful := color.RGBA{116, 49, 196, 255}
|
|
||||||
|
|
||||||
require.Equal(t, colorful, img.At(1, 1), "Failed to create correct color")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetDefaultProfileImage(t *testing.T) {
|
func TestSetDefaultProfileImage(t *testing.T) {
|
||||||
th := Setup(t).InitBasic()
|
th := Setup(t).InitBasic()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|||||||
@@ -153,15 +153,18 @@ func TestHubSessionRevokeRace(t *testing.T) {
|
|||||||
mockStatusStore.On("UpdateLastActivityAt", "user1", mock.Anything).Return(nil)
|
mockStatusStore.On("UpdateLastActivityAt", "user1", mock.Anything).Return(nil)
|
||||||
mockStatusStore.On("SaveOrUpdate", mock.AnythingOfType("*model.Status")).Return(nil)
|
mockStatusStore.On("SaveOrUpdate", mock.AnythingOfType("*model.Status")).Return(nil)
|
||||||
|
|
||||||
|
mockOAuthStore := mocks.OAuthStore{}
|
||||||
mockStore.On("Session").Return(&mockSessionStore)
|
mockStore.On("Session").Return(&mockSessionStore)
|
||||||
|
mockStore.On("OAuth").Return(&mockOAuthStore)
|
||||||
mockStore.On("Status").Return(&mockStatusStore)
|
mockStore.On("Status").Return(&mockStatusStore)
|
||||||
mockStore.On("User").Return(&mockUserStore)
|
mockStore.On("User").Return(&mockUserStore)
|
||||||
mockStore.On("Post").Return(&mockPostStore)
|
mockStore.On("Post").Return(&mockPostStore)
|
||||||
mockStore.On("System").Return(&mockSystemStore)
|
mockStore.On("System").Return(&mockSystemStore)
|
||||||
|
|
||||||
userService, err := users.New(users.ServiceInitializer{
|
userService, err := users.New(users.ServiceConfig{
|
||||||
UserStore: &mockUserStore,
|
UserStore: &mockUserStore,
|
||||||
SessionStore: &mockSessionStore,
|
SessionStore: &mockSessionStore,
|
||||||
|
OAuthStore: &mockOAuthStore,
|
||||||
ConfigFn: th.App.srv.Config,
|
ConfigFn: th.App.srv.Config,
|
||||||
Metrics: th.App.Metrics(),
|
Metrics: th.App.Metrics(),
|
||||||
Cluster: th.App.Cluster(),
|
Cluster: th.App.Cluster(),
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
package model
|
package users
|
||||||
|
|
||||||
var BotDefaultImage = []byte{
|
var botDefaultImage = []byte{
|
||||||
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
|
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
|
||||||
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x7e,
|
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x7e,
|
||||||
0x08, 0x06, 0x00, 0x00, 0x00, 0xec, 0xa6, 0x19, 0xa2, 0x00, 0x00, 0x00,
|
0x08, 0x06, 0x00, 0x00, 0x00, 0xec, 0xa6, 0x19, 0xa2, 0x00, 0x00, 0x00,
|
||||||
@@ -9,6 +9,17 @@ var (
|
|||||||
AcceptedDomainError = errors.New("the email provided does not belong to an accepted domain")
|
AcceptedDomainError = errors.New("the email provided does not belong to an accepted domain")
|
||||||
VerifyUserError = errors.New("could not update verify email field")
|
VerifyUserError = errors.New("could not update verify email field")
|
||||||
UserCountError = errors.New("could not get the total number of the users.")
|
UserCountError = errors.New("could not get the total number of the users.")
|
||||||
|
UserCreationDisabledError = errors.New("user creation is not allowed")
|
||||||
|
|
||||||
|
GetTokenError = errors.New("could not get token")
|
||||||
|
GetSessionError = errors.New("could not get session")
|
||||||
|
DeleteTokenError = errors.New("could not delete token")
|
||||||
|
DeleteSessionError = errors.New("could not delete session")
|
||||||
|
DeleteAllAccessDataError = errors.New("could not delete all access data")
|
||||||
|
|
||||||
|
DefaultFontError = errors.New("could not get default font")
|
||||||
|
UserInitialsError = errors.New("could not get user initials")
|
||||||
|
ImageEncodingError = errors.New("could not encode image")
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrInvalidPassword indicates an error against the password settings
|
// ErrInvalidPassword indicates an error against the password settings
|
||||||
|
|||||||
@@ -87,8 +87,14 @@ func setupTestHelper(s store.Store, includeCacheLayer bool, tb testing.TB) *Test
|
|||||||
service: &UserService{
|
service: &UserService{
|
||||||
store: s.User(),
|
store: s.User(),
|
||||||
sessionStore: s.Session(),
|
sessionStore: s.Session(),
|
||||||
|
oAuthStore: s.OAuth(),
|
||||||
sessionCache: cache,
|
sessionCache: cache,
|
||||||
config: configStore.Get,
|
config: configStore.Get,
|
||||||
|
sessionPool: sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return &model.Session{}
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Context: &request.Context{},
|
Context: &request.Context{},
|
||||||
configStore: configStore,
|
configStore: configStore,
|
||||||
@@ -155,3 +161,15 @@ func (th *TestHelper) TearDown() {
|
|||||||
os.RemoveAll(th.workspace)
|
os.RemoveAll(th.workspace)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (th *TestHelper) UpdateConfig(f func(*model.Config)) {
|
||||||
|
if th.configStore.IsReadOnly() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
old := th.configStore.Get()
|
||||||
|
updated := old.Clone()
|
||||||
|
f(updated)
|
||||||
|
if _, _, err := th.configStore.Set(updated); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
117
services/users/profile_picture.go
Обычный файл
117
services/users/profile_picture.go
Обычный файл
@@ -0,0 +1,117 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package users
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"hash/fnv"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"image/draw"
|
||||||
|
"image/png"
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/golang/freetype"
|
||||||
|
"github.com/golang/freetype/truetype"
|
||||||
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
|
"github.com/mattermost/mattermost-server/v5/utils/fileutils"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
imageProfilePixelDimension = 128
|
||||||
|
)
|
||||||
|
|
||||||
|
func (us *UserService) GetDefaultProfileImage(user *model.User) ([]byte, error) {
|
||||||
|
if user.IsBot {
|
||||||
|
return botDefaultImage, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return createProfileImage(user.Username, user.Id, *us.config().FileSettings.InitialFont)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createProfileImage(username string, userID string, initialFont string) ([]byte, error) {
|
||||||
|
colors := []color.NRGBA{
|
||||||
|
{197, 8, 126, 255},
|
||||||
|
{227, 207, 18, 255},
|
||||||
|
{28, 181, 105, 255},
|
||||||
|
{35, 188, 224, 255},
|
||||||
|
{116, 49, 196, 255},
|
||||||
|
{197, 8, 126, 255},
|
||||||
|
{197, 19, 19, 255},
|
||||||
|
{250, 134, 6, 255},
|
||||||
|
{227, 207, 18, 255},
|
||||||
|
{123, 201, 71, 255},
|
||||||
|
{28, 181, 105, 255},
|
||||||
|
{35, 188, 224, 255},
|
||||||
|
{116, 49, 196, 255},
|
||||||
|
{197, 8, 126, 255},
|
||||||
|
{197, 19, 19, 255},
|
||||||
|
{250, 134, 6, 255},
|
||||||
|
{227, 207, 18, 255},
|
||||||
|
{123, 201, 71, 255},
|
||||||
|
{28, 181, 105, 255},
|
||||||
|
{35, 188, 224, 255},
|
||||||
|
{116, 49, 196, 255},
|
||||||
|
{197, 8, 126, 255},
|
||||||
|
{197, 19, 19, 255},
|
||||||
|
{250, 134, 6, 255},
|
||||||
|
{227, 207, 18, 255},
|
||||||
|
{123, 201, 71, 255},
|
||||||
|
}
|
||||||
|
|
||||||
|
h := fnv.New32a()
|
||||||
|
h.Write([]byte(userID))
|
||||||
|
seed := h.Sum32()
|
||||||
|
|
||||||
|
initial := string(strings.ToUpper(username)[0])
|
||||||
|
|
||||||
|
font, err := getFont(initialFont)
|
||||||
|
if err != nil {
|
||||||
|
return nil, DefaultFontError
|
||||||
|
}
|
||||||
|
|
||||||
|
color := colors[int64(seed)%int64(len(colors))]
|
||||||
|
dstImg := image.NewRGBA(image.Rect(0, 0, imageProfilePixelDimension, imageProfilePixelDimension))
|
||||||
|
srcImg := image.White
|
||||||
|
draw.Draw(dstImg, dstImg.Bounds(), &image.Uniform{color}, image.Point{}, draw.Src)
|
||||||
|
size := float64(imageProfilePixelDimension / 2)
|
||||||
|
|
||||||
|
c := freetype.NewContext()
|
||||||
|
c.SetFont(font)
|
||||||
|
c.SetFontSize(size)
|
||||||
|
c.SetClip(dstImg.Bounds())
|
||||||
|
c.SetDst(dstImg)
|
||||||
|
c.SetSrc(srcImg)
|
||||||
|
|
||||||
|
pt := freetype.Pt(imageProfilePixelDimension/5, imageProfilePixelDimension*2/3)
|
||||||
|
_, err = c.DrawString(initial, pt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, UserInitialsError
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
|
if imgErr := png.Encode(buf, dstImg); imgErr != nil {
|
||||||
|
return nil, ImageEncodingError
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFont(initialFont string) (*truetype.Font, error) {
|
||||||
|
// Some people have the old default font still set, so just treat that as if they're using the new default
|
||||||
|
if initialFont == "luximbi.ttf" {
|
||||||
|
initialFont = "nunito-bold.ttf"
|
||||||
|
}
|
||||||
|
|
||||||
|
fontDir, _ := fileutils.FindDir("fonts")
|
||||||
|
fontBytes, err := ioutil.ReadFile(filepath.Join(fontDir, initialFont))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return freetype.ParseFont(fontBytes)
|
||||||
|
}
|
||||||
26
services/users/profile_picture_test.go
Обычный файл
26
services/users/profile_picture_test.go
Обычный файл
@@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package users
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateProfileImage(t *testing.T) {
|
||||||
|
b, err := createProfileImage("Corey Hulen", "eo1zkdr96pdj98pjmq8zy35wba", "nunito-bold.ttf")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
rdr := bytes.NewReader(b)
|
||||||
|
img, _, err2 := image.Decode(rdr)
|
||||||
|
require.NoError(t, err2)
|
||||||
|
|
||||||
|
colorful := color.RGBA{116, 49, 196, 255}
|
||||||
|
|
||||||
|
require.Equal(t, colorful, img.At(1, 1), "Failed to create correct color")
|
||||||
|
}
|
||||||
82
services/users/service.go
Обычный файл
82
services/users/service.go
Обычный файл
@@ -0,0 +1,82 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package users
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v5/einterfaces"
|
||||||
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
|
"github.com/mattermost/mattermost-server/v5/services/cache"
|
||||||
|
"github.com/mattermost/mattermost-server/v5/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserService struct {
|
||||||
|
store store.UserStore
|
||||||
|
sessionStore store.SessionStore
|
||||||
|
oAuthStore store.OAuthStore
|
||||||
|
sessionCache cache.Cache
|
||||||
|
sessionPool sync.Pool
|
||||||
|
metrics einterfaces.MetricsInterface
|
||||||
|
cluster einterfaces.ClusterInterface
|
||||||
|
config func() *model.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceConfig is used to initialize the UserService.
|
||||||
|
type ServiceConfig struct {
|
||||||
|
// Mandatory fields
|
||||||
|
UserStore store.UserStore
|
||||||
|
SessionStore store.SessionStore
|
||||||
|
OAuthStore store.OAuthStore
|
||||||
|
ConfigFn func() *model.Config
|
||||||
|
// Optional fields
|
||||||
|
Metrics einterfaces.MetricsInterface
|
||||||
|
Cluster einterfaces.ClusterInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(c ServiceConfig) (*UserService, error) {
|
||||||
|
if err := c.validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheProvider := cache.NewProvider()
|
||||||
|
if err := cacheProvider.Connect(); err != nil {
|
||||||
|
return nil, fmt.Errorf("could not connect to cache provider: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionCache, err := cacheProvider.NewCache(&cache.CacheOptions{
|
||||||
|
Size: model.SESSION_CACHE_SIZE,
|
||||||
|
Striped: true,
|
||||||
|
StripedBuckets: maxInt(runtime.NumCPU()-1, 1),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not create session cache: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &UserService{
|
||||||
|
store: c.UserStore,
|
||||||
|
sessionStore: c.SessionStore,
|
||||||
|
oAuthStore: c.OAuthStore,
|
||||||
|
config: c.ConfigFn,
|
||||||
|
metrics: c.Metrics,
|
||||||
|
cluster: c.Cluster,
|
||||||
|
sessionCache: sessionCache,
|
||||||
|
sessionPool: sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return &model.Session{}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServiceConfig) validate() error {
|
||||||
|
if in := c; in.ConfigFn == nil || in.UserStore == nil || in.SessionStore == nil || in.OAuthStore == nil {
|
||||||
|
return errors.New("required parameters are not provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
30
services/users/service_test.go
Обычный файл
30
services/users/service_test.go
Обычный файл
@@ -0,0 +1,30 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package users
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
_, err := New(ServiceConfig{})
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
dbStore := mainHelper.GetStore()
|
||||||
|
|
||||||
|
cfn := func() *model.Config {
|
||||||
|
return &model.Config{}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = New(ServiceConfig{
|
||||||
|
UserStore: dbStore.User(),
|
||||||
|
SessionStore: dbStore.Session(),
|
||||||
|
OAuthStore: dbStore.OAuth(),
|
||||||
|
ConfigFn: cfn,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
@@ -4,9 +4,14 @@
|
|||||||
package users
|
package users
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v5/model"
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
|
"github.com/mattermost/mattermost-server/v5/shared/mlog"
|
||||||
|
"github.com/mattermost/mattermost-server/v5/store/sqlstore"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (us *UserService) ReturnSessionToPool(session *model.Session) {
|
func (us *UserService) ReturnSessionToPool(session *model.Session) {
|
||||||
@@ -40,9 +45,22 @@ func (us *UserService) GetSession(token string) (*model.Session, error) {
|
|||||||
us.metrics.IncrementMemCacheMissCounterSession()
|
us.metrics.IncrementMemCacheMissCounterSession()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if session.Id != "" {
|
||||||
return session, nil
|
return session, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return us.GetSessionContext(sqlstore.WithMaster(context.Background()), token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (us *UserService) GetSessionContext(ctx context.Context, token string) (*model.Session, error) {
|
||||||
|
return us.sessionStore.Get(ctx, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (us *UserService) GetSessions(userID string) ([]*model.Session, error) {
|
||||||
|
return us.sessionStore.GetSessions(userID)
|
||||||
|
}
|
||||||
|
|
||||||
func (us *UserService) AddSessionToCache(session *model.Session) {
|
func (us *UserService) AddSessionToCache(session *model.Session) {
|
||||||
us.sessionCache.SetWithExpiry(session.Token, session, time.Duration(int64(*us.config().ServiceSettings.SessionCacheInMinutes))*time.Minute)
|
us.sessionCache.SetWithExpiry(session.Token, session, time.Duration(int64(*us.config().ServiceSettings.SessionCacheInMinutes))*time.Minute)
|
||||||
}
|
}
|
||||||
@@ -98,3 +116,134 @@ func (us *UserService) ClearAllUsersSessionCache() {
|
|||||||
us.cluster.SendClusterMessage(msg)
|
us.cluster.SendClusterMessage(msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (us *UserService) GetSessionByID(sessionID string) (*model.Session, error) {
|
||||||
|
return us.sessionStore.Get(context.Background(), sessionID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (us *UserService) RevokeSessionsFromAllUsers() error {
|
||||||
|
// revoke tokens before sessions so they can't be used to relogin
|
||||||
|
nErr := us.oAuthStore.RemoveAllAccessData()
|
||||||
|
if nErr != nil {
|
||||||
|
return errors.Wrap(DeleteAllAccessDataError, nErr.Error())
|
||||||
|
}
|
||||||
|
err := us.sessionStore.RemoveAllSessions()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
us.ClearAllUsersSessionCache()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (us *UserService) RevokeSessionsForDeviceId(userID string, deviceID string, currentSessionId string) error {
|
||||||
|
sessions, err := us.sessionStore.GetSessions(userID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, session := range sessions {
|
||||||
|
if session.DeviceId == deviceID && session.Id != currentSessionId {
|
||||||
|
mlog.Debug("Revoking sessionId for userId. Re-login with the same device Id", mlog.String("session_id", session.Id), mlog.String("user_id", userID))
|
||||||
|
if err := us.RevokeSession(session); err != nil {
|
||||||
|
mlog.Warn("Could not revoke session for device", mlog.String("device_id", deviceID), mlog.Err(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (us *UserService) RevokeSession(session *model.Session) error {
|
||||||
|
if session.IsOAuth {
|
||||||
|
if err := us.RevokeAccessToken(session.Token); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := us.sessionStore.Remove(session.Id); err != nil {
|
||||||
|
return errors.Wrap(DeleteSessionError, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
us.ClearUserSessionCache(session.UserId)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (us *UserService) RevokeAccessToken(token string) error {
|
||||||
|
session, _ := us.GetSession(token)
|
||||||
|
|
||||||
|
defer us.ReturnSessionToPool(session)
|
||||||
|
|
||||||
|
schan := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
schan <- us.sessionStore.Remove(token)
|
||||||
|
close(schan)
|
||||||
|
}()
|
||||||
|
|
||||||
|
if _, err := us.oAuthStore.GetAccessData(token); err != nil {
|
||||||
|
return errors.Wrap(GetTokenError, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := us.oAuthStore.RemoveAccessData(token); err != nil {
|
||||||
|
return errors.Wrap(DeleteTokenError, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := <-schan; err != nil {
|
||||||
|
return errors.Wrap(DeleteSessionError, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if session != nil {
|
||||||
|
us.ClearUserSessionCache(session.UserId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSessionExpireInDays sets the session's expiry the specified number of days
|
||||||
|
// relative to either the session creation date or the current time, depending
|
||||||
|
// on the `ExtendSessionOnActivity` config setting.
|
||||||
|
func (us *UserService) SetSessionExpireInDays(session *model.Session, days int) {
|
||||||
|
if session.CreateAt == 0 || *us.config().ServiceSettings.ExtendSessionLengthWithActivity {
|
||||||
|
session.ExpiresAt = model.GetMillis() + (1000 * 60 * 60 * 24 * int64(days))
|
||||||
|
} else {
|
||||||
|
session.ExpiresAt = session.CreateAt + (1000 * 60 * 60 * 24 * int64(days))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (us *UserService) UpdateSessionsIsGuest(userID string, isGuest bool) error {
|
||||||
|
sessions, err := us.GetSessions(userID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, session := range sessions {
|
||||||
|
session.AddProp(model.SESSION_PROP_IS_GUEST, fmt.Sprintf("%t", isGuest))
|
||||||
|
err := us.sessionStore.UpdateProps(session)
|
||||||
|
if err != nil {
|
||||||
|
mlog.Warn("Unable to update isGuest session", mlog.Err(err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
us.AddSessionToCache(session)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (us *UserService) RevokeAllSessions(userID string) error {
|
||||||
|
sessions, err := us.sessionStore.GetSessions(userID)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(GetSessionError, err.Error())
|
||||||
|
}
|
||||||
|
for _, session := range sessions {
|
||||||
|
if session.IsOAuth {
|
||||||
|
us.RevokeAccessToken(session.Token)
|
||||||
|
} else {
|
||||||
|
if err := us.sessionStore.Remove(session.Id); err != nil {
|
||||||
|
return errors.Wrap(DeleteSessionError, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
us.ClearUserSessionCache(userID)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,12 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
dayInMillis = 86400000
|
||||||
|
grace = 5 * 1000
|
||||||
|
thirtyDays = dayInMillis * 30
|
||||||
|
)
|
||||||
|
|
||||||
func TestCache(t *testing.T) {
|
func TestCache(t *testing.T) {
|
||||||
th := Setup(t)
|
th := Setup(t)
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
@@ -47,3 +53,81 @@ func TestCache(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Empty(t, rkeys)
|
require.Empty(t, rkeys)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSetSessionExpireInDays(t *testing.T) {
|
||||||
|
th := Setup(t)
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
now := model.GetMillis()
|
||||||
|
createAt := now - (dayInMillis * 20)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
extend bool
|
||||||
|
create bool
|
||||||
|
days int
|
||||||
|
want int64
|
||||||
|
}{
|
||||||
|
{name: "zero days, extend", extend: true, create: true, days: 0, want: now},
|
||||||
|
{name: "zero days, extend", extend: true, create: false, days: 0, want: now},
|
||||||
|
{name: "zero days, no extend", extend: false, create: true, days: 0, want: createAt},
|
||||||
|
{name: "zero days, no extend", extend: false, create: false, days: 0, want: now},
|
||||||
|
{name: "thirty days, extend", extend: true, create: true, days: 30, want: now + thirtyDays},
|
||||||
|
{name: "thirty days, extend", extend: true, create: false, days: 30, want: now + thirtyDays},
|
||||||
|
{name: "thirty days, no extend", extend: false, create: true, days: 30, want: createAt + thirtyDays},
|
||||||
|
{name: "thirty days, no extend", extend: false, create: false, days: 30, want: now + thirtyDays},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
th.UpdateConfig(func(cfg *model.Config) {
|
||||||
|
*cfg.ServiceSettings.ExtendSessionLengthWithActivity = tt.extend
|
||||||
|
})
|
||||||
|
var create int64
|
||||||
|
if tt.create {
|
||||||
|
create = createAt
|
||||||
|
}
|
||||||
|
|
||||||
|
session := &model.Session{
|
||||||
|
CreateAt: create,
|
||||||
|
ExpiresAt: model.GetMillis() + dayInMillis,
|
||||||
|
}
|
||||||
|
th.service.SetSessionExpireInDays(session, tt.days)
|
||||||
|
|
||||||
|
// must be within 5 seconds of expected time.
|
||||||
|
require.GreaterOrEqual(t, session.ExpiresAt, tt.want-grace)
|
||||||
|
require.LessOrEqual(t, session.ExpiresAt, tt.want+grace)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOAuthRevokeAccessToken(t *testing.T) {
|
||||||
|
th := Setup(t)
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
err := th.service.RevokeAccessToken(model.NewRandomString(16))
|
||||||
|
require.Error(t, err, "Should have failed due to an incorrect token")
|
||||||
|
|
||||||
|
session := &model.Session{}
|
||||||
|
session.CreateAt = model.GetMillis()
|
||||||
|
session.UserId = model.NewId()
|
||||||
|
session.Token = model.NewId()
|
||||||
|
session.Roles = model.SYSTEM_USER_ROLE_ID
|
||||||
|
th.service.SetSessionExpireInDays(session, 1)
|
||||||
|
|
||||||
|
session, _ = th.service.CreateSession(session)
|
||||||
|
err = th.service.RevokeAccessToken(session.Token)
|
||||||
|
require.Error(t, err, "Should have failed does not have an access token")
|
||||||
|
|
||||||
|
accessData := &model.AccessData{}
|
||||||
|
accessData.Token = session.Token
|
||||||
|
accessData.UserId = session.UserId
|
||||||
|
accessData.RedirectUri = "http://example.com"
|
||||||
|
accessData.ClientId = model.NewId()
|
||||||
|
accessData.ExpiresAt = session.ExpiresAt
|
||||||
|
|
||||||
|
_, nErr := th.service.oAuthStore.SaveAccessData(accessData)
|
||||||
|
require.NoError(t, nErr)
|
||||||
|
|
||||||
|
err = th.service.RevokeAccessToken(accessData.Token)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,79 +5,21 @@ package users
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v5/einterfaces"
|
|
||||||
"github.com/mattermost/mattermost-server/v5/model"
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
"github.com/mattermost/mattermost-server/v5/services/cache"
|
|
||||||
"github.com/mattermost/mattermost-server/v5/shared/i18n"
|
"github.com/mattermost/mattermost-server/v5/shared/i18n"
|
||||||
|
"github.com/mattermost/mattermost-server/v5/shared/mfa"
|
||||||
"github.com/mattermost/mattermost-server/v5/shared/mlog"
|
"github.com/mattermost/mattermost-server/v5/shared/mlog"
|
||||||
"github.com/mattermost/mattermost-server/v5/store"
|
"github.com/mattermost/mattermost-server/v5/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserService struct {
|
|
||||||
store store.UserStore
|
|
||||||
sessionStore store.SessionStore
|
|
||||||
sessionCache cache.Cache
|
|
||||||
sessionPool sync.Pool
|
|
||||||
metrics einterfaces.MetricsInterface
|
|
||||||
cluster einterfaces.ClusterInterface
|
|
||||||
config func() *model.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserCreateOptions struct {
|
type UserCreateOptions struct {
|
||||||
Guest bool
|
Guest bool
|
||||||
FromImport bool
|
FromImport bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServiceInitializer is used to initialize the UserService.
|
|
||||||
type ServiceInitializer struct {
|
|
||||||
// Mandatory fields
|
|
||||||
UserStore store.UserStore
|
|
||||||
SessionStore store.SessionStore
|
|
||||||
ConfigFn func() *model.Config
|
|
||||||
// Optional fields
|
|
||||||
Metrics einterfaces.MetricsInterface
|
|
||||||
Cluster einterfaces.ClusterInterface
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(initializer ServiceInitializer) (*UserService, error) {
|
|
||||||
cacheProvider := cache.NewProvider()
|
|
||||||
if err := cacheProvider.Connect(); err != nil {
|
|
||||||
return nil, fmt.Errorf("could not create cache provider: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionCache, err := cacheProvider.NewCache(&cache.CacheOptions{
|
|
||||||
Size: model.SESSION_CACHE_SIZE,
|
|
||||||
Striped: true,
|
|
||||||
StripedBuckets: maxInt(runtime.NumCPU()-1, 1),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not create session cache: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if initializer.ConfigFn == nil || initializer.UserStore == nil || initializer.SessionStore == nil {
|
|
||||||
return nil, errors.New("required parameters are not provided")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &UserService{
|
|
||||||
store: initializer.UserStore,
|
|
||||||
sessionStore: initializer.SessionStore,
|
|
||||||
config: initializer.ConfigFn,
|
|
||||||
metrics: initializer.Metrics,
|
|
||||||
cluster: initializer.Cluster,
|
|
||||||
sessionCache: sessionCache,
|
|
||||||
sessionPool: sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
return &model.Session{}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateUser creates a user
|
// CreateUser creates a user
|
||||||
func (us *UserService) CreateUser(user *model.User, opts UserCreateOptions) (*model.User, error) {
|
func (us *UserService) CreateUser(user *model.User, opts UserCreateOptions) (*model.User, error) {
|
||||||
if opts.FromImport {
|
if opts.FromImport {
|
||||||
@@ -167,6 +109,10 @@ func (us *UserService) GetUsers(options *model.UserGetOptions) ([]*model.User, e
|
|||||||
return us.store.GetAllProfiles(options)
|
return us.store.GetAllProfiles(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (us *UserService) GetUsersByUsernames(usernames []string, options *model.UserGetOptions) ([]*model.User, error) {
|
||||||
|
return us.store.GetProfilesByUsernames(usernames, options.ViewRestrictions)
|
||||||
|
}
|
||||||
|
|
||||||
func (us *UserService) GetUsersPage(options *model.UserGetOptions, asAdmin bool) ([]*model.User, error) {
|
func (us *UserService) GetUsersPage(options *model.UserGetOptions, asAdmin bool) ([]*model.User, error) {
|
||||||
users, err := us.GetUsers(options)
|
users, err := us.GetUsers(options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -243,6 +189,25 @@ func (us *UserService) GetUsersWithoutTeam(options *model.UserGetOptions) ([]*mo
|
|||||||
return users, nil
|
return users, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (us *UserService) UpdateUser(user *model.User, allowRoleUpdate bool) (*model.UserUpdate, error) {
|
||||||
|
return us.store.Update(user, allowRoleUpdate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (us *UserService) DeactivateAllGuests() ([]string, error) {
|
||||||
|
users, err := us.store.DeactivateGuests()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, userID := range users {
|
||||||
|
if err := us.RevokeAllSessions(userID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return users, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (us *UserService) InvalidateCacheForUser(userID string) {
|
func (us *UserService) InvalidateCacheForUser(userID string) {
|
||||||
us.store.InvalidateProfilesInChannelCacheByUser(userID)
|
us.store.InvalidateProfilesInChannelCacheByUser(userID)
|
||||||
us.store.InvalidateProfileCacheForUser(userID)
|
us.store.InvalidateProfileCacheForUser(userID)
|
||||||
@@ -256,3 +221,24 @@ func (us *UserService) InvalidateCacheForUser(userID string) {
|
|||||||
us.cluster.SendClusterMessage(msg)
|
us.cluster.SendClusterMessage(msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (us *UserService) GenerateMfaSecret(user *model.User) (*model.MfaSecret, error) {
|
||||||
|
secret, img, err := mfa.New(us.store).GenerateSecret(*us.config().ServiceSettings.SiteURL, user.Email, user.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the old secret is not cached on any cluster nodes.
|
||||||
|
us.InvalidateCacheForUser(user.Id)
|
||||||
|
|
||||||
|
mfaSecret := &model.MfaSecret{Secret: secret, QRCode: base64.StdEncoding.EncodeToString(img)}
|
||||||
|
return mfaSecret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (us *UserService) ActivateMfa(user *model.User, token string) error {
|
||||||
|
return mfa.New(us.store).Activate(user.MfaSecret, user.Id, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (us *UserService) DeactivateMfa(user *model.User) error {
|
||||||
|
return mfa.New(us.store).Deactivate(user.Id)
|
||||||
|
}
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ func GetMockStoreForSetupFunctions() *mocks.Store {
|
|||||||
roleStore.On("GetAll").Return([]*model.Role{}, nil)
|
roleStore.On("GetAll").Return([]*model.Role{}, nil)
|
||||||
|
|
||||||
sessionStore := mocks.SessionStore{}
|
sessionStore := mocks.SessionStore{}
|
||||||
|
oAuthStore := mocks.OAuthStore{}
|
||||||
|
|
||||||
mockStore.On("System").Return(&systemStore)
|
mockStore.On("System").Return(&systemStore)
|
||||||
mockStore.On("User").Return(&userStore)
|
mockStore.On("User").Return(&userStore)
|
||||||
@@ -101,5 +102,6 @@ func GetMockStoreForSetupFunctions() *mocks.Store {
|
|||||||
mockStore.On("DropAllTables").Return(nil)
|
mockStore.On("DropAllTables").Return(nil)
|
||||||
mockStore.On("MarkSystemRanUnitTests").Return(nil)
|
mockStore.On("MarkSystemRanUnitTests").Return(nil)
|
||||||
mockStore.On("Session").Return(&sessionStore)
|
mockStore.On("Session").Return(&sessionStore)
|
||||||
|
mockStore.On("OAuth").Return(&oAuthStore)
|
||||||
return &mockStore
|
return &mockStore
|
||||||
}
|
}
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user