* desktop oauth and saml login
---------
Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
Ben Cooke
2024-08-21 18:00:19 -04:00
коммит произвёл GitHub
родитель c113cdbd9b
Коммит 0d6b1070a2
6 изменённых файлов: 157 добавлений и 42 удалений

Просмотреть файл

@@ -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", "")