Pass remote address in WebSocketMessageHasBeenPosted plugin hook (#27332)

Этот коммит содержится в:
Claudio Costa
2024-06-13 09:01:49 +02:00
коммит произвёл GitHub
родитель d2c3710265
Коммит 4b0ae20ef7
5 изменённых файлов: 110 добавлений и 15 удалений

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

@@ -2033,3 +2033,49 @@ func TestPluginWebSocketSession(t *testing.T) {
_, sessionID := hooks.MessageWillBePosted(nil, nil) _, sessionID := hooks.MessageWillBePosted(nil, nil)
require.Equal(t, sessions[0].Id, sessionID) require.Equal(t, sessions[0].Id, sessionID)
} }
func TestPluginWebSocketRemoteAddress(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
pluginID := "com.mattermost.websocket_remote_address_test"
// Compile plugin
fullPath := filepath.Join(server.GetPackagePath(), "channels", "app", "plugin_api_tests", "manual.test_websocket_remote_address", "main.go")
pluginCode, err := os.ReadFile(fullPath)
require.NoError(t, err)
require.NotEmpty(t, pluginCode)
pluginDir, err := filepath.Abs(*th.App.Config().PluginSettings.Directory)
require.NoError(t, err)
backend := filepath.Join(pluginDir, pluginID, "backend.exe")
utils.CompileGo(t, string(pluginCode), backend)
os.WriteFile(filepath.Join(pluginDir, pluginID, "plugin.json"), []byte(`{"id": "`+pluginID+`", "server": {"executable": "backend.exe"}}`), 0600)
// Activate the plugin
manifest, activated, reterr := th.App.GetPluginsEnvironment().Activate(pluginID)
require.NoError(t, reterr)
require.NotNil(t, manifest)
require.True(t, activated)
// Connect through WebSocket and send a message
reqURL := fmt.Sprintf("ws://localhost:%d", th.Server.ListenAddr.Port)
wsc, err := model.NewWebSocketClient4(reqURL, th.Client.AuthToken)
require.NoError(t, err)
require.NotNil(t, wsc)
wsc.Listen()
defer wsc.Close()
resp := <-wsc.ResponseChannel
require.Equal(t, resp.Status, model.StatusOk)
wsc.SendMessage("custom_action", map[string]any{"value": "test"})
// Verify the remote address has been set correctly. Check plugin code in
// channels/app/plugin_api_tests/manual.test_websocket_remote_address
//
// Here the MessageWillBePosted hook is used purely as a way to
// communicate with the plugin side.
hooks, err := th.App.GetPluginsEnvironment().HooksForPlugin(pluginID)
require.NoError(t, err)
require.NotNil(t, hooks)
_, remoteAddr := hooks.MessageWillBePosted(nil, nil)
require.NotEmpty(t, remoteAddr)
}

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

@@ -44,12 +44,14 @@ func connectWebSocket(c *Context, w http.ResponseWriter, r *http.Request) {
// We initialize webconn with all the necessary data. // We initialize webconn with all the necessary data.
// If the queues are empty, they are initialized in the constructor. // If the queues are empty, they are initialized in the constructor.
cfg := &platform.WebConnConfig{ cfg := &platform.WebConnConfig{
WebSocket: ws, WebSocket: ws,
Session: *c.AppContext.Session(), Session: *c.AppContext.Session(),
TFunc: c.AppContext.T, TFunc: c.AppContext.T,
Locale: "", Locale: "",
Active: true, Active: true,
PostedAck: r.URL.Query().Get(postedAckParam) == "true", PostedAck: r.URL.Query().Get(postedAckParam) == "true",
RemoteAddress: c.AppContext.IPAddress(),
XForwardedFor: c.AppContext.XForwardedFor(),
} }
// The WebSocket upgrade request coming from mobile is missing the // The WebSocket upgrade request coming from mobile is missing the
// user agent so we need to fallback on the session's metadata. // user agent so we need to fallback on the session's metadata.

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

@@ -61,15 +61,17 @@ type pluginWSPostedHook struct {
} }
type WebConnConfig struct { type WebConnConfig struct {
WebSocket *websocket.Conn WebSocket *websocket.Conn
Session model.Session Session model.Session
TFunc i18n.TranslateFunc TFunc i18n.TranslateFunc
Locale string Locale string
ConnectionID string ConnectionID string
Active bool Active bool
ReuseCount int ReuseCount int
OriginClient string OriginClient string
PostedAck bool PostedAck bool
RemoteAddress string
XForwardedFor string
// These aren't necessary to be exported to api layer. // These aren't necessary to be exported to api layer.
sequence int sequence int
@@ -121,6 +123,10 @@ type WebConn struct {
// The client type behind the connection (i.e. web, desktop or mobile) // The client type behind the connection (i.e. web, desktop or mobile)
originClient string originClient string
// The remote address from the original HTTP Upgrade request
remoteAddress string
// The X-Forwarded-For HTTP header value from the origina HTTP Upgrade request
xForwardedFor string
activeChannelID atomic.Value activeChannelID atomic.Value
activeTeamID atomic.Value activeTeamID atomic.Value
@@ -245,6 +251,8 @@ func (ps *PlatformService) NewWebConn(cfg *WebConnConfig, suite SuiteIFace, runn
lastLogTimeSlow: time.Now(), lastLogTimeSlow: time.Now(),
lastLogTimeFull: time.Now(), lastLogTimeFull: time.Now(),
originClient: cfg.OriginClient, originClient: cfg.OriginClient,
remoteAddress: cfg.RemoteAddress,
xForwardedFor: cfg.XForwardedFor,
} }
wc.Active.Store(cfg.Active) wc.Active.Store(cfg.Active)
@@ -479,6 +487,12 @@ func (wc *WebConn) readPump() {
clonedReq.Session.Id = session.Id clonedReq.Session.Id = session.Id
} }
if clonedReq.Data == nil {
clonedReq.Data = map[string]any{}
}
clonedReq.Data[model.WebSocketRemoteAddr] = wc.remoteAddress
clonedReq.Data[model.WebSocketXForwardedFor] = wc.xForwardedFor
wc.pluginPosted <- pluginWSPostedHook{wc.GetConnectionID(), wc.UserId, clonedReq} wc.pluginPosted <- pluginWSPostedHook{wc.GetConnectionID(), wc.UserId, clonedReq}
} }
} }

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

@@ -0,0 +1,28 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package main
import (
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/plugin"
)
type Plugin struct {
plugin.MattermostPlugin
addrCh chan string
}
func (p *Plugin) MessageWillBePosted(_ *plugin.Context, _ *model.Post) (*model.Post, string) {
return nil, <-p.addrCh
}
func (p *Plugin) WebSocketMessageHasBeenPosted(connID, userID string, req *model.WebSocketRequest) {
p.addrCh <- req.Data[model.WebSocketRemoteAddr].(string)
}
func main() {
plugin.ClientMain(&Plugin{
addrCh: make(chan string, 1),
})
}

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

@@ -9,6 +9,11 @@ import (
"github.com/vmihailenco/msgpack/v5" "github.com/vmihailenco/msgpack/v5"
) )
const (
WebSocketRemoteAddr = "remote_addr"
WebSocketXForwardedFor = "x_forwarded_for"
)
// WebSocketRequest represents a request made to the server through a websocket. // WebSocketRequest represents a request made to the server through a websocket.
type WebSocketRequest struct { type WebSocketRequest struct {
// Client-provided fields // Client-provided fields