login.go 5.9 KB
Newer Older
1
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
2
3
4
5
6
7
8
// See License.txt for license information.

package app

import (
	"fmt"
	"net/http"
Corey Hulen's avatar
Corey Hulen committed
9
	"strings"
10
11
	"time"

12
	"github.com/avct/uasurfer"
Christopher Speller's avatar
Christopher Speller committed
13
	"github.com/mattermost/mattermost-server/model"
Daniel Schalla's avatar
Daniel Schalla committed
14
	"github.com/mattermost/mattermost-server/plugin"
15
	"github.com/mattermost/mattermost-server/store"
16
17
)

Corey Hulen's avatar
Corey Hulen committed
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
func (a *App) CheckForClienSideCert(r *http.Request) (string, string, string) {
	pem := r.Header.Get("X-SSL-Client-Cert")                // mapped to $ssl_client_cert from nginx
	subject := r.Header.Get("X-SSL-Client-Cert-Subject-DN") // mapped to $ssl_client_s_dn from nginx
	email := ""

	if len(subject) > 0 {
		for _, v := range strings.Split(subject, "/") {
			kv := strings.Split(v, "=")
			if len(kv) == 2 && kv[0] == "emailAddress" {
				email = kv[1]
			}
		}
	}

	return pem, subject, email
}

35
36
37
38
39
40
41
42
43
44
45
46
func (a *App) AuthenticateUserForLogin(id, loginId, password, mfaToken string, ldapOnly bool) (user *model.User, err *model.AppError) {
	// Do statistics
	defer func() {
		if a.Metrics != nil {
			if user == nil || err != nil {
				a.Metrics.IncrementLoginFail()
			} else {
				a.Metrics.IncrementLogin()
			}
		}
	}()

47
	if len(password) == 0 {
48
		err := model.NewAppError("AuthenticateUserForLogin", "api.user.login.blank_pwd.app_error", nil, "", http.StatusBadRequest)
49
50
51
		return nil, err
	}

52
53
54
55
56
	// Get the MM user we are trying to login
	if user, err = a.GetUserForLogin(id, loginId); err != nil {
		return nil, err
	}

Corey Hulen's avatar
Corey Hulen committed
57
58
59
60
61
62
63
	// If client side cert is enable and it's checking as a primary source
	// then trust the proxy and cert that the correct user is supplied and allow
	// them access
	if *a.Config().ExperimentalSettings.ClientSideCertEnable && *a.Config().ExperimentalSettings.ClientSideCertCheck == model.CLIENT_SIDE_CERT_CHECK_PRIMARY_AUTH {
		return user, nil
	}

64
65
66
67
68
	// and then authenticate them
	if user, err = a.authenticateUser(user, password, mfaToken); err != nil {
		return nil, err
	}

Daniel Schalla's avatar
Daniel Schalla committed
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
	if a.PluginsReady() {
		var rejectionReason string
		pluginContext := &plugin.Context{}
		a.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
			rejectionReason = hooks.UserWillLogIn(pluginContext, user)
			return rejectionReason == ""
		}, plugin.UserWillLogInId)

		if rejectionReason != "" {
			return nil, model.NewAppError("AuthenticateUserForLogin", "Login rejected by plugin: "+rejectionReason, nil, "", http.StatusBadRequest)
		}

		a.Go(func() {
			pluginContext := &plugin.Context{}
			a.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
				hooks.UserHasLoggedIn(pluginContext, user)
				return true
			}, plugin.UserHasLoggedInId)
		})
	}

90
91
	return user, nil
}
92

93
94
95
96
97
func (a *App) GetUserForLogin(id, loginId string) (*model.User, *model.AppError) {
	enableUsername := *a.Config().EmailSettings.EnableSignInWithUsername
	enableEmail := *a.Config().EmailSettings.EnableSignInWithEmail

	// If we are given a userID then fail if we can't find a user with that ID
98
	if len(id) != 0 {
99
100
101
102
103
104
105
		if user, err := a.GetUser(id); err != nil {
			if err.Id != store.MISSING_ACCOUNT_ERROR {
				err.StatusCode = http.StatusInternalServerError
				return nil, err
			} else {
				err.StatusCode = http.StatusBadRequest
				return nil, err
106
			}
107
108
		} else {
			return user, nil
109
110
111
		}
	}

112
113
114
	// Try to get the user by username/email
	if result := <-a.Srv.Store.User().GetForLogin(loginId, enableUsername, enableEmail); result.Err == nil {
		return result.Data.(*model.User), nil
115
116
	}

Christopher Speller's avatar
Christopher Speller committed
117
118
	// Try to get the user with LDAP if enabled
	if *a.Config().LdapSettings.Enable && a.Ldap != nil {
119
120
121
122
123
		if ldapUser, err := a.Ldap.GetUser(loginId); err == nil {
			if user, err := a.GetUserByAuth(ldapUser.AuthData, model.USER_AUTH_SERVICE_LDAP); err == nil {
				return user, nil
			}
			return ldapUser, nil
Christopher Speller's avatar
Christopher Speller committed
124
		}
125
126
	}

127
	return nil, model.NewAppError("GetUserForLogin", "store.sql_user.get_for_login.app_error", nil, "", http.StatusBadRequest)
128
129
}

Chris's avatar
Chris committed
130
func (a *App) DoLogin(w http.ResponseWriter, r *http.Request, user *model.User, deviceId string) (*model.Session, *model.AppError) {
131
	session := &model.Session{UserId: user.Id, Roles: user.GetRawRoles(), DeviceId: deviceId, IsOAuth: false}
132
	session.GenerateCSRF()
Chris's avatar
Chris committed
133
	maxAge := *a.Config().ServiceSettings.SessionLengthWebInDays * 60 * 60 * 24
134
135

	if len(deviceId) > 0 {
Chris's avatar
Chris committed
136
		session.SetExpireInDays(*a.Config().ServiceSettings.SessionLengthMobileInDays)
137
138

		// A special case where we logout of all other sessions with the same Id
Chris's avatar
Chris committed
139
		if err := a.RevokeSessionsForDeviceId(user.Id, deviceId, ""); err != nil {
140
141
142
143
			err.StatusCode = http.StatusInternalServerError
			return nil, err
		}
	} else {
Chris's avatar
Chris committed
144
		session.SetExpireInDays(*a.Config().ServiceSettings.SessionLengthWebInDays)
145
146
	}

147
	ua := uasurfer.Parse(r.UserAgent())
148

149
150
151
152
	plat := getPlatformName(ua)
	os := getOSName(ua)
	bname := getBrowserName(ua, r.UserAgent())
	bversion := getBrowserVersion(ua, r.UserAgent())
153
154
155
156
157
158

	session.AddProp(model.SESSION_PROP_PLATFORM, plat)
	session.AddProp(model.SESSION_PROP_OS, os)
	session.AddProp(model.SESSION_PROP_BROWSER, fmt.Sprintf("%v/%v", bname, bversion))

	var err *model.AppError
Chris's avatar
Chris committed
159
	if session, err = a.CreateSession(session); err != nil {
160
161
162
163
164
165
166
167
168
169
170
		err.StatusCode = http.StatusInternalServerError
		return nil, err
	}

	w.Header().Set(model.HEADER_TOKEN, session.Token)

	secure := false
	if GetProtocol(r) == "https" {
		secure = true
	}

171
	domain := a.GetCookieDomain()
172
173
174
175
176
177
178
179
	expiresAt := time.Unix(model.GetMillis()/1000+int64(maxAge), 0)
	sessionCookie := &http.Cookie{
		Name:     model.SESSION_COOKIE_TOKEN,
		Value:    session.Token,
		Path:     "/",
		MaxAge:   maxAge,
		Expires:  expiresAt,
		HttpOnly: true,
180
		Domain:   domain,
181
182
183
		Secure:   secure,
	}

184
185
186
187
188
189
	userCookie := &http.Cookie{
		Name:    model.SESSION_COOKIE_USER,
		Value:   user.Id,
		Path:    "/",
		MaxAge:  maxAge,
		Expires: expiresAt,
190
		Domain:  domain,
191
192
193
		Secure:  secure,
	}

194
	http.SetCookie(w, sessionCookie)
195
	http.SetCookie(w, userCookie)
196
197
198
199
200

	return session, nil
}

func GetProtocol(r *http.Request) string {
201
	if r.Header.Get(model.HEADER_FORWARDED_PROTO) == "https" || r.TLS != nil {
202
203
204
205
206
		return "https"
	} else {
		return "http"
	}
}