users service: add sessions (#17744)
* users: add cache to service * reflect review comments
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
0ae307808a
Коммит
24fb0033f4
@@ -8,12 +8,14 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/app/request"
|
||||
"github.com/mattermost/mattermost-server/v5/config"
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
"github.com/mattermost/mattermost-server/v5/services/cache"
|
||||
"github.com/mattermost/mattermost-server/v5/store"
|
||||
)
|
||||
|
||||
@@ -72,9 +74,22 @@ func setupTestHelper(s store.Store, includeCacheLayer bool, tb testing.TB) *Test
|
||||
configStore.Set(config)
|
||||
|
||||
buffer := &bytes.Buffer{}
|
||||
|
||||
provider := cache.NewProvider()
|
||||
cache, err := provider.NewCache(&cache.CacheOptions{
|
||||
Size: model.SESSION_CACHE_SIZE,
|
||||
Striped: true,
|
||||
StripedBuckets: maxInt(runtime.NumCPU()-1, 1),
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &TestHelper{
|
||||
service: &UserService{store: s.User(), config: configStore.Get},
|
||||
service: &UserService{
|
||||
store: s.User(),
|
||||
sessionStore: s.Session(),
|
||||
sessionCache: cache,
|
||||
config: configStore.Get,
|
||||
},
|
||||
Context: &request.Context{},
|
||||
configStore: configStore,
|
||||
dbStore: s,
|
||||
|
||||
100
services/users/session.go
Обычный файл
100
services/users/session.go
Обычный файл
@@ -0,0 +1,100 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package users
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
)
|
||||
|
||||
func (us *UserService) ReturnSessionToPool(session *model.Session) {
|
||||
if session != nil {
|
||||
session.Id = ""
|
||||
us.sessionPool.Put(session)
|
||||
}
|
||||
}
|
||||
|
||||
func (us *UserService) CreateSession(session *model.Session) (*model.Session, error) {
|
||||
session.Token = ""
|
||||
|
||||
session, err := us.sessionStore.Save(session)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
us.AddSessionToCache(session)
|
||||
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func (us *UserService) GetSession(token string) (*model.Session, error) {
|
||||
var session = us.sessionPool.Get().(*model.Session)
|
||||
if err := us.sessionCache.Get(token, session); err == nil {
|
||||
if us.metrics != nil {
|
||||
us.metrics.IncrementMemCacheHitCounterSession()
|
||||
}
|
||||
} else {
|
||||
if us.metrics != nil {
|
||||
us.metrics.IncrementMemCacheMissCounterSession()
|
||||
}
|
||||
}
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func (us *UserService) AddSessionToCache(session *model.Session) {
|
||||
us.sessionCache.SetWithExpiry(session.Token, session, time.Duration(int64(*us.config().ServiceSettings.SessionCacheInMinutes))*time.Minute)
|
||||
}
|
||||
|
||||
func (us *UserService) SessionCacheLength() int {
|
||||
if l, err := us.sessionCache.Len(); err == nil {
|
||||
return l
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (us *UserService) ClearUserSessionCacheLocal(userID string) {
|
||||
if keys, err := us.sessionCache.Keys(); err == nil {
|
||||
var session *model.Session
|
||||
for _, key := range keys {
|
||||
if err := us.sessionCache.Get(key, &session); err == nil {
|
||||
if session.UserId == userID {
|
||||
us.sessionCache.Remove(key)
|
||||
if us.metrics != nil {
|
||||
us.metrics.IncrementMemCacheInvalidationCounterSession()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (us *UserService) ClearAllUsersSessionCacheLocal() {
|
||||
us.sessionCache.Purge()
|
||||
}
|
||||
|
||||
func (us *UserService) ClearUserSessionCache(userID string) {
|
||||
us.ClearUserSessionCacheLocal(userID)
|
||||
|
||||
if us.cluster != nil {
|
||||
msg := &model.ClusterMessage{
|
||||
Event: model.CLUSTER_EVENT_CLEAR_SESSION_CACHE_FOR_USER,
|
||||
SendType: model.CLUSTER_SEND_RELIABLE,
|
||||
Data: userID,
|
||||
}
|
||||
us.cluster.SendClusterMessage(msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (us *UserService) ClearAllUsersSessionCache() {
|
||||
us.ClearAllUsersSessionCacheLocal()
|
||||
|
||||
if us.cluster != nil {
|
||||
msg := &model.ClusterMessage{
|
||||
Event: model.CLUSTER_EVENT_CLEAR_SESSION_CACHE_FOR_ALL_USERS,
|
||||
SendType: model.CLUSTER_SEND_RELIABLE,
|
||||
}
|
||||
us.cluster.SendClusterMessage(msg)
|
||||
}
|
||||
}
|
||||
49
services/users/session_test.go
Обычный файл
49
services/users/session_test.go
Обычный файл
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package users
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCache(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
session := &model.Session{
|
||||
Id: model.NewId(),
|
||||
Token: model.NewId(),
|
||||
UserId: model.NewId(),
|
||||
}
|
||||
|
||||
session2 := &model.Session{
|
||||
Id: model.NewId(),
|
||||
Token: model.NewId(),
|
||||
UserId: model.NewId(),
|
||||
}
|
||||
|
||||
th.service.sessionCache.SetWithExpiry(session.Token, session, 5*time.Minute)
|
||||
th.service.sessionCache.SetWithExpiry(session2.Token, session2, 5*time.Minute)
|
||||
|
||||
keys, err := th.service.sessionCache.Keys()
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, keys)
|
||||
|
||||
th.service.ClearUserSessionCache(session.UserId)
|
||||
|
||||
rkeys, err := th.service.sessionCache.Keys()
|
||||
require.NoError(t, err)
|
||||
require.Lenf(t, rkeys, len(keys)-1, "should have one less: %d - %d != 1", len(keys), len(rkeys))
|
||||
require.NotEmpty(t, rkeys)
|
||||
|
||||
th.service.ClearAllUsersSessionCache()
|
||||
|
||||
rkeys, err = th.service.sessionCache.Keys()
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, rkeys)
|
||||
}
|
||||
@@ -5,17 +5,27 @@ package users
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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/shared/i18n"
|
||||
"github.com/mattermost/mattermost-server/v5/shared/mlog"
|
||||
"github.com/mattermost/mattermost-server/v5/store"
|
||||
)
|
||||
|
||||
type UserService struct {
|
||||
store store.UserStore
|
||||
config func() *model.Config
|
||||
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 {
|
||||
@@ -23,11 +33,49 @@ type UserCreateOptions struct {
|
||||
FromImport bool
|
||||
}
|
||||
|
||||
func New(s store.UserStore, cfgFn func() *model.Config) *UserService {
|
||||
return &UserService{
|
||||
store: s,
|
||||
config: cfgFn,
|
||||
// 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
|
||||
@@ -41,11 +89,11 @@ func (us *UserService) CreateUser(user *model.User, opts UserCreateOptions) (*mo
|
||||
user.Roles = model.SYSTEM_GUEST_ROLE_ID
|
||||
}
|
||||
|
||||
if !user.IsLDAPUser() && !user.IsSAMLUser() && !user.IsGuest() && !checkUserDomain(user, *us.config().TeamSettings.RestrictCreationToDomains) {
|
||||
if !user.IsLDAPUser() && !user.IsSAMLUser() && !user.IsGuest() && !CheckUserDomain(user, *us.config().TeamSettings.RestrictCreationToDomains) {
|
||||
return nil, AcceptedDomainError
|
||||
}
|
||||
|
||||
if !user.IsLDAPUser() && !user.IsSAMLUser() && user.IsGuest() && !checkUserDomain(user, *us.config().GuestAccountsSettings.RestrictCreationToDomains) {
|
||||
if !user.IsLDAPUser() && !user.IsSAMLUser() && user.IsGuest() && !CheckUserDomain(user, *us.config().GuestAccountsSettings.RestrictCreationToDomains) {
|
||||
return nil, AcceptedDomainError
|
||||
}
|
||||
|
||||
@@ -194,3 +242,17 @@ func (us *UserService) GetUsersWithoutTeam(options *model.UserGetOptions) ([]*mo
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (us *UserService) InvalidateCacheForUser(userID string) {
|
||||
us.store.InvalidateProfilesInChannelCacheByUser(userID)
|
||||
us.store.InvalidateProfileCacheForUser(userID)
|
||||
|
||||
if us.cluster != nil {
|
||||
msg := &model.ClusterMessage{
|
||||
Event: model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_USER,
|
||||
SendType: model.CLUSTER_SEND_BEST_EFFORT,
|
||||
Data: userID,
|
||||
}
|
||||
us.cluster.SendClusterMessage(msg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,13 +9,38 @@ import (
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
)
|
||||
|
||||
// checkUserDomain checks that a user's email domain matches a list of space-delimited domains as a string.
|
||||
func checkUserDomain(user *model.User, domains string) bool {
|
||||
return checkEmailDomain(user.Email, domains)
|
||||
func maxInt(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// checkEmailDomain checks that an email domain matches a list of space-delimited domains as a string.
|
||||
func checkEmailDomain(email string, domains string) bool {
|
||||
func (us *UserService) IsFirstUserAccount() bool {
|
||||
cachedSessions, err := us.sessionCache.Len()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if cachedSessions == 0 {
|
||||
count, err := us.store.Count(model.UserCountOptions{IncludeDeleted: true})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if count <= 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// CheckUserDomain checks that a user's email domain matches a list of space-delimited domains as a string.
|
||||
func CheckUserDomain(user *model.User, domains string) bool {
|
||||
return CheckEmailDomain(user.Email, domains)
|
||||
}
|
||||
|
||||
// CheckEmailDomain checks that an email domain matches a list of space-delimited domains as a string.
|
||||
func CheckEmailDomain(email string, domains string) bool {
|
||||
if domains == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user