github.com/haalcala/mattermost-server-change-repo/v5@v5.33.2/app/login.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package app
     5  
     6  import (
     7  	"crypto/subtle"
     8  	"errors"
     9  	"fmt"
    10  	"net/http"
    11  	"os"
    12  	"strconv"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/avct/uasurfer"
    17  
    18  	"github.com/mattermost/mattermost-server/v5/mlog"
    19  	"github.com/mattermost/mattermost-server/v5/model"
    20  	"github.com/mattermost/mattermost-server/v5/plugin"
    21  	"github.com/mattermost/mattermost-server/v5/store"
    22  	"github.com/mattermost/mattermost-server/v5/utils"
    23  )
    24  
    25  const cwsTokenEnv = "CWS_CLOUD_TOKEN"
    26  
    27  func (a *App) CheckForClientSideCert(r *http.Request) (string, string, string) {
    28  	pem := r.Header.Get("X-SSL-Client-Cert")                // mapped to $ssl_client_cert from nginx
    29  	subject := r.Header.Get("X-SSL-Client-Cert-Subject-DN") // mapped to $ssl_client_s_dn from nginx
    30  	email := ""
    31  
    32  	if subject != "" {
    33  		for _, v := range strings.Split(subject, "/") {
    34  			kv := strings.Split(v, "=")
    35  			if len(kv) == 2 && kv[0] == "emailAddress" {
    36  				email = kv[1]
    37  			}
    38  		}
    39  	}
    40  
    41  	return pem, subject, email
    42  }
    43  
    44  func (a *App) AuthenticateUserForLogin(id, loginId, password, mfaToken, cwsToken string, ldapOnly bool) (user *model.User, err *model.AppError) {
    45  	// Do statistics
    46  	defer func() {
    47  		if a.Metrics() != nil {
    48  			if user == nil || err != nil {
    49  				a.Metrics().IncrementLoginFail()
    50  			} else {
    51  				a.Metrics().IncrementLogin()
    52  			}
    53  		}
    54  	}()
    55  
    56  	if password == "" && !IsCWSLogin(a, cwsToken) {
    57  		return nil, model.NewAppError("AuthenticateUserForLogin", "api.user.login.blank_pwd.app_error", nil, "", http.StatusBadRequest)
    58  	}
    59  
    60  	// Get the MM user we are trying to login
    61  	if user, err = a.GetUserForLogin(id, loginId); err != nil {
    62  		return nil, err
    63  	}
    64  
    65  	// CWS login allow to use the one-time token to login the users when they're redirected to their
    66  	// installation for the first time
    67  	if IsCWSLogin(a, cwsToken) {
    68  		if err = checkUserNotBot(user); err != nil {
    69  			return nil, err
    70  		}
    71  		token, err := a.Srv().Store.Token().GetByToken(cwsToken)
    72  		if nfErr := new(store.ErrNotFound); err != nil && !errors.As(err, &nfErr) {
    73  			mlog.Debug("Error retrieving the cws token from the store", mlog.Err(err))
    74  			return nil, model.NewAppError("AuthenticateUserForLogin",
    75  				"api.user.login_by_cws.invalid_token.app_error", nil, "", http.StatusInternalServerError)
    76  		}
    77  		// If token is stored in the database that means it was used
    78  		if token != nil {
    79  			return nil, model.NewAppError("AuthenticateUserForLogin",
    80  				"api.user.login_by_cws.invalid_token.app_error", nil, "", http.StatusBadRequest)
    81  		}
    82  		envToken, ok := os.LookupEnv(cwsTokenEnv)
    83  		if ok && subtle.ConstantTimeCompare([]byte(envToken), []byte(cwsToken)) == 1 {
    84  			token = &model.Token{
    85  				Token:    cwsToken,
    86  				CreateAt: model.GetMillis(),
    87  				Type:     TokenTypeCWSAccess,
    88  			}
    89  			err := a.Srv().Store.Token().Save(token)
    90  			if err != nil {
    91  				mlog.Debug("Error storing the cws token in the store", mlog.Err(err))
    92  				return nil, model.NewAppError("AuthenticateUserForLogin",
    93  					"api.user.login_by_cws.invalid_token.app_error", nil, "", http.StatusInternalServerError)
    94  			}
    95  			return user, nil
    96  		}
    97  		return nil, model.NewAppError("AuthenticateUserForLogin",
    98  			"api.user.login_by_cws.invalid_token.app_error", nil, "", http.StatusBadRequest)
    99  	}
   100  
   101  	// If client side cert is enable and it's checking as a primary source
   102  	// then trust the proxy and cert that the correct user is supplied and allow
   103  	// them access
   104  	if *a.Config().ExperimentalSettings.ClientSideCertEnable && *a.Config().ExperimentalSettings.ClientSideCertCheck == model.CLIENT_SIDE_CERT_CHECK_PRIMARY_AUTH {
   105  		// Unless the user is a bot.
   106  		if err = checkUserNotBot(user); err != nil {
   107  			return nil, err
   108  		}
   109  
   110  		return user, nil
   111  	}
   112  
   113  	// and then authenticate them
   114  	if user, err = a.authenticateUser(user, password, mfaToken); err != nil {
   115  		return nil, err
   116  	}
   117  
   118  	return user, nil
   119  }
   120  
   121  func (a *App) GetUserForLogin(id, loginId string) (*model.User, *model.AppError) {
   122  	enableUsername := *a.Config().EmailSettings.EnableSignInWithUsername
   123  	enableEmail := *a.Config().EmailSettings.EnableSignInWithEmail
   124  
   125  	// If we are given a userID then fail if we can't find a user with that ID
   126  	if id != "" {
   127  		user, err := a.GetUser(id)
   128  		if err != nil {
   129  			if err.Id != MissingAccountError {
   130  				err.StatusCode = http.StatusInternalServerError
   131  				return nil, err
   132  			}
   133  			err.StatusCode = http.StatusBadRequest
   134  			return nil, err
   135  		}
   136  		return user, nil
   137  	}
   138  
   139  	// Try to get the user by username/email
   140  	if user, err := a.Srv().Store.User().GetForLogin(loginId, enableUsername, enableEmail); err == nil {
   141  		return user, nil
   142  	}
   143  
   144  	// Try to get the user with LDAP if enabled
   145  	if *a.Config().LdapSettings.Enable && a.Ldap() != nil {
   146  		if ldapUser, err := a.Ldap().GetUser(loginId); err == nil {
   147  			if user, err := a.GetUserByAuth(ldapUser.AuthData, model.USER_AUTH_SERVICE_LDAP); err == nil {
   148  				return user, nil
   149  			}
   150  			return ldapUser, nil
   151  		}
   152  	}
   153  
   154  	return nil, model.NewAppError("GetUserForLogin", "store.sql_user.get_for_login.app_error", nil, "", http.StatusBadRequest)
   155  }
   156  
   157  func (a *App) DoLogin(w http.ResponseWriter, r *http.Request, user *model.User, deviceId string, isMobile, isOAuthUser, isSaml bool) *model.AppError {
   158  	if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil {
   159  		var rejectionReason string
   160  		pluginContext := a.PluginContext()
   161  		pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
   162  			rejectionReason = hooks.UserWillLogIn(pluginContext, user)
   163  			return rejectionReason == ""
   164  		}, plugin.UserWillLogInId)
   165  
   166  		if rejectionReason != "" {
   167  			return model.NewAppError("DoLogin", "Login rejected by plugin: "+rejectionReason, nil, "", http.StatusBadRequest)
   168  		}
   169  	}
   170  
   171  	session := &model.Session{UserId: user.Id, Roles: user.GetRawRoles(), DeviceId: deviceId, IsOAuth: false, Props: map[string]string{
   172  		model.USER_AUTH_SERVICE_IS_MOBILE: strconv.FormatBool(isMobile),
   173  		model.USER_AUTH_SERVICE_IS_SAML:   strconv.FormatBool(isSaml),
   174  		model.USER_AUTH_SERVICE_IS_OAUTH:  strconv.FormatBool(isOAuthUser),
   175  	}}
   176  	session.GenerateCSRF()
   177  
   178  	if deviceId != "" {
   179  		a.SetSessionExpireInDays(session, *a.Config().ServiceSettings.SessionLengthMobileInDays)
   180  
   181  		// A special case where we logout of all other sessions with the same Id
   182  		if err := a.RevokeSessionsForDeviceId(user.Id, deviceId, ""); err != nil {
   183  			err.StatusCode = http.StatusInternalServerError
   184  			return err
   185  		}
   186  	} else if isMobile {
   187  		a.SetSessionExpireInDays(session, *a.Config().ServiceSettings.SessionLengthMobileInDays)
   188  	} else if isOAuthUser || isSaml {
   189  		a.SetSessionExpireInDays(session, *a.Config().ServiceSettings.SessionLengthSSOInDays)
   190  	} else {
   191  		a.SetSessionExpireInDays(session, *a.Config().ServiceSettings.SessionLengthWebInDays)
   192  	}
   193  
   194  	ua := uasurfer.Parse(r.UserAgent())
   195  
   196  	plat := getPlatformName(ua)
   197  	os := getOSName(ua)
   198  	bname := getBrowserName(ua, r.UserAgent())
   199  	bversion := getBrowserVersion(ua, r.UserAgent())
   200  
   201  	session.AddProp(model.SESSION_PROP_PLATFORM, plat)
   202  	session.AddProp(model.SESSION_PROP_OS, os)
   203  	session.AddProp(model.SESSION_PROP_BROWSER, fmt.Sprintf("%v/%v", bname, bversion))
   204  	if user.IsGuest() {
   205  		session.AddProp(model.SESSION_PROP_IS_GUEST, "true")
   206  	} else {
   207  		session.AddProp(model.SESSION_PROP_IS_GUEST, "false")
   208  	}
   209  
   210  	var err *model.AppError
   211  	if session, err = a.CreateSession(session); err != nil {
   212  		err.StatusCode = http.StatusInternalServerError
   213  		return err
   214  	}
   215  
   216  	w.Header().Set(model.HEADER_TOKEN, session.Token)
   217  
   218  	a.SetSession(session)
   219  
   220  	if a.Srv().License() != nil && *a.Srv().License().Features.LDAP && a.Ldap() != nil {
   221  		userVal := *user
   222  		sessionVal := *session
   223  		a.Srv().Go(func() {
   224  			a.Ldap().UpdateProfilePictureIfNecessary(userVal, sessionVal)
   225  		})
   226  	}
   227  
   228  	if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil {
   229  		a.Srv().Go(func() {
   230  			pluginContext := a.PluginContext()
   231  			pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
   232  				hooks.UserHasLoggedIn(pluginContext, user)
   233  				return true
   234  			}, plugin.UserHasLoggedInId)
   235  		})
   236  	}
   237  
   238  	return nil
   239  }
   240  
   241  func (a *App) AttachSessionCookies(w http.ResponseWriter, r *http.Request) {
   242  	secure := false
   243  	if GetProtocol(r) == "https" {
   244  		secure = true
   245  	}
   246  
   247  	maxAge := *a.Config().ServiceSettings.SessionLengthWebInDays * 60 * 60 * 24
   248  	domain := a.GetCookieDomain()
   249  	subpath, _ := utils.GetSubpathFromConfig(a.Config())
   250  
   251  	expiresAt := time.Unix(model.GetMillis()/1000+int64(maxAge), 0)
   252  	sessionCookie := &http.Cookie{
   253  		Name:     model.SESSION_COOKIE_TOKEN,
   254  		Value:    a.Session().Token,
   255  		Path:     subpath,
   256  		MaxAge:   maxAge,
   257  		Expires:  expiresAt,
   258  		HttpOnly: true,
   259  		Domain:   domain,
   260  		Secure:   secure,
   261  	}
   262  
   263  	userCookie := &http.Cookie{
   264  		Name:    model.SESSION_COOKIE_USER,
   265  		Value:   a.Session().UserId,
   266  		Path:    subpath,
   267  		MaxAge:  maxAge,
   268  		Expires: expiresAt,
   269  		Domain:  domain,
   270  		Secure:  secure,
   271  	}
   272  
   273  	csrfCookie := &http.Cookie{
   274  		Name:    model.SESSION_COOKIE_CSRF,
   275  		Value:   a.Session().GetCSRF(),
   276  		Path:    subpath,
   277  		MaxAge:  maxAge,
   278  		Expires: expiresAt,
   279  		Domain:  domain,
   280  		Secure:  secure,
   281  	}
   282  
   283  	http.SetCookie(w, sessionCookie)
   284  	http.SetCookie(w, userCookie)
   285  	http.SetCookie(w, csrfCookie)
   286  }
   287  
   288  func GetProtocol(r *http.Request) string {
   289  	if r.Header.Get(model.HEADER_FORWARDED_PROTO) == "https" || r.TLS != nil {
   290  		return "https"
   291  	}
   292  	return "http"
   293  }
   294  
   295  func IsCWSLogin(a *App, token string) bool {
   296  	return a.Srv().License() != nil && *a.Srv().License().Features.Cloud && token != ""
   297  }