mattermost/server/channels/utils/utils_test.go

628 lines
17 KiB
Go
Raw Permalink Normal View History

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package utils
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost/server/public/model"
)
func TestStringArrayIntersection(t *testing.T) {
a := []string{
"abc",
"def",
"ghi",
}
b := []string{
"jkl",
}
c := []string{
"def",
}
assert.Empty(t, StringArrayIntersection(a, b))
assert.Len(t, StringArrayIntersection(a, c), 1)
}
PLT-3105 Files table migration (#4068) * Implemented initial changes for files table * Removed *_benchmark_test.go files * Re-implemented GetPublicFile and added support for old path * Localization for files table * Moved file system code into utils package * Finished server-side changes and added initial upgrade script * Added getPostFiles api * Re-add Extension and HasPreviewImage fields to FileInfo * Removed unused translation * Fixed merge conflicts left over after permissions changes * Forced FileInfo.extension to be lower case * Changed FileUploadResponse to contain the FileInfos instead of FileIds * Fixed permissions on getFile* calls * Fixed notifications for file uploads * Added initial version of client code for files changes * Permanently added FileIds field to Post object and removed Post.HasFiles * Updated PostStore.Update to be usable in more circumstances * Re-added Filenames field and switched file migration to be entirely lazy-loaded * Increased max listener count for FileStore * Removed unused fileInfoCache * Moved file system code back into api * Removed duplicate test case * Fixed unit test running on ports other than 8065 * Renamed HasPermissionToPostContext to HasPermissionToChannelByPostContext * Refactored handleImages to make it more easily understandable * Renamed getPostFiles to getFileInfosForPost * Re-added pre-FileIds posts to analytics * Changed files to be saved as their ids as opposed to id/filename.ext * Renamed FileInfo.UserId to FileInfo.CreatorId * Fixed detection of language in CodePreview * Fixed switching between threads in the RHS not loading new files * Add serverside protection against a rare bug where the client sends the same file twice for a single post * Refactored the important parts of uploadFile api call into a function that can be called without a web context
2016-09-30 11:06:30 -04:00
func TestRemoveDuplicatesFromStringArray(t *testing.T) {
a := []string{
"a",
"b",
"a",
"a",
"b",
"c",
"a",
}
assert.Len(t, RemoveDuplicatesFromStringArray(a), 3)
PLT-3105 Files table migration (#4068) * Implemented initial changes for files table * Removed *_benchmark_test.go files * Re-implemented GetPublicFile and added support for old path * Localization for files table * Moved file system code into utils package * Finished server-side changes and added initial upgrade script * Added getPostFiles api * Re-add Extension and HasPreviewImage fields to FileInfo * Removed unused translation * Fixed merge conflicts left over after permissions changes * Forced FileInfo.extension to be lower case * Changed FileUploadResponse to contain the FileInfos instead of FileIds * Fixed permissions on getFile* calls * Fixed notifications for file uploads * Added initial version of client code for files changes * Permanently added FileIds field to Post object and removed Post.HasFiles * Updated PostStore.Update to be usable in more circumstances * Re-added Filenames field and switched file migration to be entirely lazy-loaded * Increased max listener count for FileStore * Removed unused fileInfoCache * Moved file system code back into api * Removed duplicate test case * Fixed unit test running on ports other than 8065 * Renamed HasPermissionToPostContext to HasPermissionToChannelByPostContext * Refactored handleImages to make it more easily understandable * Renamed getPostFiles to getFileInfosForPost * Re-added pre-FileIds posts to analytics * Changed files to be saved as their ids as opposed to id/filename.ext * Renamed FileInfo.UserId to FileInfo.CreatorId * Fixed detection of language in CodePreview * Fixed switching between threads in the RHS not loading new files * Add serverside protection against a rare bug where the client sends the same file twice for a single post * Refactored the important parts of uploadFile api call into a function that can be called without a web context
2016-09-30 11:06:30 -04:00
}
func TestStringSliceDiff(t *testing.T) {
a := []string{"one", "two", "three", "four", "five", "six"}
b := []string{"two", "seven", "four", "six"}
expected := []string{"one", "three", "five"}
assert.Equal(t, expected, StringSliceDiff(a, b))
}
func TestGetIPAddress(t *testing.T) {
t.Run("Single IP in the X-Forwarded-For", func(t *testing.T) {
httpRequest1 := http.Request{
Header: http.Header{
"X-Forwarded-For": []string{"10.0.0.1"},
"X-Real-Ip": []string{"10.1.0.1"},
},
RemoteAddr: "10.2.0.1:12345",
}
assert.Equal(t, "10.0.0.1", GetIPAddress(&httpRequest1, []string{"X-Forwarded-For"}))
})
t.Run("Multiple IPs in the X-Forwarded-For", func(t *testing.T) {
httpRequest2 := http.Request{
Header: http.Header{
"X-Forwarded-For": []string{"10.0.0.1, 10.0.0.2, 10.0.0.3"},
"X-Real-Ip": []string{"10.1.0.1"},
},
RemoteAddr: "10.2.0.1:12345",
}
assert.Equal(t, "10.0.0.1", GetIPAddress(&httpRequest2, []string{"X-Forwarded-For"}))
})
t.Run("Empty X-Forwarded-For", func(t *testing.T) {
httpRequest3 := http.Request{
Header: http.Header{
"X-Forwarded-For": []string{""},
"X-Real-Ip": []string{"10.1.0.1"},
},
RemoteAddr: "10.2.0.1:12345",
}
assert.Equal(t, "10.1.0.1", GetIPAddress(&httpRequest3, []string{"X-Forwarded-For", "X-Real-Ip"}))
})
t.Run("Without an X-Forwarded-For", func(t *testing.T) {
httpRequest4 := http.Request{
Header: http.Header{
"X-Real-Ip": []string{"10.1.0.1"},
},
RemoteAddr: "10.2.0.1:12345",
}
assert.Equal(t, "10.1.0.1", GetIPAddress(&httpRequest4, []string{"X-Forwarded-For", "X-Real-Ip"}))
})
t.Run("Without any headers", func(t *testing.T) {
httpRequest5 := http.Request{
RemoteAddr: "10.2.0.1:12345",
}
assert.Equal(t, "10.2.0.1", GetIPAddress(&httpRequest5, []string{"X-Forwarded-For", "X-Real-Ip"}))
})
t.Run("Two headers, but both untrusted", func(t *testing.T) {
httpRequest6 := http.Request{
Header: http.Header{
"X-Forwarded-For": []string{"10.3.0.1"},
"X-Real-Ip": []string{"10.1.0.1"},
},
RemoteAddr: "10.2.0.1:12345",
}
assert.Equal(t, "10.2.0.1", GetIPAddress(&httpRequest6, nil))
})
t.Run("Two headers, but only X-Real-Ip trusted", func(t *testing.T) {
httpRequest7 := http.Request{
Header: http.Header{
"X-Forwarded-For": []string{"10.3.0.1"},
"X-Real-Ip": []string{"10.1.0.1"},
},
RemoteAddr: "10.2.0.1:12345",
}
assert.Equal(t, "10.1.0.1", GetIPAddress(&httpRequest7, []string{"X-Real-Ip"}))
})
t.Run("X-Forwarded-For, comma separated, untrusted", func(t *testing.T) {
httpRequest8 := http.Request{
Header: http.Header{
"X-Forwarded-For": []string{"10.3.0.1, 10.1.0.1"},
},
RemoteAddr: "10.2.0.1:12345",
}
assert.Equal(t, "10.2.0.1", GetIPAddress(&httpRequest8, nil))
})
t.Run("X-Forwarded-For, comma separated, untrusted", func(t *testing.T) {
httpRequest9 := http.Request{
Header: http.Header{
"X-Forwarded-For": []string{"10.3.0.1, 10.1.0.1"},
},
RemoteAddr: "10.2.0.1:12345",
}
assert.Equal(t, "10.3.0.1", GetIPAddress(&httpRequest9, []string{"X-Forwarded-For"}))
})
t.Run("Two headers, both allowed, first one in trusted used", func(t *testing.T) {
httpRequest10 := http.Request{
Header: http.Header{
"X-Forwarded-For": []string{"10.3.0.1"},
"X-Real-Ip": []string{"10.1.0.1"},
},
RemoteAddr: "10.2.0.1:12345",
}
assert.Equal(t, "10.1.0.1", GetIPAddress(&httpRequest10, []string{"X-Real-Ip", "X-Forwarded-For"}))
})
t.Run("Multiple IPs in the X-Forwarded-For with no spaces", func(t *testing.T) {
httpRequest11 := http.Request{
Header: http.Header{
"X-Forwarded-For": []string{"10.0.0.1,10.0.0.2,10.0.0.3"},
"X-Real-Ip": []string{"10.1.0.1"},
},
RemoteAddr: "10.2.0.1:12345",
}
assert.Equal(t, "10.0.0.1", GetIPAddress(&httpRequest11, []string{"X-Forwarded-For"}))
})
t.Run("Make sure that the parsed value from headers only accept IP Addresses", func(t *testing.T) {
httpRequest12 := http.Request{
Header: http.Header{
"X-Forwarded-For": []string{"127.0.0.1"},
},
RemoteAddr: "10.2.0.1:12345",
}
assert.Equal(t, "127.0.0.1", GetIPAddress(&httpRequest12, []string{"X-Forwarded-For"}))
httpRequest13 := http.Request{
Header: http.Header{
"X-Forwarded-For": []string{"localhost"},
},
RemoteAddr: "10.2.0.1:12345",
}
assert.Equal(t, "10.2.0.1", GetIPAddress(&httpRequest13, []string{"X-Forwarded-For"}))
})
}
MM-16368 - Plugin Signing (#13017) * [MM-18757] POST handler for `/plugins/marketplace` (#12372) * Implement installMarketplacePlugin * Add InstallMarketplacePlugin endpoint * Fix go.mod * merge with master * Fix go.mod * Fix plugin tests * Move get plugin to marketplace client * Fix stylistic concerns * Add trailing newline to the go.mod * [MM-16586] Add plugin signature settings (#12390) * MM-17149 - Extend config.json for marketplace settings (#11933) * MM-17149 - Extend config.json for marketplace settings * Renamed MarketplaceUrl, tracking default marketplace url * Added EnableMarketplace to the client config * Revert "Added EnableMarketplace to the client config" This reverts commit 0f982c4c661c2cd9bb96264e9a01a2363c40d9c5. * MM-17149 - Added EnableMarketplace to the client config (#11958) * Added EnableMarketplace to the client config * Moved EnableMarketplace setting out of limited client configuration * Add public key settings to the config.json * Rename PublicKeys to SignaturePublicKeyFiles * Change filepath.Split to Base * Remove additional prints * Force extention of a public key file * Remove config validation * Remove error on delete * Remove config cloning * Add error messages * Add plugin public key tests * Rename extension to PluginSignaturePublicKeyFileExtention * Remove EnforceVerification * Change []*PublicKeyDescription to []string * Change .asc extension to .plugin.asc * Change ordering of public methods * Change plugin key commands * Update examples in the plugin key commands * Remove forcing extention * Add verify signature in settings * Fix tabbing * Fix naming * Remove unused text * Remove unused text * Update command examples * Fix unit tests * Change errors.New to errors.Wrap * Fix verbose flag * Change .asc to .gpg * Fix } * Change AddPublicKey signature * Change public.key extension * Add plugin public key command tests * Update en.json * Bootstrap the public keys * Update en.json * Fix en.json * Fix en.json * Bootstrap hard-coded public key * Remove unused texts in en.json * Change file to name * Add license header * Update development public key * Remove writeFile method * Remove .plugin.asc extension * Rename publiKey to mattermostPublicKey * Remove init_public_keys string * GolangCI * Closing file handlers * Fixed test that was installing nps plugin * [MM-19798] Implement plugin signature verification (#12768) * MM-17149 - Extend config.json for marketplace settings (#11933) * MM-17149 - Extend config.json for marketplace settings * Renamed MarketplaceUrl, tracking default marketplace url * Added EnableMarketplace to the client config * Revert "Added EnableMarketplace to the client config" This reverts commit 0f982c4c661c2cd9bb96264e9a01a2363c40d9c5. * MM-17149 - Added EnableMarketplace to the client config (#11958) * Added EnableMarketplace to the client config * Moved EnableMarketplace setting out of limited client configuration * Add public key settings to the config.json * Rename PublicKeys to SignaturePublicKeyFiles * Change filepath.Split to Base * Remove additional prints * Force extention of a public key file * Remove config validation * Remove error on delete * Remove config cloning * Add error messages * Add plugin public key tests * Rename extension to PluginSignaturePublicKeyFileExtention * Remove EnforceVerification * Change []*PublicKeyDescription to []string * Change .asc extension to .plugin.asc * Change ordering of public methods * Change plugin key commands * Update examples in the plugin key commands * Remove forcing extention * Add verify signature in settings * Fix tabbing * Fix naming * Remove unused text * Remove unused text * Update command examples * Fix unit tests * Change errors.New to errors.Wrap * Fix verbose flag * Change .asc to .gpg * Fix } * Change AddPublicKey signature * Change public.key extension * Add plugin public key command tests * Update en.json * Bootstrap the public keys * Update en.json * Fix en.json * Fix en.json * Bootstrap hard-coded public key * Remove unused texts in en.json * Change file to name * Add license header * Implement plugin signature verification * Remove benburker openpgp * Update en.json * Update development public key * Add support of multiple signatures in filestore * Update en.json * Run go mod vendor * Fix style * Remove writeFile method * Remove .plugin.asc extension * Rename publiKey to mattermostPublicKey * Verify plugin with mattermost public key * Remove init_public_keys string * Add InstallPluginWithSignature method and Refactor * Add signature verification on claster notification * Remove armored signature headers * Add error strings * Fix en.json * Change signatureStorePath * Implement minor fixes * Refactor plugin install methods * Add installPlugin method to uploadPlugin * Update en.json * Refactor installPlugin * Limit number of signatures * Close signatures * Fix helper function * Fix fromReadCloseSeekerToReadSeeker * Cleaned up ReadCloseSeeker for signatures * Remove signature truncation on FS * GolangCI * Add tests for armored signatures and plugin uploads * Fix nil slice issue * Fix TestPluginSync * Fixed tests * Return io.ReadSeeker from downloadFromUrl * Add log for the found plugins in the file store * Remove logging plugin detection info * [MM-20134] Consume and store single-signature for each plugin (#13081) * Consume and store single-signature for each plugin * Fix en.json * Remove saveSignature method * Remove public key hash * PR Feedback * refactored config * PR feedback
2019-11-18 19:02:41 -05:00
func TestRemoveStringFromSlice(t *testing.T) {
a := []string{"one", "two", "three", "four", "five", "six"}
expected := []string{"one", "two", "three", "five", "six"}
assert.Equal(t, RemoveStringFromSlice("four", a), expected)
}
func TestAppendQueryParamsToURL(t *testing.T) {
url := "mattermost://callback"
redirectURL := AppendQueryParamsToURL(url, map[string]string{
"key1": "value1",
"key2": "value2",
})
expected := url + "?key1=value1&key2=value2"
assert.Equal(t, redirectURL, expected)
}
func TestRoundOffToZeroes(t *testing.T) {
testCases := []struct {
desc string
n float64
expected int64
}{
{
desc: "returns 0 when n is 0",
n: 0,
expected: 0,
},
{
desc: "returns 0 when n is 9",
n: 9,
expected: 0,
},
{
desc: "returns 10 when n is 10",
n: 10,
expected: 10,
},
{
desc: "returns 90 when n is 99",
n: 99,
expected: 90,
},
{
desc: "returns 100 when n is 100",
n: 100,
expected: 100,
},
{
desc: "returns 100 when n is 101",
n: 101,
expected: 100,
},
{
desc: "returns 4000 when n is 4321",
n: 4321,
expected: 4000,
},
{
desc: "returns 0 when n is -9",
n: -9,
expected: 0,
},
{
desc: "returns -4000 when n is -4321",
n: -4321,
expected: -4000,
},
{
desc: "returns 4000 when n is 4321.235",
n: 4321.235,
expected: 4000,
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
res := RoundOffToZeroes(tc.n)
assert.Equal(t, tc.expected, res)
})
}
}
func TestIsMobileRequest(t *testing.T) {
testCases := []struct {
userAgent string
expected bool
}{
// Test cases with mobile devices
{"Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X)", true},
{"Mozilla/5.0 (Android 12; Mobile)", true},
{"Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.9999.99 Mobile Safari/537.36", true},
// Test cases with NO mobile devices
{"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36", false},
{"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36", false},
}
for _, tc := range testCases {
req := httptest.NewRequest(http.MethodGet, "/", nil)
req.Header.Set("User-Agent", tc.userAgent)
result := IsMobileRequest(req)
if result != tc.expected {
t.Errorf("User-Agent: %s, expected: %v, got: %v", tc.userAgent, tc.expected, result)
}
}
}
func TestRoundOffToZeroesResolution(t *testing.T) {
messageGranularity := 3
storageGranularity := 8
testCases := []struct {
desc string
n float64
minResolution int
expected int64
}{
{
desc: "returns 0 when n is 0",
n: 0,
expected: 0,
},
{
desc: "resolution of 0 does not round",
n: 12345,
expected: 12345,
minResolution: 0,
},
{
desc: "resolution of 1 truncates to 10s: 9 -> 0",
n: 9,
expected: 0,
minResolution: 1,
},
{
desc: "resolution of 1 truncates to 10s: 10 -> 10",
n: 10,
expected: 10,
minResolution: 1,
},
{
desc: "resolution of 1 truncates to 10s: 11 -> 10",
n: 11,
expected: 10,
minResolution: 1,
},
{
desc: "resolution of 1 truncates to 10s: 19 -> 10",
n: 19,
expected: 10,
minResolution: 1,
},
{
desc: "supports message usage granularity (1000s): 9 -> 0",
n: 9,
expected: 0,
minResolution: messageGranularity,
},
{
desc: "supports message usage granularity (1000s): 123 -> 100",
n: 9,
expected: 0,
minResolution: messageGranularity,
},
{
desc: "supports message usage granularity (1000s): 1234 -> 1000",
n: 1234,
expected: 1000,
minResolution: messageGranularity,
},
{
desc: "supports message usage granularity (1000s): 1500 -> 1000",
n: 1500,
expected: 1000,
minResolution: messageGranularity,
},
{
desc: "supports file storage usage granularity (~100s of MiB): 13GiB -> 12.94GiB",
n: 13 * 1024 * 1024 * 1024,
expected: 13_900_000_000,
minResolution: storageGranularity,
},
{
desc: "supports file storage usage granularity (~100s of MiB): 10GiB -> 9.965GiB",
n: 10 * 1024 * 1024 * 1024,
expected: 10_700_000_000,
minResolution: storageGranularity,
},
{
// first number at which usage reports as in excess of 10GiB.
// Evaluates to 10299.5992MiB
// Should be close enough for notifying of being over limit.
desc: "supports file storage usage granularity (~100s of MiB): 10.0583GiB -> 10.0583GiB",
n: 10.0583 * 1024 * 1024 * 1024,
expected: 10_800_000_000,
minResolution: storageGranularity,
},
{
desc: "supports file storage usage granularity (~100s of MiB): 953.67MiB -> 953.67MiB",
n: 1_000_000_000,
expected: 1_000_000_000,
minResolution: storageGranularity,
},
{
desc: "supports file storage usage granularity (~100s of MiB): 1GiB -> 953.67MiB",
n: 1 * 1024 * 1024 * 1024,
expected: 1_000_000_000,
minResolution: storageGranularity,
},
{
desc: "supports file storage usage granularity (~100s of MiB): 1049.04MiB -> 1049.04MiB",
n: 1_100_000_000,
expected: 1_100_000_000,
minResolution: storageGranularity,
},
{
desc: "supports file storage usage granularity (smaller amounts): 104.904MiB -> 104.904MiB",
n: 100_000_000,
expected: 100_000_000,
minResolution: storageGranularity,
},
{
desc: "supports file storage usage granularity (smaller amounts): 10.4904MiB -> 10.4904MiB",
n: 10_000_000,
expected: 10_000_000,
minResolution: storageGranularity,
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
res := RoundOffToZeroesResolution(tc.n, tc.minResolution)
assert.Equal(t, tc.expected, res)
})
}
}
func TestIsValidWebAuthRedirectURL(t *testing.T) {
t.Run("Valid redirect URL with matching scheme and host", func(t *testing.T) {
config := &model.Config{
ServiceSettings: model.ServiceSettings{
SiteURL: model.NewPointer("https://example.com"),
},
}
redirectURL := "https://example.com/oauth/callback"
err := ValidateWebAuthRedirectUrl(config, redirectURL)
require.NoError(t, err)
})
t.Run("Valid redirect URL with matching scheme and host with port", func(t *testing.T) {
config := &model.Config{
ServiceSettings: model.ServiceSettings{
SiteURL: model.NewPointer("https://example.com:8080"),
},
}
redirectURL := "https://example.com:8080/oauth/callback"
err := ValidateWebAuthRedirectUrl(config, redirectURL)
require.NoError(t, err)
})
t.Run("Invalid redirect URL with different scheme", func(t *testing.T) {
config := &model.Config{
ServiceSettings: model.ServiceSettings{
SiteURL: model.NewPointer("https://example.com"),
},
}
redirectURL := "http://example.com/oauth/callback"
err := ValidateWebAuthRedirectUrl(config, redirectURL)
require.Error(t, err)
assert.Contains(t, err.Error(), "scheme")
})
t.Run("Invalid redirect URL with different host", func(t *testing.T) {
config := &model.Config{
ServiceSettings: model.ServiceSettings{
SiteURL: model.NewPointer("https://example.com"),
},
}
redirectURL := "https://malicious.com/oauth/callback"
err := ValidateWebAuthRedirectUrl(config, redirectURL)
require.Error(t, err)
assert.Contains(t, err.Error(), "host")
})
t.Run("Invalid redirect URL with different port", func(t *testing.T) {
config := &model.Config{
ServiceSettings: model.ServiceSettings{
SiteURL: model.NewPointer("https://example.com:8080"),
},
}
redirectURL := "https://example.com:9090/oauth/callback"
err := ValidateWebAuthRedirectUrl(config, redirectURL)
require.Error(t, err)
assert.Contains(t, err.Error(), "host")
})
t.Run("Invalid redirect URL - malformed URL", func(t *testing.T) {
config := &model.Config{
ServiceSettings: model.ServiceSettings{
SiteURL: model.NewPointer("https://example.com"),
},
}
redirectURL := "://not-a-valid-url"
err := ValidateWebAuthRedirectUrl(config, redirectURL)
require.Error(t, err)
assert.Contains(t, err.Error(), "failed to parse redirect URL")
})
t.Run("Invalid config - nil SiteURL", func(t *testing.T) {
config := &model.Config{
ServiceSettings: model.ServiceSettings{
SiteURL: nil,
},
}
redirectURL := "https://example.com/oauth/callback"
err := ValidateWebAuthRedirectUrl(config, redirectURL)
require.Error(t, err)
assert.Contains(t, err.Error(), "SiteURL is not configured")
})
t.Run("Invalid config - malformed SiteURL", func(t *testing.T) {
config := &model.Config{
ServiceSettings: model.ServiceSettings{
SiteURL: model.NewPointer("://not-a-valid-url"),
},
}
redirectURL := "https://example.com/oauth/callback"
err := ValidateWebAuthRedirectUrl(config, redirectURL)
require.Error(t, err)
assert.Contains(t, err.Error(), "failed to parse SiteURL")
})
t.Run("Valid redirect URL with subdomain", func(t *testing.T) {
config := &model.Config{
ServiceSettings: model.ServiceSettings{
SiteURL: model.NewPointer("https://app.example.com"),
},
}
redirectURL := "https://app.example.com/oauth/callback"
err := ValidateWebAuthRedirectUrl(config, redirectURL)
require.NoError(t, err)
})
t.Run("Invalid redirect URL with different subdomain", func(t *testing.T) {
config := &model.Config{
ServiceSettings: model.ServiceSettings{
SiteURL: model.NewPointer("https://app.example.com"),
},
}
redirectURL := "https://api.example.com/oauth/callback"
err := ValidateWebAuthRedirectUrl(config, redirectURL)
require.Error(t, err)
assert.Contains(t, err.Error(), "host")
})
t.Run("Valid redirect URL with path", func(t *testing.T) {
config := &model.Config{
ServiceSettings: model.ServiceSettings{
SiteURL: model.NewPointer("https://example.com/mattermost"),
},
}
redirectURL := "https://example.com/mattermost/oauth/callback"
err := ValidateWebAuthRedirectUrl(config, redirectURL)
require.NoError(t, err)
})
t.Run("Valid redirect URL with query parameters", func(t *testing.T) {
config := &model.Config{
ServiceSettings: model.ServiceSettings{
SiteURL: model.NewPointer("https://example.com"),
},
}
redirectURL := "https://example.com/oauth/callback?state=abc123&code=def456"
err := ValidateWebAuthRedirectUrl(config, redirectURL)
require.NoError(t, err)
})
t.Run("Valid redirect URL with fragment", func(t *testing.T) {
config := &model.Config{
ServiceSettings: model.ServiceSettings{
SiteURL: model.NewPointer("https://example.com"),
},
}
redirectURL := "https://example.com/oauth/callback#token=abc123"
err := ValidateWebAuthRedirectUrl(config, redirectURL)
require.NoError(t, err)
})
t.Run("Invalid redirect URL with @ symbol in host", func(t *testing.T) {
config := &model.Config{
ServiceSettings: model.ServiceSettings{
SiteURL: model.NewPointer("https://qa-release.test.mattermost.cloud"),
},
}
redirectURL := "https://qa-release.test.mattermost.cloud@example.com/oauth/callback"
err := ValidateWebAuthRedirectUrl(config, redirectURL)
require.Error(t, err)
assert.Contains(t, err.Error(), "host")
})
t.Run("Valid relative URL path", func(t *testing.T) {
config := &model.Config{
ServiceSettings: model.ServiceSettings{
SiteURL: model.NewPointer("https://example.com"),
},
}
redirectURL := "/test/channels/town-square"
err := ValidateWebAuthRedirectUrl(config, redirectURL)
require.NoError(t, err)
})
}