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" },