From ea9b22cfa71e6cda0e3dabe8f904622fcdb23378 Mon Sep 17 00:00:00 2001 From: boreq Date: Mon, 31 Oct 2022 16:42:32 +0100 Subject: [PATCH] Add an RPC endpoint listing all members Currently an RPC endpoint which lists only the connected members is available. A list of all members, even those who are offline, is available only using the web dashboard. This pull request adds an RPC endpoint which lists all members of the room. This new endpoint can be used to augment SSB clients with extra information about the rooms. For example friends who are room members can be displayed in room-related settings or information about shared rooms can be displayed in user profiles. The new endpoint is a source endpoint called ["room", "members"]. Source endpoint was selected to make it possible to return multiple smaller responses instead of one large response - an async endpoint could struggle to return the list of all members in case of larger rooms. Each response carries a list of member objects. Currently the implementation naively returns one member per response message but that can be adjusted in the future. Currently the request takes no arguments but extra options could be added in the future. Currently member objects only have one property: their id. This can be extended in the future. --- muxrpc/handlers/tunnel/server/attendants.go | 2 +- muxrpc/handlers/tunnel/server/members.go | 57 ++++++++++++ muxrpc/handlers/tunnel/server/plugin.go | 3 +- muxrpc/handlers/tunnel/server/state.go | 12 +-- muxrpc/test/go/members_test.go | 96 +++++++++++++++++++++ roomsrv/manifest.go | 1 + 6 files changed, 163 insertions(+), 8 deletions(-) create mode 100644 muxrpc/handlers/tunnel/server/members.go create mode 100644 muxrpc/test/go/members_test.go diff --git a/muxrpc/handlers/tunnel/server/attendants.go b/muxrpc/handlers/tunnel/server/attendants.go index 3a36bba..cd5026b 100644 --- a/muxrpc/handlers/tunnel/server/attendants.go +++ b/muxrpc/handlers/tunnel/server/attendants.go @@ -43,7 +43,7 @@ func (h *Handler) attendants(ctx context.Context, req *muxrpc.Request, snk *muxr } if pm == roomdb.ModeCommunity || pm == roomdb.ModeRestricted { - _, err := h.members.GetByFeed(ctx, *peer) + _, err := h.membersdb.GetByFeed(ctx, *peer) if err != nil { return fmt.Errorf("external user are not allowed to enumerate members") } diff --git a/muxrpc/handlers/tunnel/server/members.go b/muxrpc/handlers/tunnel/server/members.go new file mode 100644 index 0000000..307a285 --- /dev/null +++ b/muxrpc/handlers/tunnel/server/members.go @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: 2021 The NGI Pointer Secure-Scuttlebutt Team of 2020/2021 +// +// SPDX-License-Identifier: MIT + +package server + +import ( + "context" + "encoding/json" + "fmt" + "github.com/ssb-ngi-pointer/go-ssb-room/v2/internal/network" + "github.com/ssb-ngi-pointer/go-ssb-room/v2/roomdb" + "go.cryptoscope.co/muxrpc/v2" + refs "go.mindeco.de/ssb-refs" +) + +type Member struct { + ID refs.FeedRef `json:"id"` +} + +func (h *Handler) members(ctx context.Context, req *muxrpc.Request, snk *muxrpc.ByteSink) error { + peer, err := network.GetFeedRefFromAddr(req.RemoteAddr()) + if err != nil { + return err + } + + pm, err := h.config.GetPrivacyMode(ctx) + if err != nil { + return fmt.Errorf("running with unknown privacy mode: %w", err) + } + + if pm == roomdb.ModeCommunity || pm == roomdb.ModeRestricted { + _, err := h.membersdb.GetByFeed(ctx, *peer) + if err != nil { + return fmt.Errorf("external user are not allowed to list members: %w", err) + } + } + + members, err := h.membersdb.List(ctx) + if err != nil { + return fmt.Errorf("error listing members: %w", err) + } + + snk.SetEncoding(muxrpc.TypeJSON) + + for _, member := range members { + if err = json.NewEncoder(snk).Encode([]Member{ + { + ID: member.PubKey, + }, + }); err != nil { + return fmt.Errorf("encoder error: %w", err) + } + } + + return snk.Close() +} diff --git a/muxrpc/handlers/tunnel/server/plugin.go b/muxrpc/handlers/tunnel/server/plugin.go index b8d86b0..6144b5d 100644 --- a/muxrpc/handlers/tunnel/server/plugin.go +++ b/muxrpc/handlers/tunnel/server/plugin.go @@ -30,7 +30,7 @@ func New(log kitlog.Logger, netInfo network.ServerEndpointDetails, m *roomstate. h.netInfo = netInfo h.logger = log h.state = m - h.members = members + h.membersdb = members h.config = config return h @@ -59,6 +59,7 @@ func (h *Handler) RegisterRoom(mux typemux.HandlerMux) { mux.RegisterAsync(append(namespace, "ping"), typemux.AsyncFunc(h.ping)) mux.RegisterSource(append(namespace, "attendants"), typemux.SourceFunc(h.attendants)) + mux.RegisterSource(append(namespace, "members"), typemux.SourceFunc(h.members)) mux.RegisterDuplex(append(namespace, "connect"), connectHandler{ logger: h.logger, diff --git a/muxrpc/handlers/tunnel/server/state.go b/muxrpc/handlers/tunnel/server/state.go index c2fa60e..59a01f5 100644 --- a/muxrpc/handlers/tunnel/server/state.go +++ b/muxrpc/handlers/tunnel/server/state.go @@ -23,10 +23,10 @@ import ( type Handler struct { logger kitlog.Logger - netInfo network.ServerEndpointDetails - state *roomstate.Manager - members roomdb.MembersService - config roomdb.RoomConfig + netInfo network.ServerEndpointDetails + state *roomstate.Manager + membersdb roomdb.MembersService + config roomdb.RoomConfig } type MetadataReply struct { @@ -50,7 +50,7 @@ func (h *Handler) metadata(ctx context.Context, req *muxrpc.Request) (interface{ reply.Name = h.netInfo.Domain // check if caller is a member - if _, err := h.members.GetByFeed(ctx, *ref); err != nil { + if _, err := h.membersdb.GetByFeed(ctx, *ref); err != nil { if !errors.Is(err, roomdb.ErrNotFound) { return nil, err } @@ -123,7 +123,7 @@ func (h *Handler) endpoints(ctx context.Context, req *muxrpc.Request, snk *muxrp case roomdb.ModeCommunity: fallthrough case roomdb.ModeRestricted: - _, err := h.members.GetByFeed(ctx, *peer) + _, err := h.membersdb.GetByFeed(ctx, *peer) if err != nil { return fmt.Errorf("external user are not allowed to enumerate members") } diff --git a/muxrpc/test/go/members_test.go b/muxrpc/test/go/members_test.go new file mode 100644 index 0000000..d8bff20 --- /dev/null +++ b/muxrpc/test/go/members_test.go @@ -0,0 +1,96 @@ +// SPDX-FileCopyrightText: 2021 The NGI Pointer Secure-Scuttlebutt Team of 2020/2021 +// +// SPDX-License-Identifier: MIT + +package go_test + +import ( + "context" + "encoding/json" + "github.com/ssb-ngi-pointer/go-ssb-room/v2/internal/maybemod/keys" + "github.com/ssb-ngi-pointer/go-ssb-room/v2/muxrpc/handlers/tunnel/server" + "github.com/ssb-ngi-pointer/go-ssb-room/v2/roomdb" + "github.com/ssb-ngi-pointer/go-ssb-room/v2/roomsrv" + "math/rand" + "testing" + "time" + + "github.com/stretchr/testify/require" + "go.cryptoscope.co/muxrpc/v2" +) + +// this tests the new room.members call +func TestRoomMembers(t *testing.T) { + testInit(t) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + r := require.New(t) + + appKey := make([]byte, 32) + rand.Read(appKey) + + netOpts := []roomsrv.Option{ + roomsrv.WithAppKey(appKey), + roomsrv.WithContext(ctx), + } + + session := makeNamedTestBot(t, "srv", ctx, netOpts) + + aliceKey, err := keys.NewKeyPair(nil) + r.NoError(err) + + bobKey, err := keys.NewKeyPair(nil) + r.NoError(err) + + bobSession := makeNamedTestBot(t, "bob", ctx, append(netOpts, + roomsrv.WithKeyPair(bobKey), + )) + + _, err = session.srv.Members.Add(ctx, aliceKey.Feed, roomdb.RoleMember) + r.NoError(err) + + _, err = session.srv.Members.Add(ctx, bobKey.Feed, roomdb.RoleMember) + r.NoError(err) + + // allow bots to dial the remote + // side-effect of re-using a room-server as the client + _, err = bobSession.srv.Members.Add(ctx, session.srv.Whoami(), roomdb.RoleMember) + r.NoError(err) + + err = bobSession.srv.Network.Connect(ctx, session.srv.Network.GetListenAddr()) + r.NoError(err, "connect A to the Server") + + t.Log("letting handshaking settle..") + time.Sleep(1 * time.Second) + + clientForServer, ok := bobSession.srv.Network.GetEndpointFor(session.srv.Whoami()) + r.True(ok) + + src, err := clientForServer.Source(ctx, muxrpc.TypeString, muxrpc.Method{"room", "members"}) + r.NoError(err) + + var responses []server.Member + for src.Next(ctx) { + bytes, err := src.Bytes() + r.NoError(err) + + var members []server.Member + err = json.Unmarshal(bytes, &members) + r.NoError(err) + responses = append(responses, members...) + } + + r.Equal( + []server.Member{ + { + ID: aliceKey.Feed, + }, + { + ID: bobKey.Feed, + }, + }, + responses, + ) +} diff --git a/roomsrv/manifest.go b/roomsrv/manifest.go index 70530a7..61760c3 100644 --- a/roomsrv/manifest.go +++ b/roomsrv/manifest.go @@ -45,6 +45,7 @@ const manifest manifestHandler = ` "connect": "duplex", "attendants": "source", + "members": "source", "metadata": "async", "ping": "sync" },