Remove atmos/camo image proxy support (#37284)

This commit is contained in:
Jesse Hallam
2026-08-18 08:25:17 -03:00
committed by GitHub
parent 0912a75c9c
commit eb3966e30b
17 changed files with 43 additions and 541 deletions
@@ -775,8 +775,6 @@ const defaultServerConfig: AdminConfig = {
ImageProxySettings: {
Enable: false,
ImageProxyType: 'local',
RemoteImageProxyURL: '',
RemoteImageProxyOptions: '',
},
CloudSettings: {
CWSURL: 'https://customers.mattermost.com',
-1
View File
@@ -215,7 +215,6 @@ linters:
platform/services/cache/lru_test.go|\
platform/services/docextractor/combine.go|\
platform/services/docextractor/pdf.go|\
platform/services/imageproxy/atmos_camo_test.go|\
platform/services/imageproxy/local.go|\
platform/services/imageproxy/local_test.go|\
platform/services/remotecluster/invitation.go|\
-21
View File
@@ -43,27 +43,6 @@ func TestGetImage(t *testing.T) {
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
})
t.Run("atmos/camo", func(t *testing.T) {
imageURL := "http://foo.bar/baz.gif"
proxiedURL := "https://proxy.foo.bar/83d4d9ac78b76ce425ea67038826df867c62cc5c/687474703a2f2f666f6f2e6261722f62617a2e676966"
th.App.UpdateConfig(func(cfg *model.Config) {
cfg.ImageProxySettings.Enable = new(true)
cfg.ImageProxySettings.ImageProxyType = new("atmos/camo")
cfg.ImageProxySettings.RemoteImageProxyOptions = new("7e5f3fab20b94782b43cdb022a66985ef28ba355df2c5d5da3c9a05e4b697bac")
cfg.ImageProxySettings.RemoteImageProxyURL = new("https://proxy.foo.bar")
})
r, err := http.NewRequest("GET", th.Client.APIURL+"/image?url="+url.QueryEscape(imageURL), nil)
require.NoError(t, err)
r.Header.Set(model.HeaderAuth, th.Client.AuthType+" "+th.Client.AuthToken)
resp, err := th.Client.HTTPClient.Do(r)
require.NoError(t, err)
assert.Equal(t, http.StatusFound, resp.StatusCode)
assert.Equal(t, proxiedURL, resp.Header.Get("Location"))
})
t.Run("local", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
cfg.ImageProxySettings.Enable = new(true)
+1 -3
View File
@@ -43,9 +43,7 @@ func (s *Server) initPostMetadata() {
// Dump any cached links if the proxy settings have changed so image URLs can be updated
s.platform.AddConfigListener(func(before, after *model.Config) {
if (before.ImageProxySettings.Enable != after.ImageProxySettings.Enable) ||
(before.ImageProxySettings.ImageProxyType != after.ImageProxySettings.ImageProxyType) ||
(before.ImageProxySettings.RemoteImageProxyURL != after.ImageProxySettings.RemoteImageProxyURL) ||
(before.ImageProxySettings.RemoteImageProxyOptions != after.ImageProxySettings.RemoteImageProxyOptions) {
(before.ImageProxySettings.ImageProxyType != after.ImageProxySettings.ImageProxyType) {
if err := platform.PurgeLinkCache(); err != nil {
mlog.Warn("Failed to remove cached links when the proxy settings changed", mlog.Err(err))
}
+1 -3
View File
@@ -1035,9 +1035,7 @@ func TestPreparePostForClientWithImageProxy(t *testing.T) {
*cfg.ServiceSettings.SiteURL = "http://mymattermost.com"
*cfg.ServiceSettings.AllowedUntrustedInternalConnections = "localhost,127.0.0.1"
*cfg.ImageProxySettings.Enable = true
*cfg.ImageProxySettings.ImageProxyType = "atmos/camo"
*cfg.ImageProxySettings.RemoteImageProxyURL = "https://127.0.0.1"
*cfg.ImageProxySettings.RemoteImageProxyOptions = model.NewTestPassword()
*cfg.ImageProxySettings.ImageProxyType = "local"
})
th.App.ch.imageProxy = imageproxy.MakeImageProxy(th.Server.platform, th.Server.HTTPService(), th.Server.Log())
+3 -47
View File
@@ -894,48 +894,12 @@ func TestImageProxy(t *testing.T) {
th.App.ch.imageProxy = imageproxy.MakeImageProxy(th.Server.platform, th.Server.HTTPService(), th.Server.Log())
testHMACKey := model.NewTestPassword()
for name, tc := range map[string]struct {
ProxyType string
ProxyURL string
ProxyOptions string
ImageURL string
ProxiedImageURL string
ProxiedRemovedImageURL string
}{
"atmos/camo": {
ProxyType: model.ImageProxyTypeAtmosCamo,
ProxyURL: "https://127.0.0.1",
ProxyOptions: testHMACKey,
ImageURL: "http://mydomain.com/myimage",
ProxiedRemovedImageURL: "http://mydomain.com/myimage",
ProxiedImageURL: "http://mymattermost.com/api/v4/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage",
},
"atmos/camo_SameSite": {
ProxyType: model.ImageProxyTypeAtmosCamo,
ProxyURL: "https://127.0.0.1",
ProxyOptions: testHMACKey,
ImageURL: "http://mymattermost.com/myimage",
ProxiedRemovedImageURL: "http://mymattermost.com/myimage",
ProxiedImageURL: "http://mymattermost.com/myimage",
},
"atmos/camo_PathOnly": {
ProxyType: model.ImageProxyTypeAtmosCamo,
ProxyURL: "https://127.0.0.1",
ProxyOptions: testHMACKey,
ImageURL: "/myimage",
ProxiedRemovedImageURL: "http://mymattermost.com/myimage",
ProxiedImageURL: "http://mymattermost.com/myimage",
},
"atmos/camo_EmptyImageURL": {
ProxyType: model.ImageProxyTypeAtmosCamo,
ProxyURL: "https://127.0.0.1",
ProxyOptions: testHMACKey,
ImageURL: "",
ProxiedRemovedImageURL: "",
ProxiedImageURL: "",
},
"local": {
ProxyType: model.ImageProxyTypeLocal,
ImageURL: "http://mydomain.com/myimage",
@@ -965,8 +929,6 @@ func TestImageProxy(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
cfg.ImageProxySettings.Enable = new(true)
cfg.ImageProxySettings.ImageProxyType = new(tc.ProxyType)
cfg.ImageProxySettings.RemoteImageProxyOptions = new(tc.ProxyOptions)
cfg.ImageProxySettings.RemoteImageProxyURL = new(tc.ProxyURL)
})
post := &model.Post{
@@ -1164,9 +1126,7 @@ func TestCreatePost(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.SiteURL = "http://mymattermost.com"
*cfg.ImageProxySettings.Enable = true
*cfg.ImageProxySettings.ImageProxyType = "atmos/camo"
*cfg.ImageProxySettings.RemoteImageProxyURL = "https://127.0.0.1"
*cfg.ImageProxySettings.RemoteImageProxyOptions = model.NewTestPassword()
*cfg.ImageProxySettings.ImageProxyType = "local"
})
th.App.ch.imageProxy = imageproxy.MakeImageProxy(th.Server.platform, th.Server.HTTPService(), th.Server.Log())
@@ -2029,9 +1989,7 @@ func TestPatchPost(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.SiteURL = "http://mymattermost.com"
*cfg.ImageProxySettings.Enable = true
*cfg.ImageProxySettings.ImageProxyType = "atmos/camo"
*cfg.ImageProxySettings.RemoteImageProxyURL = "https://127.0.0.1"
*cfg.ImageProxySettings.RemoteImageProxyOptions = model.NewTestPassword()
*cfg.ImageProxySettings.ImageProxyType = "local"
})
th.App.ch.imageProxy = imageproxy.MakeImageProxy(th.Server.platform, th.Server.HTTPService(), th.Server.Log())
@@ -2490,9 +2448,7 @@ func TestUpdatePost(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.SiteURL = "http://mymattermost.com"
*cfg.ImageProxySettings.Enable = true
*cfg.ImageProxySettings.ImageProxyType = "atmos/camo"
*cfg.ImageProxySettings.RemoteImageProxyURL = "https://127.0.0.1"
*cfg.ImageProxySettings.RemoteImageProxyOptions = model.NewTestPassword()
*cfg.ImageProxySettings.ImageProxyType = "local"
})
th.App.ch.imageProxy = imageproxy.MakeImageProxy(th.Server.platform, th.Server.HTTPService(), th.Server.Log())
+1 -3
View File
@@ -598,9 +598,7 @@ func NewServer(options ...Option) (*Server, error) {
// Dump the image cache if the proxy settings have changed. (need switch URLs to the correct proxy)
s.platform.AddConfigListener(func(oldCfg, newCfg *model.Config) {
if (oldCfg.ImageProxySettings.Enable != newCfg.ImageProxySettings.Enable) ||
(oldCfg.ImageProxySettings.ImageProxyType != newCfg.ImageProxySettings.ImageProxyType) ||
(oldCfg.ImageProxySettings.RemoteImageProxyURL != newCfg.ImageProxySettings.RemoteImageProxyURL) ||
(oldCfg.ImageProxySettings.RemoteImageProxyOptions != newCfg.ImageProxySettings.RemoteImageProxyOptions) {
(oldCfg.ImageProxySettings.ImageProxyType != newCfg.ImageProxySettings.ImageProxyType) {
if err = s.openGraphDataCache.Purge(); err != nil {
mlog.Error("Failed to purge Open Graph data cache after config change", mlog.Err(err))
}
+3 -11
View File
@@ -11719,16 +11719,8 @@
"translation": "Invalid timeout value {{.Value}}. Should be a positive number."
},
{
"id": "model.config.is_valid.atmos_camo_image_proxy_options.app_error",
"translation": "Invalid RemoteImageProxyOptions for atmos/camo. Must be set to your shared key."
},
{
"id": "model.config.is_valid.atmos_camo_image_proxy_options_length.app_error",
"translation": "Invalid RemoteImageProxyOptions for atmos/camo: HMAC key must be at least {{.MinLength}} bytes for FIPS compliance."
},
{
"id": "model.config.is_valid.atmos_camo_image_proxy_url.app_error",
"translation": "Invalid RemoteImageProxyURL for atmos/camo. Must be set to your shared key."
"id": "model.config.is_valid.atmos_camo_image_proxy_removed.app_error",
"translation": "The 'atmos/camo' image proxy type was removed in Mattermost v12.0. Set ImageProxySettings.ImageProxyType to 'local', or set ImageProxySettings.Enable to false to disable the image proxy."
},
{
"id": "model.config.is_valid.autotranslation.agents.llm_service_id.app_error",
@@ -11992,7 +11984,7 @@
},
{
"id": "model.config.is_valid.image_proxy_type.app_error",
"translation": "Invalid image proxy type. Must be 'local' or 'atmos/camo'."
"translation": "Invalid image proxy type. Must be 'local'."
},
{
"id": "model.config.is_valid.import.directory.app_error",
@@ -1,90 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package imageproxy
import (
"crypto/hmac"
"crypto/sha1"
"encoding/hex"
"io"
"net/http"
"net/url"
"github.com/mattermost/mattermost/server/public/model"
)
type AtmosCamoBackend struct {
siteURL *url.URL
remoteOptions string
remoteURL *url.URL
client *http.Client
}
func makeAtmosCamoBackend(proxy *ImageProxy, proxySettings model.ImageProxySettings) *AtmosCamoBackend {
// We deliberately ignore the error because it's from config.json.
// The function returns a nil pointer in case of error, and we handle it when it's used.
remoteURL, _ := url.Parse(*proxySettings.RemoteImageProxyURL)
return &AtmosCamoBackend{
siteURL: proxy.siteURL,
remoteURL: remoteURL,
remoteOptions: *proxySettings.RemoteImageProxyOptions,
client: proxy.HTTPService.MakeClient(false),
}
}
func (backend *AtmosCamoBackend) GetImage(w http.ResponseWriter, r *http.Request, imageURL string) {
http.Redirect(w, r, backend.getAtmosCamoImageURL(imageURL), http.StatusFound)
}
func (backend *AtmosCamoBackend) GetImageDirect(imageURL string) (io.ReadCloser, string, error) {
req, err := http.NewRequest("GET", backend.getAtmosCamoImageURL(imageURL), nil)
if err != nil {
return nil, "", Error{err}
}
resp, err := backend.client.Do(req)
if err != nil {
return nil, "", Error{err}
}
// Note that we don't do any additional validation of the received data since we expect the image proxy to do that
return resp.Body, resp.Header.Get("Content-Type"), nil
}
func (backend *AtmosCamoBackend) getAtmosCamoImageURL(imageURL string) string {
if imageURL == "" || backend.siteURL == nil {
return imageURL
}
// Parse url, return siteURL in case of failure.
// Also if the URL is opaque.
parsedURL, err := url.Parse(imageURL)
if err != nil || parsedURL.Opaque != "" {
return backend.siteURL.String()
}
// If host is same as siteURL host/ remoteURL host, return.
if parsedURL.Host == backend.siteURL.Host || parsedURL.Host == backend.remoteURL.Host {
return parsedURL.String()
}
// Handle protocol-relative URLs.
if parsedURL.Scheme == "" {
parsedURL.Scheme = backend.siteURL.Scheme
}
// If it's a relative URL, fill up the hostname and scheme and return.
if parsedURL.Host == "" {
parsedURL.Host = backend.siteURL.Host
return parsedURL.String()
}
urlBytes := []byte(parsedURL.String())
mac := hmac.New(sha1.New, []byte(backend.remoteOptions))
mac.Write(urlBytes)
digest := hex.EncodeToString(mac.Sum(nil))
return backend.remoteURL.String() + "/" + digest + "/" + hex.EncodeToString(urlBytes)
}
@@ -1,187 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package imageproxy
import (
"io"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/httpservice"
"github.com/mattermost/mattermost/server/v8/channels/utils/testutils"
)
func makeTestAtmosCamoProxy() *ImageProxy {
configService := &testutils.StaticConfigService{
Cfg: &model.Config{
ServiceSettings: model.ServiceSettings{
SiteURL: new("https://mattermost.example.com"),
AllowedUntrustedInternalConnections: new("127.0.0.1"),
},
ImageProxySettings: model.ImageProxySettings{
Enable: new(true),
ImageProxyType: model.NewPointer(model.ImageProxyTypeAtmosCamo),
RemoteImageProxyURL: new("http://images.example.com"),
RemoteImageProxyOptions: new("7e5f3fab20b94782b43cdb022a66985ef28ba355df2c5d5da3c9a05e4b697bac"),
},
},
}
return MakeImageProxy(configService, httpservice.MakeHTTPService(configService), nil)
}
func TestAtmosCamoBackend_GetImage(t *testing.T) {
imageURL := "https://www.mattermost.com/wp-content/uploads/2022/02/logoHorizontalWhite.png"
proxiedURL := "http://images.example.com/b569ce17f1be4550cffa8d8dd3a9e80e6d209584/68747470733a2f2f7777772e6d61747465726d6f73742e636f6d2f77702d636f6e74656e742f75706c6f6164732f323032322f30322f6c6f676f486f72697a6f6e74616c57686974652e706e67"
proxy := makeTestAtmosCamoProxy()
recorder := httptest.NewRecorder()
request, _ := http.NewRequest(http.MethodGet, "", nil)
proxy.GetImage(recorder, request, imageURL)
resp := recorder.Result()
assert.Equal(t, http.StatusFound, resp.StatusCode)
assert.Equal(t, proxiedURL, resp.Header.Get("Location"))
}
func TestAtmosCamoBackend_GetImageDirect(t *testing.T) {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "max-age=2592000, private")
w.Header().Set("Content-Type", "image/png")
w.Header().Set("Content-Length", "10")
w.WriteHeader(http.StatusOK)
w.Write([]byte("1111111111"))
})
mock := httptest.NewServer(handler)
defer mock.Close()
proxy := makeTestAtmosCamoProxy()
parsedURL, err := url.Parse("https://mattermost.example.com")
require.NoError(t, err)
remoteURL, err := url.Parse(mock.URL)
require.NoError(t, err)
backend := &AtmosCamoBackend{
siteURL: parsedURL,
remoteURL: remoteURL,
client: proxy.HTTPService.MakeClient(false),
}
body, contentType, err := backend.GetImageDirect("https://example.com/image.png")
assert.NoError(t, err)
assert.Equal(t, "image/png", contentType)
require.NotNil(t, body)
respBody, _ := io.ReadAll(body)
assert.Equal(t, []byte("1111111111"), respBody)
}
func TestGetAtmosCamoImageURL(t *testing.T) {
imageURL := "https://mattermost.com/wp-content/uploads/2022/02/logoHorizontal.png"
proxiedURL := "http://images.example.com/03b122734ae088d10cb46ea05512ec7dc852299e/68747470733a2f2f6d61747465726d6f73742e636f6d2f77702d636f6e74656e742f75706c6f6164732f323032322f30322f6c6f676f486f72697a6f6e74616c2e706e67"
defaultSiteURL := "https://mattermost.example.com"
proxyURL := "http://images.example.com"
for _, test := range []struct {
Name string
Input string
SiteURL string
Expected string
}{
{
Name: "should proxy image",
Input: imageURL,
SiteURL: defaultSiteURL,
Expected: proxiedURL,
},
{
Name: "should proxy image when no site URL is set",
Input: imageURL,
SiteURL: "",
Expected: proxiedURL,
},
{
Name: "should proxy image when a site URL with a subpath is set",
Input: imageURL,
SiteURL: proxyURL + "/subpath",
Expected: proxiedURL,
},
{
Name: "should not proxy a relative image",
Input: "/static/logo.png",
SiteURL: defaultSiteURL,
Expected: "https://mattermost.example.com/static/logo.png",
},
{
Name: "should bypass opaque URLs",
Input: "http:xyz123?query",
SiteURL: defaultSiteURL,
Expected: defaultSiteURL,
},
{
Name: "should not proxy an image on the Mattermost server",
Input: "https://mattermost.example.com/static/logo.png",
SiteURL: defaultSiteURL,
Expected: "https://mattermost.example.com/static/logo.png",
},
{
Name: "should not proxy an image on the Mattermost server when a subpath is set",
Input: "https://mattermost.example.com/static/logo.png",
SiteURL: defaultSiteURL + "/static",
Expected: "https://mattermost.example.com/static/logo.png",
},
{
Name: "should not proxy an image that has already been proxied",
Input: proxiedURL,
SiteURL: defaultSiteURL,
Expected: proxiedURL,
},
{
Name: "should not bypass protocol relative URLs",
Input: "https://mattermost.com/wp-content/uploads/2022/02/logoHorizontal.png",
SiteURL: "http://mattermost.example.com",
Expected: proxiedURL,
},
{
Name: "should not bypass if the host prefix is same",
Input: "https://mattermost.com/wp-content/uploads/2022/02/logoHorizontal.png",
SiteURL: defaultSiteURL,
Expected: "http://images.example.com/03b122734ae088d10cb46ea05512ec7dc852299e/68747470733a2f2f6d61747465726d6f73742e636f6d2f77702d636f6e74656e742f75706c6f6164732f323032322f30322f6c6f676f486f72697a6f6e74616c2e706e67",
},
{
Name: "should not bypass for user auth URLs",
Input: "https://mattermost.com/wp-content/uploads/2022/02/logoHorizontal.png",
SiteURL: defaultSiteURL,
Expected: "http://images.example.com/03b122734ae088d10cb46ea05512ec7dc852299e/68747470733a2f2f6d61747465726d6f73742e636f6d2f77702d636f6e74656e742f75706c6f6164732f323032322f30322f6c6f676f486f72697a6f6e74616c2e706e67",
},
} {
t.Run(test.Name, func(t *testing.T) {
parsedURL, err := url.Parse(test.SiteURL)
require.NoError(t, err)
remoteURL, err := url.Parse(proxyURL)
require.NoError(t, err)
backend := &AtmosCamoBackend{
siteURL: parsedURL,
remoteURL: remoteURL,
remoteOptions: *makeTestAtmosCamoProxy().ConfigService.Config().ImageProxySettings.RemoteImageProxyOptions,
}
assert.Equal(t, test.Expected, backend.getAtmosCamoImageURL(test.Input))
})
}
}
@@ -73,8 +73,6 @@ func (proxy *ImageProxy) makeBackend(proxySettings model.ImageProxySettings) Ima
switch *proxySettings.ImageProxyType {
case model.ImageProxyTypeLocal:
return makeLocalBackend(proxy)
case model.ImageProxyTypeAtmosCamo:
return makeAtmosCamoBackend(proxy, proxySettings)
default:
return nil
}
@@ -7,7 +7,6 @@ import (
"net/url"
"testing"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/v8/channels/utils/testutils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -119,26 +118,6 @@ func TestGetUnproxiedImageURL(t *testing.T) {
}
func TestOnConfigChange(t *testing.T) {
t.Run("should switch between backends", func(t *testing.T) {
proxy := makeTestAtmosCamoProxy()
require.Equal(t, "https://mattermost.example.com", proxy.backend.(*AtmosCamoBackend).siteURL.String())
newConfig := proxy.ConfigService.Config().Clone()
newConfig.ImageProxySettings.ImageProxyType = model.NewPointer(model.ImageProxyTypeLocal)
proxy.ConfigService.(*testutils.StaticConfigService).UpdateConfig(newConfig)
require.Equal(t, "https://mattermost.example.com", proxy.backend.(*LocalBackend).baseURL.String())
newConfig = proxy.ConfigService.Config().Clone()
newConfig.ImageProxySettings.ImageProxyType = model.NewPointer(model.ImageProxyTypeAtmosCamo)
proxy.ConfigService.(*testutils.StaticConfigService).UpdateConfig(newConfig)
require.Equal(t, "https://mattermost.example.com", proxy.backend.(*AtmosCamoBackend).siteURL.String())
})
t.Run("for local proxy, should update site URL when that changes", func(t *testing.T) {
proxy := makeTestLocalProxy()
@@ -153,35 +132,4 @@ func TestOnConfigChange(t *testing.T) {
require.Equal(t, "https://new.example.com", proxy.siteURL.String())
require.Equal(t, "https://new.example.com", proxy.backend.(*LocalBackend).baseURL.String())
})
t.Run("for atmos/camo proxy, should update site URL when that changes", func(t *testing.T) {
proxy := makeTestAtmosCamoProxy()
require.Equal(t, "https://mattermost.example.com", proxy.siteURL.String())
require.Equal(t, "https://mattermost.example.com", proxy.backend.(*AtmosCamoBackend).siteURL.String())
newConfig := proxy.ConfigService.Config().Clone()
newConfig.ServiceSettings.SiteURL = new("https://new.example.com")
proxy.ConfigService.(*testutils.StaticConfigService).UpdateConfig(newConfig)
require.Equal(t, "https://new.example.com", proxy.siteURL.String())
require.Equal(t, "https://new.example.com", proxy.backend.(*AtmosCamoBackend).siteURL.String())
})
t.Run("for atmos/camo proxy, should update additional options when those change", func(t *testing.T) {
proxy := makeTestAtmosCamoProxy()
require.Equal(t, "http://images.example.com", proxy.backend.(*AtmosCamoBackend).remoteURL.String())
// require.Equal(t, "7e5f3fab20b94782b43cdb022a66985ef28ba355df2c5d5da3c9a05e4b697bac", proxy.backend.(*AtmosCamoBackend).remoteOptions)
newConfig := proxy.ConfigService.Config().Clone()
newConfig.ImageProxySettings.RemoteImageProxyURL = new("https://new.example.com")
newConfig.ImageProxySettings.RemoteImageProxyOptions = new("some other random hash")
proxy.ConfigService.(*testutils.StaticConfigService).UpdateConfig(newConfig)
require.Equal(t, "https://new.example.com", proxy.backend.(*AtmosCamoBackend).remoteURL.String())
// require.Equal(t, "some other random hash", proxy.backend.(*AtmosCamoBackend).remoteOptions)
})
}
+8 -28
View File
@@ -286,8 +286,8 @@ const (
GlobalrelayCustomerTypeA10 = "A10"
GlobalrelayCustomerTypeCustom = "CUSTOM"
ImageProxyTypeLocal = "local"
ImageProxyTypeAtmosCamo = "atmos/camo"
ImageProxyTypeLocal = "local"
ImageProxyTypeLegacyAtmosCamo = "atmos/camo"
GoogleSettingsDefaultScope = "profile email"
GoogleSettingsDefaultAuthEndpoint = "https://accounts.google.com/o/oauth2/v2/auth"
@@ -3986,10 +3986,8 @@ func (s *GuestAccountsSettings) IsValid() *AppError {
}
type ImageProxySettings struct {
Enable *bool `access:"environment_image_proxy"`
ImageProxyType *string `access:"environment_image_proxy"`
RemoteImageProxyURL *string `access:"environment_image_proxy"`
RemoteImageProxyOptions *string `access:"environment_image_proxy"`
Enable *bool `access:"environment_image_proxy"`
ImageProxyType *string `access:"environment_image_proxy"`
}
func (s *ImageProxySettings) SetDefaults() {
@@ -4000,14 +3998,6 @@ func (s *ImageProxySettings) SetDefaults() {
if s.ImageProxyType == nil {
s.ImageProxyType = new(ImageProxyTypeLocal)
}
if s.RemoteImageProxyURL == nil {
s.RemoteImageProxyURL = new("")
}
if s.RemoteImageProxyOptions == nil {
s.RemoteImageProxyOptions = new("")
}
}
// ImportSettings defines configuration settings for file imports.
@@ -5295,24 +5285,14 @@ func (s *DisplaySettings) isValid() *AppError {
}
func (s *ImageProxySettings) isValid() *AppError {
if *s.ImageProxyType == ImageProxyTypeLegacyAtmosCamo {
return NewAppError("Config.IsValid", "model.config.is_valid.atmos_camo_image_proxy_removed.app_error", nil, "", http.StatusBadRequest)
}
if *s.Enable {
switch *s.ImageProxyType {
case ImageProxyTypeLocal:
// No other settings to validate
case ImageProxyTypeAtmosCamo:
if *s.RemoteImageProxyURL == "" {
return NewAppError("Config.IsValid", "model.config.is_valid.atmos_camo_image_proxy_url.app_error", nil, "", http.StatusBadRequest)
}
if *s.RemoteImageProxyOptions == "" {
return NewAppError("Config.IsValid", "model.config.is_valid.atmos_camo_image_proxy_options.app_error", nil, "", http.StatusBadRequest)
}
// RemoteImageProxyOptions is used as the HMAC key for URL signing,
// so it is subject to the same FIPS minimum key length as passwords.
if FIPSEnabled && len(*s.RemoteImageProxyOptions) < PasswordFIPSMinimumLength {
return NewAppError("Config.IsValid", "model.config.is_valid.atmos_camo_image_proxy_options_length.app_error", map[string]any{"MinLength": PasswordFIPSMinimumLength}, "", http.StatusBadRequest)
}
default:
return NewAppError("Config.IsValid", "model.config.is_valid.image_proxy_type.app_error", nil, "", http.StatusBadRequest)
}
+26 -58
View File
@@ -1354,21 +1354,15 @@ func TestImageProxySettingsSetDefaults(t *testing.T) {
assert.Equal(t, false, *ips.Enable)
assert.Equal(t, ImageProxyTypeLocal, *ips.ImageProxyType)
assert.Equal(t, "", *ips.RemoteImageProxyURL)
assert.Equal(t, "", *ips.RemoteImageProxyOptions)
})
}
func TestImageProxySettingsIsValid(t *testing.T) {
testHMACKey := NewTestPassword()
for _, test := range []struct {
Name string
Enable bool
ImageProxyType string
RemoteImageProxyURL string
RemoteImageProxyOptions string
ExpectError bool
Name string
Enable bool
ImageProxyType string
ExpectError bool
}{
{
Name: "disabled",
@@ -1376,12 +1370,22 @@ func TestImageProxySettingsIsValid(t *testing.T) {
ExpectError: false,
},
{
Name: "disabled with bad values",
Enable: false,
ImageProxyType: "garbage",
RemoteImageProxyURL: "garbage",
RemoteImageProxyOptions: "garbage",
ExpectError: false,
Name: "disabled with bad values",
Enable: false,
ImageProxyType: "garbage",
ExpectError: false,
},
{
Name: "atmos/camo, disabled",
Enable: false,
ImageProxyType: ImageProxyTypeLegacyAtmosCamo,
ExpectError: true,
},
{
Name: "atmos/camo, enabled",
Enable: true,
ImageProxyType: ImageProxyTypeLegacyAtmosCamo,
ExpectError: true,
},
{
Name: "missing type",
@@ -1390,52 +1394,16 @@ func TestImageProxySettingsIsValid(t *testing.T) {
ExpectError: true,
},
{
Name: "local",
Enable: true,
ImageProxyType: "local",
RemoteImageProxyURL: "garbage",
RemoteImageProxyOptions: "garbage",
ExpectError: false,
},
{
Name: "atmos/camo",
Enable: true,
ImageProxyType: ImageProxyTypeAtmosCamo,
RemoteImageProxyURL: "someurl",
RemoteImageProxyOptions: testHMACKey,
ExpectError: false,
},
{
Name: "atmos/camo, missing url",
Enable: true,
ImageProxyType: ImageProxyTypeAtmosCamo,
RemoteImageProxyURL: "",
RemoteImageProxyOptions: "garbage",
ExpectError: true,
},
{
Name: "atmos/camo, missing options",
Enable: true,
ImageProxyType: ImageProxyTypeAtmosCamo,
RemoteImageProxyURL: "someurl",
RemoteImageProxyOptions: "",
ExpectError: true,
},
{
Name: "atmos/camo, short options under FIPS",
Enable: true,
ImageProxyType: ImageProxyTypeAtmosCamo,
RemoteImageProxyURL: "someurl",
RemoteImageProxyOptions: "foo",
ExpectError: FIPSEnabled,
Name: "local",
Enable: true,
ImageProxyType: "local",
ExpectError: false,
},
} {
t.Run(test.Name, func(t *testing.T) {
ips := &ImageProxySettings{
Enable: &test.Enable,
ImageProxyType: &test.ImageProxyType,
RemoteImageProxyURL: &test.RemoteImageProxyURL,
RemoteImageProxyOptions: &test.RemoteImageProxyOptions,
Enable: &test.Enable,
ImageProxyType: &test.ImageProxyType,
}
appErr := ips.isValid()
@@ -1673,10 +1673,6 @@ const AdminDefinition: AdminDefinitionType = {
},
help_text_markdown: false,
options: [
{
value: 'atmos/camo',
display_name: defineMessage({id: 'atmos/camo', defaultMessage: 'atmos/camo'}),
},
{
value: 'local',
display_name: defineMessage({id: 'local', defaultMessage: 'local'}),
@@ -1687,28 +1683,6 @@ const AdminDefinition: AdminDefinitionType = {
it.stateIsFalse('ImageProxySettings.Enable'),
),
},
{
type: 'text',
key: 'ImageProxySettings.RemoteImageProxyURL',
label: defineMessage({id: 'admin.image.proxyURL', defaultMessage: 'Remote Image Proxy URL:'}),
help_text: defineMessage({id: 'admin.image.proxyURLDescription', defaultMessage: 'URL of your remote image proxy server.'}),
isDisabled: it.any(
it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.IMAGE_PROXY)),
it.stateIsFalse('ImageProxySettings.Enable'),
it.stateEquals('ImageProxySettings.ImageProxyType', 'local'),
),
},
{
type: 'text',
key: 'ImageProxySettings.RemoteImageProxyOptions',
label: defineMessage({id: 'admin.image.proxyOptions', defaultMessage: 'Remote Image Proxy Options:'}),
help_text: defineMessage({id: 'admin.image.proxyOptionsDescription', defaultMessage: 'Additional options such as the URL signing key. Refer to your image proxy documentation to learn more about what options are supported.'}),
isDisabled: it.any(
it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ENVIRONMENT.IMAGE_PROXY)),
it.stateIsFalse('ImageProxySettings.Enable'),
it.stateEquals('ImageProxySettings.ImageProxyType', 'local'),
),
},
],
},
},
-5
View File
@@ -1689,12 +1689,8 @@
"admin.image.maxFileSizeDescription": "Maximum file size for message attachments in megabytes. Caution: Verify server memory can support your setting choice. Large file sizes increase the risk of server crashes and failed uploads due to network interruptions.",
"admin.image.maxFileSizeExample": "50",
"admin.image.maxFileSizeTitle": "Maximum File Size:",
"admin.image.proxyOptions": "Remote Image Proxy Options:",
"admin.image.proxyOptionsDescription": "Additional options such as the URL signing key. Refer to your image proxy documentation to learn more about what options are supported.",
"admin.image.proxyType": "Image Proxy Type:",
"admin.image.proxyTypeDescription": "Configure an image proxy to load all Markdown images through a proxy. The image proxy prevents users from making insecure image requests, provides caching for increased performance, and automates image adjustments such as resizing. See <link>documentation</link> to learn more.",
"admin.image.proxyURL": "Remote Image Proxy URL:",
"admin.image.proxyURLDescription": "URL of your remote image proxy server.",
"admin.image.publicLinkDescription": "32-character salt added to signing of public links. Randomly generated on install. Select \"Regenerate\" to create new salt.",
"admin.image.publicLinkTitle": "Public Link Salt:",
"admin.image.shareDescription": "Allow users to share public links to files and images.",
@@ -4191,7 +4187,6 @@
"apps.suggestion.no_dynamic": "No data was returned for dynamic suggestions",
"apps.suggestion.no_static": "No matching options.",
"apps.suggestion.no_suggestion": "No matching suggestions.",
"atmos/camo": "atmos/camo",
"attachment.footerIconAltText": "Attachment footer icon",
"attachment.imageAltText": "Attachment image",
"audit_table.accountActive": "Account activated",
-2
View File
@@ -1027,8 +1027,6 @@ export type GuestAccountsSettings = {
export type ImageProxySettings = {
Enable: boolean;
ImageProxyType: string;
RemoteImageProxyURL: string;
RemoteImageProxyOptions: string;
};
export type CloudSettings = {