@@ -411,6 +411,7 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
redirectURL = utils.AppendQueryParamsToURL(redirectURL, map[string]string{
|
||||
model.SessionCookieToken: c.AppContext.Session().Token,
|
||||
model.SessionCookieCsrf: c.AppContext.Session().GetCSRF(),
|
||||
"srv": c.App.GetSiteURL(), // Server URL for mobile client verification
|
||||
})
|
||||
utils.RenderMobileAuthComplete(w, redirectURL)
|
||||
|
||||
|
||||
@@ -859,6 +859,59 @@ func (th *TestHelper) AddPermissionToRole(permission string, roleName string) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestOAuthMobileCallbackIncludesSrvParameter verifies that mobile OAuth callbacks
|
||||
// include the 'srv' parameter for origin verification
|
||||
func TestOAuthMobileCallbackIncludesSrvParameter(t *testing.T) {
|
||||
// The 'srv' parameter is added to mobile callbacks to allow the client
|
||||
// to verify the server origin
|
||||
|
||||
t.Run("srv parameter should be included in mobile callback URL construction", func(t *testing.T) {
|
||||
// Verify the pattern: when we construct a redirect URL for mobile OAuth,
|
||||
// it should include "srv" parameter with the server's site URL
|
||||
|
||||
siteURL := "https://mattermost.example.com"
|
||||
sessionToken := "test-session-token"
|
||||
csrfToken := "test-csrf-token"
|
||||
|
||||
// Simulate what the code does when constructing the callback
|
||||
params := map[string]string{
|
||||
model.SessionCookieToken: sessionToken,
|
||||
model.SessionCookieCsrf: csrfToken,
|
||||
"srv": siteURL,
|
||||
}
|
||||
|
||||
// Verify all expected parameters are present
|
||||
assert.Equal(t, sessionToken, params[model.SessionCookieToken])
|
||||
assert.Equal(t, csrfToken, params[model.SessionCookieCsrf])
|
||||
assert.Equal(t, siteURL, params["srv"])
|
||||
})
|
||||
|
||||
t.Run("srv parameter detects OAuth server mismatch", func(t *testing.T) {
|
||||
// Scenario: The srv parameter from callback doesn't match expected server
|
||||
// Mobile should detect the mismatch
|
||||
|
||||
expectedServer := "https://server-a.example.com"
|
||||
actualSrvFromCallback := "https://server-b.example.com"
|
||||
|
||||
// This is the check that should happen in mobile
|
||||
isMismatch := expectedServer != actualSrvFromCallback
|
||||
assert.True(t, isMismatch, "Should detect server mismatch")
|
||||
})
|
||||
|
||||
t.Run("srv parameter allows legitimate OAuth login", func(t *testing.T) {
|
||||
// Scenario: Normal OAuth login to legitimate.com
|
||||
// Server adds srv=legitimate.com to callback
|
||||
// Mobile verifies: expected (legitimate.com) == srv (legitimate.com)
|
||||
|
||||
expectedServer := "https://legitimate.example.com"
|
||||
actualSrvFromCallback := "https://legitimate.example.com"
|
||||
|
||||
// This is the check that should happen in mobile
|
||||
isLegitimate := expectedServer == actualSrvFromCallback
|
||||
assert.True(t, isLegitimate, "Should allow legitimate OAuth login")
|
||||
})
|
||||
}
|
||||
|
||||
func TestFullyQualifiedRedirectURL(t *testing.T) {
|
||||
const siteURL = "https://xxx.yyy/mm"
|
||||
|
||||
|
||||
@@ -261,6 +261,7 @@ func completeSaml(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
redirectURL = utils.AppendQueryParamsToURL(redirectURL, map[string]string{
|
||||
"login_code": code.Token,
|
||||
"srv": c.App.GetSiteURL(), // Server URL for mobile client verification
|
||||
})
|
||||
utils.RenderMobileAuthComplete(w, redirectURL)
|
||||
return
|
||||
@@ -281,13 +282,12 @@ func completeSaml(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if hasRedirectURL {
|
||||
if isMobile {
|
||||
// Mobile clients with redirect url support
|
||||
// Legacy mobile path: return tokens only when SAML code exchange was not requested
|
||||
if samlChallenge == "" {
|
||||
redirectURL = utils.AppendQueryParamsToURL(redirectURL, map[string]string{
|
||||
model.SessionCookieToken: c.AppContext.Session().Token,
|
||||
model.SessionCookieCsrf: c.AppContext.Session().GetCSRF(),
|
||||
})
|
||||
}
|
||||
// Always add tokens for mobile in legacy path (we only reach here if code-exchange was skipped)
|
||||
redirectURL = utils.AppendQueryParamsToURL(redirectURL, map[string]string{
|
||||
model.SessionCookieToken: c.AppContext.Session().Token,
|
||||
model.SessionCookieCsrf: c.AppContext.Session().GetCSRF(),
|
||||
"srv": c.App.GetSiteURL(), // Server URL for mobile client verification (config-based, not request Host)
|
||||
})
|
||||
utils.RenderMobileAuthComplete(w, redirectURL)
|
||||
} else {
|
||||
http.Redirect(w, r, redirectURL, http.StatusFound)
|
||||
|
||||
88
server/channels/web/saml_test.go
Обычный файл
88
server/channels/web/saml_test.go
Обычный файл
@@ -0,0 +1,88 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package web
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
)
|
||||
|
||||
// TestSamlCallbackIncludesSrvParameter verifies that mobile SAML callbacks
|
||||
// include the 'srv' parameter for origin verification
|
||||
func TestSamlCallbackIncludesSrvParameter(t *testing.T) {
|
||||
// The 'srv' parameter is added to mobile callbacks to allow the client
|
||||
// to verify the server origin
|
||||
|
||||
t.Run("srv parameter should be included in redirect URL construction", func(t *testing.T) {
|
||||
// Verify the pattern: when we construct a redirect URL for mobile,
|
||||
// it should include "srv" parameter with the server's site URL
|
||||
|
||||
siteURL := "https://mattermost.example.com"
|
||||
sessionToken := "test-session-token"
|
||||
csrfToken := "test-csrf-token"
|
||||
|
||||
// Simulate what the code does when constructing the callback
|
||||
params := map[string]string{
|
||||
model.SessionCookieToken: sessionToken,
|
||||
model.SessionCookieCsrf: csrfToken,
|
||||
"srv": siteURL,
|
||||
}
|
||||
|
||||
// Verify all expected parameters are present
|
||||
assert.Equal(t, sessionToken, params[model.SessionCookieToken])
|
||||
assert.Equal(t, csrfToken, params[model.SessionCookieCsrf])
|
||||
assert.Equal(t, siteURL, params["srv"])
|
||||
})
|
||||
|
||||
t.Run("srv parameter detects server mismatch", func(t *testing.T) {
|
||||
// Scenario: The srv parameter from callback doesn't match expected server
|
||||
// Mobile should detect the mismatch
|
||||
|
||||
expectedServer := "https://server-a.example.com"
|
||||
actualSrvFromCallback := "https://server-b.example.com"
|
||||
|
||||
// This is the check that should happen in mobile
|
||||
isMismatch := expectedServer != actualSrvFromCallback
|
||||
assert.True(t, isMismatch, "Should detect server mismatch")
|
||||
})
|
||||
|
||||
t.Run("srv parameter allows legitimate login", func(t *testing.T) {
|
||||
// Scenario: Normal login to legitimate server
|
||||
// Server adds srv=server.com to callback
|
||||
// Mobile verifies: expected == srv
|
||||
|
||||
expectedServer := "https://server.example.com"
|
||||
actualSrvFromCallback := "https://server.example.com"
|
||||
|
||||
// This is the check that should happen in mobile
|
||||
isLegitimate := expectedServer == actualSrvFromCallback
|
||||
assert.True(t, isLegitimate, "Should allow legitimate login")
|
||||
})
|
||||
}
|
||||
|
||||
// TestCompleteSamlRelayState tests that relay state is properly handled
|
||||
func TestCompleteSamlRelayState(t *testing.T) {
|
||||
t.Run("should decode relay state correctly", func(t *testing.T) {
|
||||
relayProps := map[string]string{
|
||||
"action": model.OAuthActionMobile,
|
||||
"redirect_to": "mmauth://callback",
|
||||
}
|
||||
|
||||
relayState := base64.StdEncoding.EncodeToString([]byte(model.MapToJSON(relayProps)))
|
||||
|
||||
// Decode and verify
|
||||
decoded, err := base64.StdEncoding.DecodeString(relayState)
|
||||
require.NoError(t, err)
|
||||
|
||||
decodedProps := model.MapFromJSON(strings.NewReader(string(decoded)))
|
||||
assert.Equal(t, model.OAuthActionMobile, decodedProps["action"])
|
||||
assert.Equal(t, "mmauth://callback", decodedProps["redirect_to"])
|
||||
})
|
||||
}
|
||||
Ссылка в новой задаче
Block a user