mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
* Moving goroutine pool * Auto refactor * Moving plugins. * Auto refactor * Moving fields to server * Auto refactor * Removing siteurl duplication. * Moving reset of app fields * Auto refactor * Formatting * Moving niling of Server to after last use * Fixing unit tests.
426 lines
12 KiB
Go
426 lines
12 KiB
Go
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
|
// See License.txt for license information.
|
|
|
|
package app
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"github.com/mattermost/mattermost-server/mlog"
|
|
"github.com/mattermost/mattermost-server/model"
|
|
)
|
|
|
|
func (a *App) CreateSession(session *model.Session) (*model.Session, *model.AppError) {
|
|
session.Token = ""
|
|
|
|
result := <-a.Srv.Store.Session().Save(session)
|
|
if result.Err != nil {
|
|
return nil, result.Err
|
|
}
|
|
session = result.Data.(*model.Session)
|
|
|
|
a.AddSessionToCache(session)
|
|
|
|
return session, nil
|
|
}
|
|
|
|
func (a *App) GetSession(token string) (*model.Session, *model.AppError) {
|
|
metrics := a.Metrics
|
|
|
|
var session *model.Session
|
|
if ts, ok := a.Srv.sessionCache.Get(token); ok {
|
|
session = ts.(*model.Session)
|
|
if metrics != nil {
|
|
metrics.IncrementMemCacheHitCounterSession()
|
|
}
|
|
} else {
|
|
if metrics != nil {
|
|
metrics.IncrementMemCacheMissCounterSession()
|
|
}
|
|
}
|
|
|
|
if session == nil {
|
|
if sessionResult := <-a.Srv.Store.Session().Get(token); sessionResult.Err == nil {
|
|
session = sessionResult.Data.(*model.Session)
|
|
|
|
if session != nil {
|
|
if session.Token != token {
|
|
return nil, model.NewAppError("GetSession", "api.context.invalid_token.error", map[string]interface{}{"Token": token, "Error": ""}, "", http.StatusUnauthorized)
|
|
}
|
|
|
|
if !session.IsExpired() {
|
|
a.AddSessionToCache(session)
|
|
}
|
|
}
|
|
} else if sessionResult.Err.StatusCode == http.StatusInternalServerError {
|
|
return nil, sessionResult.Err
|
|
}
|
|
}
|
|
|
|
if session == nil {
|
|
var err *model.AppError
|
|
session, err = a.createSessionForUserAccessToken(token)
|
|
if err != nil {
|
|
detailedError := ""
|
|
statusCode := http.StatusUnauthorized
|
|
if err.Id != "app.user_access_token.invalid_or_missing" {
|
|
detailedError = err.Error()
|
|
statusCode = err.StatusCode
|
|
}
|
|
return nil, model.NewAppError("GetSession", "api.context.invalid_token.error", map[string]interface{}{"Token": token}, detailedError, statusCode)
|
|
}
|
|
}
|
|
|
|
if session == nil || session.IsExpired() {
|
|
return nil, model.NewAppError("GetSession", "api.context.invalid_token.error", map[string]interface{}{"Token": token}, "", http.StatusUnauthorized)
|
|
}
|
|
|
|
license := a.License()
|
|
if *a.Config().ServiceSettings.SessionIdleTimeoutInMinutes > 0 &&
|
|
license != nil && *license.Features.Compliance &&
|
|
session != nil && !session.IsOAuth && !session.IsMobileApp() &&
|
|
session.Props[model.SESSION_PROP_TYPE] != model.SESSION_TYPE_USER_ACCESS_TOKEN {
|
|
|
|
timeout := int64(*a.Config().ServiceSettings.SessionIdleTimeoutInMinutes) * 1000 * 60
|
|
if model.GetMillis()-session.LastActivityAt > timeout {
|
|
a.RevokeSessionById(session.Id)
|
|
return nil, model.NewAppError("GetSession", "api.context.invalid_token.error", map[string]interface{}{"Token": token}, "idle timeout", http.StatusUnauthorized)
|
|
}
|
|
}
|
|
|
|
return session, nil
|
|
}
|
|
|
|
func (a *App) GetSessions(userId string) ([]*model.Session, *model.AppError) {
|
|
result := <-a.Srv.Store.Session().GetSessions(userId)
|
|
if result.Err != nil {
|
|
return nil, result.Err
|
|
}
|
|
return result.Data.([]*model.Session), nil
|
|
|
|
}
|
|
|
|
func (a *App) RevokeAllSessions(userId string) *model.AppError {
|
|
result := <-a.Srv.Store.Session().GetSessions(userId)
|
|
if result.Err != nil {
|
|
return result.Err
|
|
}
|
|
sessions := result.Data.([]*model.Session)
|
|
|
|
for _, session := range sessions {
|
|
if session.IsOAuth {
|
|
a.RevokeAccessToken(session.Token)
|
|
} else {
|
|
if result := <-a.Srv.Store.Session().Remove(session.Id); result.Err != nil {
|
|
return result.Err
|
|
}
|
|
}
|
|
}
|
|
|
|
a.ClearSessionCacheForUser(userId)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *App) ClearSessionCacheForUser(userId string) {
|
|
a.ClearSessionCacheForUserSkipClusterSend(userId)
|
|
|
|
if a.Cluster != nil {
|
|
msg := &model.ClusterMessage{
|
|
Event: model.CLUSTER_EVENT_CLEAR_SESSION_CACHE_FOR_USER,
|
|
SendType: model.CLUSTER_SEND_RELIABLE,
|
|
Data: userId,
|
|
}
|
|
a.Cluster.SendClusterMessage(msg)
|
|
}
|
|
}
|
|
|
|
func (a *App) ClearSessionCacheForUserSkipClusterSend(userId string) {
|
|
keys := a.Srv.sessionCache.Keys()
|
|
|
|
for _, key := range keys {
|
|
if ts, ok := a.Srv.sessionCache.Get(key); ok {
|
|
session := ts.(*model.Session)
|
|
if session.UserId == userId {
|
|
a.Srv.sessionCache.Remove(key)
|
|
if a.Metrics != nil {
|
|
a.Metrics.IncrementMemCacheInvalidationCounterSession()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
a.InvalidateWebConnSessionCacheForUser(userId)
|
|
}
|
|
|
|
func (a *App) AddSessionToCache(session *model.Session) {
|
|
a.Srv.sessionCache.AddWithExpiresInSecs(session.Token, session, int64(*a.Config().ServiceSettings.SessionCacheInMinutes*60))
|
|
}
|
|
|
|
func (a *App) SessionCacheLength() int {
|
|
return a.Srv.sessionCache.Len()
|
|
}
|
|
|
|
func (a *App) RevokeSessionsForDeviceId(userId string, deviceId string, currentSessionId string) *model.AppError {
|
|
result := <-a.Srv.Store.Session().GetSessions(userId)
|
|
if result.Err != nil {
|
|
return result.Err
|
|
}
|
|
sessions := result.Data.([]*model.Session)
|
|
for _, session := range sessions {
|
|
if session.DeviceId == deviceId && session.Id != currentSessionId {
|
|
mlog.Debug(fmt.Sprintf("Revoking sessionId=%v for userId=%v re-login with same device Id", session.Id, userId), mlog.String("user_id", userId))
|
|
if err := a.RevokeSession(session); err != nil {
|
|
// Soft error so we still remove the other sessions
|
|
mlog.Error(err.Error())
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *App) GetSessionById(sessionId string) (*model.Session, *model.AppError) {
|
|
result := <-a.Srv.Store.Session().Get(sessionId)
|
|
if result.Err != nil {
|
|
result.Err.StatusCode = http.StatusBadRequest
|
|
return nil, result.Err
|
|
}
|
|
return result.Data.(*model.Session), nil
|
|
}
|
|
|
|
func (a *App) RevokeSessionById(sessionId string) *model.AppError {
|
|
result := <-a.Srv.Store.Session().Get(sessionId)
|
|
if result.Err != nil {
|
|
result.Err.StatusCode = http.StatusBadRequest
|
|
return result.Err
|
|
}
|
|
return a.RevokeSession(result.Data.(*model.Session))
|
|
|
|
}
|
|
|
|
func (a *App) RevokeSession(session *model.Session) *model.AppError {
|
|
if session.IsOAuth {
|
|
if err := a.RevokeAccessToken(session.Token); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if result := <-a.Srv.Store.Session().Remove(session.Id); result.Err != nil {
|
|
return result.Err
|
|
}
|
|
}
|
|
|
|
a.ClearSessionCacheForUser(session.UserId)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *App) AttachDeviceId(sessionId string, deviceId string, expiresAt int64) *model.AppError {
|
|
if result := <-a.Srv.Store.Session().UpdateDeviceId(sessionId, deviceId, expiresAt); result.Err != nil {
|
|
return result.Err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *App) UpdateLastActivityAtIfNeeded(session model.Session) {
|
|
now := model.GetMillis()
|
|
|
|
a.UpdateWebConnUserActivity(session, now)
|
|
|
|
if now-session.LastActivityAt < model.SESSION_ACTIVITY_TIMEOUT {
|
|
return
|
|
}
|
|
|
|
if result := <-a.Srv.Store.Session().UpdateLastActivityAt(session.Id, now); result.Err != nil {
|
|
mlog.Error(fmt.Sprintf("Failed to update LastActivityAt for user_id=%v and session_id=%v, err=%v", session.UserId, session.Id, result.Err), mlog.String("user_id", session.UserId))
|
|
}
|
|
|
|
session.LastActivityAt = now
|
|
a.AddSessionToCache(&session)
|
|
}
|
|
|
|
func (a *App) CreateUserAccessToken(token *model.UserAccessToken) (*model.UserAccessToken, *model.AppError) {
|
|
if !*a.Config().ServiceSettings.EnableUserAccessTokens {
|
|
return nil, model.NewAppError("CreateUserAccessToken", "app.user_access_token.disabled", nil, "", http.StatusNotImplemented)
|
|
}
|
|
|
|
token.Token = model.NewId()
|
|
|
|
uchan := a.Srv.Store.User().Get(token.UserId)
|
|
|
|
result := <-a.Srv.Store.UserAccessToken().Save(token)
|
|
if result.Err != nil {
|
|
return nil, result.Err
|
|
}
|
|
token = result.Data.(*model.UserAccessToken)
|
|
|
|
if result := <-uchan; result.Err != nil {
|
|
mlog.Error(result.Err.Error())
|
|
} else {
|
|
user := result.Data.(*model.User)
|
|
if err := a.SendUserAccessTokenAddedEmail(user.Email, user.Locale, a.GetSiteURL()); err != nil {
|
|
mlog.Error(err.Error())
|
|
}
|
|
}
|
|
|
|
return token, nil
|
|
|
|
}
|
|
|
|
func (a *App) createSessionForUserAccessToken(tokenString string) (*model.Session, *model.AppError) {
|
|
if !*a.Config().ServiceSettings.EnableUserAccessTokens {
|
|
return nil, model.NewAppError("createSessionForUserAccessToken", "app.user_access_token.invalid_or_missing", nil, "EnableUserAccessTokens=false", http.StatusUnauthorized)
|
|
}
|
|
|
|
var token *model.UserAccessToken
|
|
result := <-a.Srv.Store.UserAccessToken().GetByToken(tokenString)
|
|
if result.Err != nil {
|
|
return nil, model.NewAppError("createSessionForUserAccessToken", "app.user_access_token.invalid_or_missing", nil, result.Err.Error(), http.StatusUnauthorized)
|
|
}
|
|
token = result.Data.(*model.UserAccessToken)
|
|
|
|
if !token.IsActive {
|
|
return nil, model.NewAppError("createSessionForUserAccessToken", "app.user_access_token.invalid_or_missing", nil, "inactive_token", http.StatusUnauthorized)
|
|
}
|
|
|
|
var user *model.User
|
|
result = <-a.Srv.Store.User().Get(token.UserId)
|
|
if result.Err != nil {
|
|
return nil, result.Err
|
|
}
|
|
user = result.Data.(*model.User)
|
|
|
|
if user.DeleteAt != 0 {
|
|
return nil, model.NewAppError("createSessionForUserAccessToken", "app.user_access_token.invalid_or_missing", nil, "inactive_user_id="+user.Id, http.StatusUnauthorized)
|
|
}
|
|
|
|
session := &model.Session{
|
|
Token: token.Token,
|
|
UserId: user.Id,
|
|
Roles: user.GetRawRoles(),
|
|
IsOAuth: false,
|
|
}
|
|
|
|
session.AddProp(model.SESSION_PROP_USER_ACCESS_TOKEN_ID, token.Id)
|
|
session.AddProp(model.SESSION_PROP_TYPE, model.SESSION_TYPE_USER_ACCESS_TOKEN)
|
|
session.SetExpireInDays(model.SESSION_USER_ACCESS_TOKEN_EXPIRY)
|
|
|
|
result = <-a.Srv.Store.Session().Save(session)
|
|
if result.Err != nil {
|
|
return nil, result.Err
|
|
}
|
|
session = result.Data.(*model.Session)
|
|
|
|
a.AddSessionToCache(session)
|
|
|
|
return session, nil
|
|
|
|
}
|
|
|
|
func (a *App) RevokeUserAccessToken(token *model.UserAccessToken) *model.AppError {
|
|
var session *model.Session
|
|
if result := <-a.Srv.Store.Session().Get(token.Token); result.Err == nil {
|
|
session = result.Data.(*model.Session)
|
|
}
|
|
|
|
if result := <-a.Srv.Store.UserAccessToken().Delete(token.Id); result.Err != nil {
|
|
return result.Err
|
|
}
|
|
|
|
if session == nil {
|
|
return nil
|
|
}
|
|
|
|
return a.RevokeSession(session)
|
|
}
|
|
|
|
func (a *App) DisableUserAccessToken(token *model.UserAccessToken) *model.AppError {
|
|
var session *model.Session
|
|
if result := <-a.Srv.Store.Session().Get(token.Token); result.Err == nil {
|
|
session = result.Data.(*model.Session)
|
|
}
|
|
|
|
if result := <-a.Srv.Store.UserAccessToken().UpdateTokenDisable(token.Id); result.Err != nil {
|
|
return result.Err
|
|
}
|
|
|
|
if session == nil {
|
|
return nil
|
|
}
|
|
|
|
return a.RevokeSession(session)
|
|
}
|
|
|
|
func (a *App) EnableUserAccessToken(token *model.UserAccessToken) *model.AppError {
|
|
var session *model.Session
|
|
if result := <-a.Srv.Store.Session().Get(token.Token); result.Err == nil {
|
|
session = result.Data.(*model.Session)
|
|
}
|
|
|
|
if result := <-a.Srv.Store.UserAccessToken().UpdateTokenEnable(token.Id); result.Err != nil {
|
|
return result.Err
|
|
}
|
|
|
|
if session == nil {
|
|
return nil
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *App) GetUserAccessTokens(page, perPage int) ([]*model.UserAccessToken, *model.AppError) {
|
|
result := <-a.Srv.Store.UserAccessToken().GetAll(page*perPage, perPage)
|
|
if result.Err != nil {
|
|
return nil, result.Err
|
|
}
|
|
tokens := result.Data.([]*model.UserAccessToken)
|
|
for _, token := range tokens {
|
|
token.Token = ""
|
|
}
|
|
|
|
return tokens, nil
|
|
|
|
}
|
|
|
|
func (a *App) GetUserAccessTokensForUser(userId string, page, perPage int) ([]*model.UserAccessToken, *model.AppError) {
|
|
result := <-a.Srv.Store.UserAccessToken().GetByUser(userId, page*perPage, perPage)
|
|
if result.Err != nil {
|
|
return nil, result.Err
|
|
}
|
|
tokens := result.Data.([]*model.UserAccessToken)
|
|
for _, token := range tokens {
|
|
token.Token = ""
|
|
}
|
|
|
|
return tokens, nil
|
|
|
|
}
|
|
|
|
func (a *App) GetUserAccessToken(tokenId string, sanitize bool) (*model.UserAccessToken, *model.AppError) {
|
|
result := <-a.Srv.Store.UserAccessToken().Get(tokenId)
|
|
if result.Err != nil {
|
|
return nil, result.Err
|
|
}
|
|
token := result.Data.(*model.UserAccessToken)
|
|
if sanitize {
|
|
token.Token = ""
|
|
}
|
|
return token, nil
|
|
|
|
}
|
|
|
|
func (a *App) SearchUserAccessTokens(term string) ([]*model.UserAccessToken, *model.AppError) {
|
|
result := <-a.Srv.Store.UserAccessToken().Search(term)
|
|
if result.Err != nil {
|
|
return nil, result.Err
|
|
}
|
|
tokens := result.Data.([]*model.UserAccessToken)
|
|
for _, token := range tokens {
|
|
token.Token = ""
|
|
}
|
|
return tokens, nil
|
|
|
|
}
|