[MM-58549] Desktop login (#27732)
* desktop oauth and saml login --------- Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
@@ -1979,7 +1979,15 @@ func loginWithDesktopToken(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
session, err := c.App.DoLogin(c.AppContext, w, r, user, deviceId, false, false, false)
|
||||
isOAuthUser := user.IsOAuthUser()
|
||||
isSamlUser := user.IsSAMLUser()
|
||||
|
||||
if !isOAuthUser && !isSamlUser {
|
||||
c.Err = model.NewAppError("loginWithDesktopToken", "api.user.login_with_desktop_token.not_oauth_or_saml_user.app_error", nil, "", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
session, err := c.App.DoLogin(c.AppContext, w, r, user, deviceId, false, isOAuthUser, isSamlUser)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
|
||||
@@ -7776,3 +7776,75 @@ func TestUserUpdateEvents(t *testing.T) {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoginWithDesktopToken(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
t.Run("login SAML User with desktop token", func(t *testing.T) {
|
||||
samlUser := th.CreateUserWithAuth(model.UserAuthServiceSaml)
|
||||
|
||||
token, appErr := th.App.GenerateAndSaveDesktopToken(time.Now().Unix(), samlUser)
|
||||
assert.Nil(t, appErr)
|
||||
|
||||
user, _, err := th.Client.LoginWithDesktopToken(context.Background(), *token, "")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, samlUser.Id, user.Id)
|
||||
|
||||
sessions, _, err := th.SystemAdminClient.GetSessions(context.Background(), samlUser.Id, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, sessions, 1)
|
||||
assert.Equal(t, "true", sessions[0].Props["isSaml"])
|
||||
assert.Equal(t, "false", sessions[0].Props["isOAuthUser"])
|
||||
})
|
||||
|
||||
t.Run("login OAuth User with desktop token", func(t *testing.T) {
|
||||
gitlabUser := th.CreateUserWithAuth(model.UserAuthServiceGitlab)
|
||||
|
||||
token, appErr := th.App.GenerateAndSaveDesktopToken(time.Now().Unix(), gitlabUser)
|
||||
assert.Nil(t, appErr)
|
||||
|
||||
user, _, err := th.Client.LoginWithDesktopToken(context.Background(), *token, "")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, gitlabUser.Id, user.Id)
|
||||
|
||||
sessions, _, err := th.SystemAdminClient.GetSessions(context.Background(), gitlabUser.Id, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, sessions, 1)
|
||||
assert.Equal(t, "false", sessions[0].Props["isSaml"])
|
||||
assert.Equal(t, "true", sessions[0].Props["isOAuthUser"])
|
||||
})
|
||||
|
||||
t.Run("login email user with desktop token", func(t *testing.T) {
|
||||
// Sleep to avoid rate limit error
|
||||
time.Sleep(time.Second)
|
||||
user := th.CreateUser()
|
||||
|
||||
token, appErr := th.App.GenerateAndSaveDesktopToken(time.Now().Unix(), user)
|
||||
assert.Nil(t, appErr)
|
||||
|
||||
_, resp, err := th.Client.LoginWithDesktopToken(context.Background(), *token, "")
|
||||
require.Error(t, err)
|
||||
CheckUnauthorizedStatus(t, resp)
|
||||
})
|
||||
|
||||
t.Run("invalid desktop token on login", func(t *testing.T) {
|
||||
user := th.CreateUser()
|
||||
|
||||
_, appErr := th.App.GenerateAndSaveDesktopToken(time.Now().Unix(), user)
|
||||
assert.Nil(t, appErr)
|
||||
|
||||
invalidToken := "testinvalidToken"
|
||||
token := &invalidToken
|
||||
|
||||
_, _, err := th.Client.LoginWithDesktopToken(context.Background(), *token, "")
|
||||
require.Error(t, err)
|
||||
|
||||
sessions, _, err := th.SystemAdminClient.GetSessions(context.Background(), user.Id, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, sessions, 0)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -339,7 +339,45 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
} else if action == model.OAuthActionSSOToEmail {
|
||||
redirectURL = app.GetProtocol(r) + "://" + r.Host + "/claim?email=" + url.QueryEscape(props["email"])
|
||||
} else {
|
||||
session, err := c.App.DoLogin(c.AppContext, w, r, user, "", isMobile, false, false)
|
||||
desktopToken := ""
|
||||
if val, ok := props["desktop_token"]; ok {
|
||||
desktopToken = val
|
||||
}
|
||||
|
||||
// If it's a desktop login we generate a token and redirect to another endpoint to handle session creation
|
||||
if desktopToken != "" {
|
||||
serverToken, serverTokenErr := c.App.GenerateAndSaveDesktopToken(time.Now().Unix(), user)
|
||||
if serverTokenErr != nil {
|
||||
serverTokenErr.Translate(c.AppContext.T)
|
||||
c.LogErrorByCode(serverTokenErr)
|
||||
renderError(serverTokenErr)
|
||||
return
|
||||
}
|
||||
|
||||
queryString := map[string]string{
|
||||
"client_token": desktopToken,
|
||||
"server_token": *serverToken,
|
||||
}
|
||||
if val, ok := props["redirect_to"]; ok {
|
||||
queryString["redirect_to"] = val
|
||||
}
|
||||
if strings.HasPrefix(desktopToken, "dev-") {
|
||||
queryString["isDesktopDev"] = "true"
|
||||
}
|
||||
|
||||
redirectURL = utils.AppendQueryParamsToURL(c.GetSiteURLHeader()+"/login/desktop", queryString)
|
||||
|
||||
auditRec.Success()
|
||||
c.LogAudit("success")
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
http.Redirect(w, r, redirectURL, http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
|
||||
isOAuthUser := user.IsOAuthUser()
|
||||
|
||||
session, err := c.App.DoLogin(c.AppContext, w, r, user, "", isMobile, isOAuthUser, false)
|
||||
if err != nil {
|
||||
err.Translate(c.AppContext.T)
|
||||
c.Logger.Error(err.Error())
|
||||
@@ -372,34 +410,6 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
// For web
|
||||
c.App.AttachSessionCookies(c.AppContext, w, r)
|
||||
|
||||
desktopToken := ""
|
||||
if val, ok := props["desktop_token"]; ok {
|
||||
desktopToken = val
|
||||
}
|
||||
|
||||
if desktopToken != "" {
|
||||
serverToken, serverTokenErr := c.App.GenerateAndSaveDesktopToken(time.Now().Unix(), user)
|
||||
if serverTokenErr != nil {
|
||||
serverTokenErr.Translate(c.AppContext.T)
|
||||
c.LogErrorByCode(serverTokenErr)
|
||||
renderError(serverTokenErr)
|
||||
return
|
||||
}
|
||||
|
||||
queryString := map[string]string{
|
||||
"client_token": desktopToken,
|
||||
"server_token": *serverToken,
|
||||
}
|
||||
if val, ok := props["redirect_to"]; ok {
|
||||
queryString["redirect_to"] = val
|
||||
}
|
||||
if strings.HasPrefix(desktopToken, "dev-") {
|
||||
queryString["isDesktopDev"] = "true"
|
||||
}
|
||||
|
||||
redirectURL = utils.AppendQueryParamsToURL(c.GetSiteURLHeader()+"/login/desktop", queryString)
|
||||
}
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
|
||||
@@ -178,19 +178,9 @@ func completeSaml(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
auditRec.AddMeta("obtained_user_id", user.Id)
|
||||
c.LogAuditWithUserId(user.Id, "obtained user")
|
||||
|
||||
session, err := c.App.DoLogin(c.AppContext, w, r, user, "", isMobile, false, true)
|
||||
if err != nil {
|
||||
handleError(err)
|
||||
return
|
||||
}
|
||||
c.AppContext = c.AppContext.WithSession(session)
|
||||
|
||||
auditRec.Success()
|
||||
c.LogAuditWithUserId(user.Id, "success")
|
||||
|
||||
c.App.AttachSessionCookies(c.AppContext, w, r)
|
||||
|
||||
desktopToken := relayProps["desktop_token"]
|
||||
|
||||
// If it's a desktop login we generate a token and redirect to another endpoint to handle session creation
|
||||
if desktopToken != "" {
|
||||
serverToken, serverTokenErr := c.App.GenerateAndSaveDesktopToken(time.Now().Unix(), user)
|
||||
if serverTokenErr != nil {
|
||||
@@ -214,6 +204,18 @@ func completeSaml(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// If it's not a desktop login we create a session for this SAML User that will be used in their browser or mobile app
|
||||
session, err := c.App.DoLogin(c.AppContext, w, r, user, "", isMobile, false, true)
|
||||
if err != nil {
|
||||
handleError(err)
|
||||
return
|
||||
}
|
||||
c.AppContext = c.AppContext.WithSession(session)
|
||||
|
||||
auditRec.Success()
|
||||
c.LogAuditWithUserId(user.Id, "success")
|
||||
c.App.AttachSessionCookies(c.AppContext, w, r)
|
||||
|
||||
if hasRedirectURL {
|
||||
if isMobile {
|
||||
// Mobile clients with redirect url support
|
||||
|
||||
@@ -4066,6 +4066,10 @@
|
||||
"id": "api.user.login_ldap.not_available.app_error",
|
||||
"translation": "AD/LDAP not available on this server."
|
||||
},
|
||||
{
|
||||
"id": "api.user.login_with_desktop_token.not_oauth_or_saml_user.app_error",
|
||||
"translation": "User is not an OAuth or SAML user."
|
||||
},
|
||||
{
|
||||
"id": "api.user.oauth_to_email.context.app_error",
|
||||
"translation": "Update password failed because context user_id did not match provided user's id."
|
||||
|
||||
@@ -848,6 +848,25 @@ func (c *Client4) login(ctx context.Context, m map[string]string) (*User, *Respo
|
||||
return &user, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) LoginWithDesktopToken(ctx context.Context, token, deviceId string) (*User, *Response, error) {
|
||||
m := make(map[string]string)
|
||||
m["token"] = token
|
||||
m["deviceId"] = deviceId
|
||||
r, err := c.DoAPIPost(ctx, "/users/login/desktop_token", MapToJSON(m))
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
c.AuthToken = r.Header.Get(HeaderToken)
|
||||
c.AuthType = HeaderBearer
|
||||
|
||||
var user User
|
||||
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
|
||||
return nil, nil, NewAppError("loginWithDesktopToken", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
return &user, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
// Logout terminates the current user's session.
|
||||
func (c *Client4) Logout(ctx context.Context) (*Response, error) {
|
||||
r, err := c.DoAPIPost(ctx, "/users/logout", "")
|
||||
|
||||
Ссылка в новой задаче
Block a user