From faae7c03243e300e38bcdfa3c3f8c24f068afa1f Mon Sep 17 00:00:00 2001 From: cblgh Date: Wed, 24 Mar 2021 10:58:32 +0100 Subject: [PATCH] get started with privacy modes, parse -mode flag --- cmd/server/main.go | 29 +++++++++++++++++++ muxrpc/handlers/tunnel/server/plugin.go | 5 +++- muxrpc/handlers/tunnel/server/state.go | 23 +++++++++++++-- roomdb/interface.go | 4 +++ roomdb/types.go | 37 ++++++++++++++++++++++++- roomsrv/init_handlers.go | 2 ++ roomsrv/init_network.go | 16 +++++++++-- roomsrv/server.go | 3 ++ web/handlers/admin/handler.go | 7 +++-- web/handlers/admin/invites.go | 23 ++++++++++++++- web/handlers/aliases.go | 19 ++++++++++--- web/handlers/http.go | 9 ++++-- web/handlers/invites.go | 1 + 13 files changed, 161 insertions(+), 17 deletions(-) diff --git a/cmd/server/main.go b/cmd/server/main.go index d0945bd..0398acf 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -34,6 +34,7 @@ import ( "github.com/ssb-ngi-pointer/go-ssb-room/internal/network" "github.com/ssb-ngi-pointer/go-ssb-room/internal/repo" "github.com/ssb-ngi-pointer/go-ssb-room/internal/signinwithssb" + "github.com/ssb-ngi-pointer/go-ssb-room/roomdb" "github.com/ssb-ngi-pointer/go-ssb-room/roomdb/sqlite" "github.com/ssb-ngi-pointer/go-ssb-room/roomsrv" mksrv "github.com/ssb-ngi-pointer/go-ssb-room/roomsrv" @@ -53,6 +54,8 @@ var ( logToFile string repoDir string + config roomdb.RoomConfig + // helper log kitlog.Logger @@ -81,9 +84,23 @@ func checkAndLog(err error) { } } +type Config struct { + // open, community, restricted + privacyMode roomdb.PrivacyMode +} + +func (c Config) GetPrivacyMode(ctx context.Context) (roomdb.PrivacyMode, error) { + err := c.privacyMode.IsValid() + if err != nil { + return roomdb.ModeUnknown, err + } + return c.privacyMode, nil +} + func initFlags() { u, err := user.Current() checkFatal(err) + config = Config{privacyMode: roomdb.ModeCommunity} // set default privacy mode to community flag.StringVar(&appKey, "shscap", "1KHLiKZvAvjbY1ziZEHMXawbCEIM6qwjCDm3VYRan/s=", "secret-handshake app-key (or capability)") @@ -101,6 +118,16 @@ func initFlags() { flag.BoolVar(&flagPrintVersion, "version", false, "print version number and build date") + flag.Func("mode", "the privacy mode (values: open, community, restricted) determining room access controls", func(val string) error { + privacyMode := roomdb.ParsePrivacyMode(val) + err := privacyMode.IsValid() + if err != nil { + return fmt.Errorf("%s, valid values are open, community, restricted", err) + } + config = Config{privacyMode: privacyMode} + return nil + }) + flag.Parse() if logToFile != "" { @@ -215,6 +242,7 @@ func runroomsrv() error { db.Aliases, db.AuthWithSSB, bridge, + config, httpsDomain, opts...) if err != nil { @@ -264,6 +292,7 @@ func runroomsrv() error { Aliases: db.Aliases, AuthFallback: db.AuthFallback, AuthWithSSB: db.AuthWithSSB, + Config: config, DeniedKeys: db.DeniedKeys, Invites: db.Invites, Notices: db.Notices, diff --git a/muxrpc/handlers/tunnel/server/plugin.go b/muxrpc/handlers/tunnel/server/plugin.go index 69f474b..6a05df7 100644 --- a/muxrpc/handlers/tunnel/server/plugin.go +++ b/muxrpc/handlers/tunnel/server/plugin.go @@ -7,6 +7,7 @@ import ( "go.cryptoscope.co/muxrpc/v2" "go.cryptoscope.co/muxrpc/v2/typemux" + "github.com/ssb-ngi-pointer/go-ssb-room/roomdb" "github.com/ssb-ngi-pointer/go-ssb-room/roomstate" refs "go.mindeco.de/ssb-refs" ) @@ -22,11 +23,13 @@ import ( } */ -func New(log kitlog.Logger, self refs.FeedRef, m *roomstate.Manager) *Handler { +func New(log kitlog.Logger, self refs.FeedRef, m *roomstate.Manager, members roomdb.MembersService, config roomdb.RoomConfig) *Handler { var h = new(Handler) h.self = self h.logger = log h.state = m + h.members = members + h.config = config return h } diff --git a/muxrpc/handlers/tunnel/server/state.go b/muxrpc/handlers/tunnel/server/state.go index 3cf2b50..2df8ad6 100644 --- a/muxrpc/handlers/tunnel/server/state.go +++ b/muxrpc/handlers/tunnel/server/state.go @@ -5,9 +5,11 @@ package server import ( "context" "encoding/json" + "fmt" "time" "github.com/ssb-ngi-pointer/go-ssb-room/internal/network" + "github.com/ssb-ngi-pointer/go-ssb-room/roomdb" "github.com/ssb-ngi-pointer/go-ssb-room/roomstate" refs "go.mindeco.de/ssb-refs" @@ -20,7 +22,9 @@ type Handler struct { logger kitlog.Logger self refs.FeedRef - state *roomstate.Manager + state *roomstate.Manager + members roomdb.MembersService + config roomdb.RoomConfig } func (h *Handler) isRoom(context.Context, *muxrpc.Request) (interface{}, error) { @@ -57,7 +61,7 @@ func (h *Handler) leave(_ context.Context, req *muxrpc.Request) (interface{}, er return false, nil } -func (h *Handler) endpoints(_ context.Context, req *muxrpc.Request, snk *muxrpc.ByteSink) error { +func (h *Handler) endpoints(ctx context.Context, req *muxrpc.Request, snk *muxrpc.ByteSink) error { level.Debug(h.logger).Log("called", "endpoints") toPeer := newForwarder(snk) @@ -70,6 +74,21 @@ func (h *Handler) endpoints(_ context.Context, req *muxrpc.Request, snk *muxrpc. return err } + pm, err := h.config.GetPrivacyMode(ctx) + if err != nil { + return fmt.Errorf("running with unknown privacy mode") + } + + switch pm { + case roomdb.ModeCommunity: + fallthrough + case roomdb.ModeRestricted: + _, err := h.members.GetByFeed(ctx, *ref) + if err != nil { + return fmt.Errorf("external user are not allowed to enumerate members") + } + } + has := h.state.AlreadyAdded(*ref, req.Endpoint()) if !has { // just send the current state to the new peer diff --git a/roomdb/interface.go b/roomdb/interface.go index eb70e7d..cdf3764 100644 --- a/roomdb/interface.go +++ b/roomdb/interface.go @@ -17,6 +17,10 @@ import ( refs "go.mindeco.de/ssb-refs" ) +type RoomConfig interface { + GetPrivacyMode(context.Context) (PrivacyMode, error) +} + // AuthFallbackService allows password authentication which might be helpful for scenarios // where one lost access to his ssb device or key. type AuthFallbackService interface { diff --git a/roomdb/types.go b/roomdb/types.go index 7becf2f..425abd5 100644 --- a/roomdb/types.go +++ b/roomdb/types.go @@ -33,6 +33,41 @@ type Member struct { PubKey refs.FeedRef } +//go:generate go run golang.org/x/tools/cmd/stringer -type=PrivacyMode + +type PrivacyMode uint + +func (pm PrivacyMode) IsValid() error { + if pm == ModeUnknown || pm > ModeRestricted { + return errors.New("No such privacy mode") + } + return nil +} + +func ParsePrivacyMode(val string) PrivacyMode { + switch val { + case "open": + return ModeOpen + case "community": + return ModeCommunity + case "restricted": + return ModeRestricted + default: + return ModeUnknown + } +} + +// PrivacyMode describes the access mode the room server is currently running under. +// ModeOpen allows anyone to create an room invite +// ModeCommunity restricts invite creation to pre-existing room members (i.e. "internal users") +// ModeRestricted only allows admins and moderators to create room invitations +const ( + ModeUnknown PrivacyMode = iota + ModeOpen + ModeCommunity + ModeRestricted +) + //go:generate go run golang.org/x/tools/cmd/stringer -type=Role // Role describes the authorization level of an internal user (or member). @@ -42,7 +77,7 @@ type Role uint func (r Role) IsValid() error { if r == RoleUnknown { - return errors.New("uknown member role") + return errors.New("unknown member role") } if r > RoleAdmin { return errors.New("invalid member role") diff --git a/roomsrv/init_handlers.go b/roomsrv/init_handlers.go index c86cf67..de01406 100644 --- a/roomsrv/init_handlers.go +++ b/roomsrv/init_handlers.go @@ -23,6 +23,8 @@ func (s *Server) initHandlers() { kitlog.With(s.logger, "unit", "tunnel"), s.Whoami(), s.StateManager, + s.Members, + s.Config, ) aliasHandler := alias.New( diff --git a/roomsrv/init_network.go b/roomsrv/init_network.go index 6019bff..0ebe729 100644 --- a/roomsrv/init_network.go +++ b/roomsrv/init_network.go @@ -9,6 +9,7 @@ import ( "go.cryptoscope.co/muxrpc/v2" "github.com/ssb-ngi-pointer/go-ssb-room/internal/network" + "github.com/ssb-ngi-pointer/go-ssb-room/roomdb" ) // opens the shs listener for TCP connections @@ -27,11 +28,20 @@ func (s *Server) initNetwork() error { return &s.master, nil } - if _, err := s.authorizer.GetByFeed(s.rootCtx, *remote); err == nil { - return &s.public, nil + pm, err := s.Config.GetPrivacyMode(nil) + if err != nil { + return nil, fmt.Errorf("running with unknown privacy mode") } - return nil, fmt.Errorf("not authorized") + // if privacy mode is restricted, deny connections from non-members + if pm == roomdb.ModeRestricted { + if _, err := s.authorizer.GetByFeed(s.rootCtx, *remote); err != nil { + return nil, fmt.Errorf("access restricted to members") + } + } + + // for community + open modes, allow all connections + return &s.public, nil } // tcp+shs diff --git a/roomsrv/server.go b/roomsrv/server.go index cad3233..a68cb51 100644 --- a/roomsrv/server.go +++ b/roomsrv/server.go @@ -70,6 +70,7 @@ type Server struct { authWithSSB roomdb.AuthWithSSBService authWithSSBBridge *signinwithssb.SignalBridge + Config roomdb.RoomConfig } func (s Server) Whoami() refs.FeedRef { @@ -81,6 +82,7 @@ func New( aliasdb roomdb.AliasesService, awsdb roomdb.AuthWithSSBService, bridge *signinwithssb.SignalBridge, + config roomdb.RoomConfig, domainName string, opts ...Option, ) (*Server, error) { @@ -89,6 +91,7 @@ func New( s.Members = membersdb s.Aliases = aliasdb + s.Config = config s.authWithSSB = awsdb s.authWithSSBBridge = bridge diff --git a/web/handlers/admin/handler.go b/web/handlers/admin/handler.go index 3cb9701..f45f4ae 100644 --- a/web/handlers/admin/handler.go +++ b/web/handlers/admin/handler.go @@ -44,6 +44,7 @@ var HTMLTemplates = []string{ // Databases is an option struct that encapsualtes the required database services type Databases struct { Aliases roomdb.AliasesService + Config roomdb.RoomConfig // cblgh: kind of confusing that we have two identically named structs, in different http handler contexts? DeniedKeys roomdb.DeniedKeysService Invites roomdb.InvitesService Notices roomdb.NoticesService @@ -117,11 +118,13 @@ func Handler( mux.HandleFunc("/members/remove", mh.remove) var ih = invitesHandler{ - r: r, - db: dbs.Invites, + r: r, + db: dbs.Invites, + config: dbs.Config, domainName: domainName, } + mux.HandleFunc("/invites", r.HTML("admin/invite-list.tmpl", ih.overview)) mux.HandleFunc("/invites/create", r.HTML("admin/invite-created.tmpl", ih.create)) mux.HandleFunc("/invites/revoke/confirm", r.HTML("admin/invite-revoke-confirm.tmpl", ih.revokeConfirm)) diff --git a/web/handlers/admin/invites.go b/web/handlers/admin/invites.go index c715b63..c3e1615 100644 --- a/web/handlers/admin/invites.go +++ b/web/handlers/admin/invites.go @@ -19,7 +19,8 @@ import ( type invitesHandler struct { r *render.Renderer - db roomdb.InvitesService + db roomdb.InvitesService + config roomdb.RoomConfig domainName string } @@ -58,6 +59,26 @@ func (h invitesHandler) create(w http.ResponseWriter, req *http.Request) (interf if member == nil { return nil, fmt.Errorf("warning: no user session for elevated access request") } + pm, err := h.config.GetPrivacyMode(req.Context()) + if err != nil { + return nil, err + } + /* We want to check: + * 1. the room's privacy mode + * 2. the role of the member trying to create the invite + * and deny unallowed requests (e.g. member creating invite in ModeRestricted) + */ + switch pm { + case roomdb.ModeOpen: + case roomdb.ModeCommunity: + if member.Role == roomdb.RoleUnknown { + return nil, fmt.Errorf("warning: member with unknown role tried to create an invite") + } + case roomdb.ModeRestricted: + if member.Role == roomdb.RoleMember || member.Role == roomdb.RoleUnknown { + return nil, fmt.Errorf("warning: non-admin/mod user tried to create an invite") + } + } token, err := h.db.Create(req.Context(), member.ID) if err != nil { diff --git a/web/handlers/aliases.go b/web/handlers/aliases.go index caf3ae4..3bde986 100644 --- a/web/handlers/aliases.go +++ b/web/handlers/aliases.go @@ -21,12 +21,13 @@ import ( type aliasHandler struct { r *render.Renderer - db roomdb.AliasesService + db roomdb.AliasesService + config roomdb.RoomConfig roomEndpoint network.ServerEndpointDetails } -func (a aliasHandler) resolve(rw http.ResponseWriter, req *http.Request) { +func (h aliasHandler) resolve(rw http.ResponseWriter, req *http.Request) { respEncoding := req.URL.Query().Get("encoding") var ar aliasResponder @@ -34,18 +35,28 @@ func (a aliasHandler) resolve(rw http.ResponseWriter, req *http.Request) { case "json": ar = newAliasJSONResponder(rw) default: - ar = newAliasHTMLResponder(a.r, rw, req) + ar = newAliasHTMLResponder(h.r, rw, req) } ar.UpdateRoomInfo(a.roomEndpoint) + pm, err := h.config.GetPrivacyMode(req.Context()) + if err != nil { + ar.SendError(fmt.Errorf("room is running an unknown privacy mode")) + return + } + if pm == roomdb.ModeRestricted { + ar.SendError(fmt.Errorf("this room is restricted, alias resolving is turned off")) + return + } + name := mux.Vars(req)["alias"] if name == "" && !aliases.IsValid(name) { ar.SendError(fmt.Errorf("invalid alias")) return } - alias, err := a.db.Resolve(req.Context(), name) + alias, err := h.db.Resolve(req.Context(), name) if err != nil { ar.SendError(fmt.Errorf("aliases: failed to resolve name %q: %w", name, err)) return diff --git a/web/handlers/http.go b/web/handlers/http.go index 7ffb8b0..feaff50 100644 --- a/web/handlers/http.go +++ b/web/handlers/http.go @@ -53,6 +53,7 @@ type Databases struct { Aliases roomdb.AliasesService AuthFallback roomdb.AuthFallbackService AuthWithSSB roomdb.AuthWithSSBService + Config roomdb.RoomConfig DeniedKeys roomdb.DeniedKeysService Invites roomdb.InvitesService Notices roomdb.NoticesService @@ -260,6 +261,7 @@ func New( roomState, admin.Databases{ Aliases: dbs.Aliases, + Config: dbs.Config, DeniedKeys: dbs.DeniedKeys, Invites: dbs.Invites, Notices: dbs.Notices, @@ -294,7 +296,8 @@ func New( var ah = aliasHandler{ r: r, - db: dbs.Aliases, + db: dbs.Aliases, + config: dbs.Config, roomEndpoint: netInfo, } @@ -303,8 +306,8 @@ func New( var ih = inviteHandler{ render: r, - invites: dbs.Invites, - pinnedNotices: dbs.PinnedNotices, + config: dbs.Config, + invites: dbs.Invites, networkInfo: netInfo, } diff --git a/web/handlers/invites.go b/web/handlers/invites.go index f0685fd..c7e7c08 100644 --- a/web/handlers/invites.go +++ b/web/handlers/invites.go @@ -29,6 +29,7 @@ type inviteHandler struct { invites roomdb.InvitesService pinnedNotices roomdb.PinnedNoticesService + config roomdb.RoomConfig networkInfo network.ServerEndpointDetails }