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 }