Merge pull request #100 from ssb-ngi-pointer/privacy-modes

Implement Room Privacy Modes
This commit is contained in:
Henry 2021-04-06 17:34:13 +02:00 committed by GitHub
commit 77b1f5268a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1323 additions and 32 deletions

View File

@ -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
privacyMode = roomdb.ModeUnknown
// helper
log kitlog.Logger
@ -101,6 +104,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 {
pm := roomdb.ParsePrivacyMode(val)
err := pm.IsValid()
if err != nil {
return fmt.Errorf("%s, valid values are open, community, restricted", err)
}
privacyMode = pm
return nil
})
flag.Parse()
if logToFile != "" {
@ -201,13 +214,17 @@ func runroomsrv() error {
r := repo.New(repoDir)
// open the sqlite version of the admindb
// open the sqlite version of the roomdb
db, err := sqlite.Open(r)
if err != nil {
return fmt.Errorf("failed to initiate database: %w", err)
}
bridge := signinwithssb.NewSignalBridge()
// the privacy mode flag was passed => update it in the database
if privacyMode != roomdb.ModeUnknown {
db.Config.SetPrivacyMode(ctx, privacyMode)
}
// create the shs+muxrpc server
roomsrv, err := mksrv.New(
@ -215,6 +232,7 @@ func runroomsrv() error {
db.Aliases,
db.AuthWithSSB,
bridge,
db.Config,
httpsDomain,
opts...)
if err != nil {
@ -264,6 +282,7 @@ func runroomsrv() error {
Aliases: db.Aliases,
AuthFallback: db.AuthFallback,
AuthWithSSB: db.AuthWithSSB,
Config: db.Config,
DeniedKeys: db.DeniedKeys,
Invites: db.Invites,
Notices: db.Notices,

View File

@ -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
}

View File

@ -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

View File

@ -90,7 +90,7 @@ func makeNamedTestBot(t testing.TB, name string, opts []roomsrv.Option) (roomdb.
}
})
sb := signinwithssb.NewSignalBridge()
theBot, err := roomsrv.New(db.Members, db.Aliases, db.AuthWithSSB, sb, name, botOptions...)
theBot, err := roomsrv.New(db.Members, db.Aliases, db.AuthWithSSB, sb, db.Config, name, botOptions...)
r.NoError(err)
return db.Members, theBot
}

View File

@ -123,7 +123,9 @@ func (ts *testSession) startGoServer(
sb := signinwithssb.NewSignalBridge()
authSessionsDB := new(mockdb.FakeAuthWithSSBService)
srv, err := roomsrv.New(membersDB, aliasDB, authSessionsDB, sb, "go.test.room.server", opts...)
fakeConfig := new(mockdb.FakeRoomConfig)
srv, err := roomsrv.New(membersDB, aliasDB, authSessionsDB, sb, fakeConfig, "go.test.room.server", opts...)
r.NoError(err, "failed to init tees a server")
ts.t.Logf("go server: %s", srv.Whoami().Ref())
ts.t.Cleanup(func() {

View File

@ -17,6 +17,11 @@ import (
refs "go.mindeco.de/ssb-refs"
)
type RoomConfig interface {
GetPrivacyMode(context.Context) (PrivacyMode, error)
SetPrivacyMode(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 {
@ -196,3 +201,5 @@ type NoticesService interface {
//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -o mockdb/members.go . MembersService
//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -o mockdb/pages.go . NoticesService
//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -o mockdb/roomconfig.go . RoomConfig

193
roomdb/mockdb/roomconfig.go Normal file
View File

@ -0,0 +1,193 @@
// Code generated by counterfeiter. DO NOT EDIT.
package mockdb
import (
"context"
"sync"
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
)
type FakeRoomConfig struct {
GetPrivacyModeStub func(context.Context) (roomdb.PrivacyMode, error)
getPrivacyModeMutex sync.RWMutex
getPrivacyModeArgsForCall []struct {
arg1 context.Context
}
getPrivacyModeReturns struct {
result1 roomdb.PrivacyMode
result2 error
}
getPrivacyModeReturnsOnCall map[int]struct {
result1 roomdb.PrivacyMode
result2 error
}
SetPrivacyModeStub func(context.Context, roomdb.PrivacyMode) error
setPrivacyModeMutex sync.RWMutex
setPrivacyModeArgsForCall []struct {
arg1 context.Context
arg2 roomdb.PrivacyMode
}
setPrivacyModeReturns struct {
result1 error
}
setPrivacyModeReturnsOnCall map[int]struct {
result1 error
}
invocations map[string][][]interface{}
invocationsMutex sync.RWMutex
}
func (fake *FakeRoomConfig) GetPrivacyMode(arg1 context.Context) (roomdb.PrivacyMode, error) {
fake.getPrivacyModeMutex.Lock()
ret, specificReturn := fake.getPrivacyModeReturnsOnCall[len(fake.getPrivacyModeArgsForCall)]
fake.getPrivacyModeArgsForCall = append(fake.getPrivacyModeArgsForCall, struct {
arg1 context.Context
}{arg1})
stub := fake.GetPrivacyModeStub
fakeReturns := fake.getPrivacyModeReturns
fake.recordInvocation("GetPrivacyMode", []interface{}{arg1})
fake.getPrivacyModeMutex.Unlock()
if stub != nil {
return stub(arg1)
}
if specificReturn {
return ret.result1, ret.result2
}
return fakeReturns.result1, fakeReturns.result2
}
func (fake *FakeRoomConfig) GetPrivacyModeCallCount() int {
fake.getPrivacyModeMutex.RLock()
defer fake.getPrivacyModeMutex.RUnlock()
return len(fake.getPrivacyModeArgsForCall)
}
func (fake *FakeRoomConfig) GetPrivacyModeCalls(stub func(context.Context) (roomdb.PrivacyMode, error)) {
fake.getPrivacyModeMutex.Lock()
defer fake.getPrivacyModeMutex.Unlock()
fake.GetPrivacyModeStub = stub
}
func (fake *FakeRoomConfig) GetPrivacyModeArgsForCall(i int) context.Context {
fake.getPrivacyModeMutex.RLock()
defer fake.getPrivacyModeMutex.RUnlock()
argsForCall := fake.getPrivacyModeArgsForCall[i]
return argsForCall.arg1
}
func (fake *FakeRoomConfig) GetPrivacyModeReturns(result1 roomdb.PrivacyMode, result2 error) {
fake.getPrivacyModeMutex.Lock()
defer fake.getPrivacyModeMutex.Unlock()
fake.GetPrivacyModeStub = nil
fake.getPrivacyModeReturns = struct {
result1 roomdb.PrivacyMode
result2 error
}{result1, result2}
}
func (fake *FakeRoomConfig) GetPrivacyModeReturnsOnCall(i int, result1 roomdb.PrivacyMode, result2 error) {
fake.getPrivacyModeMutex.Lock()
defer fake.getPrivacyModeMutex.Unlock()
fake.GetPrivacyModeStub = nil
if fake.getPrivacyModeReturnsOnCall == nil {
fake.getPrivacyModeReturnsOnCall = make(map[int]struct {
result1 roomdb.PrivacyMode
result2 error
})
}
fake.getPrivacyModeReturnsOnCall[i] = struct {
result1 roomdb.PrivacyMode
result2 error
}{result1, result2}
}
func (fake *FakeRoomConfig) SetPrivacyMode(arg1 context.Context, arg2 roomdb.PrivacyMode) error {
fake.setPrivacyModeMutex.Lock()
ret, specificReturn := fake.setPrivacyModeReturnsOnCall[len(fake.setPrivacyModeArgsForCall)]
fake.setPrivacyModeArgsForCall = append(fake.setPrivacyModeArgsForCall, struct {
arg1 context.Context
arg2 roomdb.PrivacyMode
}{arg1, arg2})
stub := fake.SetPrivacyModeStub
fakeReturns := fake.setPrivacyModeReturns
fake.recordInvocation("SetPrivacyMode", []interface{}{arg1, arg2})
fake.setPrivacyModeMutex.Unlock()
if stub != nil {
return stub(arg1, arg2)
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeRoomConfig) SetPrivacyModeCallCount() int {
fake.setPrivacyModeMutex.RLock()
defer fake.setPrivacyModeMutex.RUnlock()
return len(fake.setPrivacyModeArgsForCall)
}
func (fake *FakeRoomConfig) SetPrivacyModeCalls(stub func(context.Context, roomdb.PrivacyMode) error) {
fake.setPrivacyModeMutex.Lock()
defer fake.setPrivacyModeMutex.Unlock()
fake.SetPrivacyModeStub = stub
}
func (fake *FakeRoomConfig) SetPrivacyModeArgsForCall(i int) (context.Context, roomdb.PrivacyMode) {
fake.setPrivacyModeMutex.RLock()
defer fake.setPrivacyModeMutex.RUnlock()
argsForCall := fake.setPrivacyModeArgsForCall[i]
return argsForCall.arg1, argsForCall.arg2
}
func (fake *FakeRoomConfig) SetPrivacyModeReturns(result1 error) {
fake.setPrivacyModeMutex.Lock()
defer fake.setPrivacyModeMutex.Unlock()
fake.SetPrivacyModeStub = nil
fake.setPrivacyModeReturns = struct {
result1 error
}{result1}
}
func (fake *FakeRoomConfig) SetPrivacyModeReturnsOnCall(i int, result1 error) {
fake.setPrivacyModeMutex.Lock()
defer fake.setPrivacyModeMutex.Unlock()
fake.SetPrivacyModeStub = nil
if fake.setPrivacyModeReturnsOnCall == nil {
fake.setPrivacyModeReturnsOnCall = make(map[int]struct {
result1 error
})
}
fake.setPrivacyModeReturnsOnCall[i] = struct {
result1 error
}{result1}
}
func (fake *FakeRoomConfig) Invocations() map[string][][]interface{} {
fake.invocationsMutex.RLock()
defer fake.invocationsMutex.RUnlock()
fake.getPrivacyModeMutex.RLock()
defer fake.getPrivacyModeMutex.RUnlock()
fake.setPrivacyModeMutex.RLock()
defer fake.setPrivacyModeMutex.RUnlock()
copiedInvocations := map[string][][]interface{}{}
for key, value := range fake.invocations {
copiedInvocations[key] = value
}
return copiedInvocations
}
func (fake *FakeRoomConfig) recordInvocation(key string, args []interface{}) {
fake.invocationsMutex.Lock()
defer fake.invocationsMutex.Unlock()
if fake.invocations == nil {
fake.invocations = map[string][][]interface{}{}
}
if fake.invocations[key] == nil {
fake.invocations[key] = [][]interface{}{}
}
fake.invocations[key] = append(fake.invocations[key], args)
}
var _ roomdb.RoomConfig = new(FakeRoomConfig)

View File

@ -0,0 +1,26 @@
// Code generated by "stringer -type=PrivacyMode"; DO NOT EDIT.
package roomdb
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[ModeUnknown-0]
_ = x[ModeOpen-1]
_ = x[ModeCommunity-2]
_ = x[ModeRestricted-3]
}
const _PrivacyMode_name = "ModeUnknownModeOpenModeCommunityModeRestricted"
var _PrivacyMode_index = [...]uint8{0, 11, 19, 32, 46}
func (i PrivacyMode) String() string {
if i >= PrivacyMode(len(_PrivacyMode_index)-1) {
return "PrivacyMode(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _PrivacyMode_name[_PrivacyMode_index[i]:_PrivacyMode_index[i+1]]
}

4
roomdb/sqlite/generate_models.sh Normal file → Executable file
View File

@ -1,11 +1,13 @@
#!/bin/sh
set -e
# ensure tools are installed
go get github.com/volatiletech/sqlboiler/v4
go get github.com/volatiletech/sqlboiler-sqlite3
# make sure we are in the correct directory
cd "$(dirname $0)"
# run the migrations (creates testrun/TestSchema/roomdb)
go test -run Schema

View File

@ -0,0 +1,19 @@
-- +migrate Up
-- the configuration settings for this room, currently only privacy modes
CREATE TABLE config (
id integer NOT NULL PRIMARY KEY,
privacyMode integer NOT NULL, -- open, community, restricted
CHECK (id == 0) -- should only ever store one row
);
-- the config table will only ever contain one row: the rooms current settings
-- we update that row whenever the config changes.
-- to have something to update, we insert the first and only row at id 0
INSERT INTO config (id, privacyMode) VALUES (
0, -- the constant id we will query
1 -- community is the default mode unless overridden
);
-- +migrate Down
DROP TABLE config;

View File

@ -6,6 +6,7 @@ package models
var TableNames = struct {
SIWSSBSessions string
Aliases string
Config string
DeniedKeys string
FallbackPasswords string
Invites string
@ -16,6 +17,7 @@ var TableNames = struct {
}{
SIWSSBSessions: "SIWSSB_sessions",
Aliases: "aliases",
Config: "config",
DeniedKeys: "denied_keys",
FallbackPasswords: "fallback_passwords",
Invites: "invites",

View File

@ -0,0 +1,779 @@
// Code generated by SQLBoiler 4.5.0 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT.
// This file is meant to be re-generated in place and/or deleted at any time.
package models
import (
"context"
"database/sql"
"fmt"
"reflect"
"strings"
"sync"
"time"
"github.com/friendsofgo/errors"
"github.com/volatiletech/sqlboiler/v4/boil"
"github.com/volatiletech/sqlboiler/v4/queries"
"github.com/volatiletech/sqlboiler/v4/queries/qm"
"github.com/volatiletech/sqlboiler/v4/queries/qmhelper"
"github.com/volatiletech/strmangle"
)
// Config is an object representing the database table.
type Config struct {
ID int64 `boil:"id" json:"id" toml:"id" yaml:"id"`
PrivacyMode int64 `boil:"privacyMode" json:"privacyMode" toml:"privacyMode" yaml:"privacyMode"`
R *configR `boil:"-" json:"-" toml:"-" yaml:"-"`
L configL `boil:"-" json:"-" toml:"-" yaml:"-"`
}
var ConfigColumns = struct {
ID string
PrivacyMode string
}{
ID: "id",
PrivacyMode: "privacyMode",
}
// Generated where
var ConfigWhere = struct {
ID whereHelperint64
PrivacyMode whereHelperint64
}{
ID: whereHelperint64{field: "\"config\".\"id\""},
PrivacyMode: whereHelperint64{field: "\"config\".\"privacyMode\""},
}
// ConfigRels is where relationship names are stored.
var ConfigRels = struct {
}{}
// configR is where relationships are stored.
type configR struct {
}
// NewStruct creates a new relationship struct
func (*configR) NewStruct() *configR {
return &configR{}
}
// configL is where Load methods for each relationship are stored.
type configL struct{}
var (
configAllColumns = []string{"id", "privacyMode"}
configColumnsWithoutDefault = []string{"privacyMode"}
configColumnsWithDefault = []string{"id"}
configPrimaryKeyColumns = []string{"id"}
)
type (
// ConfigSlice is an alias for a slice of pointers to Config.
// This should generally be used opposed to []Config.
ConfigSlice []*Config
// ConfigHook is the signature for custom Config hook methods
ConfigHook func(context.Context, boil.ContextExecutor, *Config) error
configQuery struct {
*queries.Query
}
)
// Cache for insert, update and upsert
var (
configType = reflect.TypeOf(&Config{})
configMapping = queries.MakeStructMapping(configType)
configPrimaryKeyMapping, _ = queries.BindMapping(configType, configMapping, configPrimaryKeyColumns)
configInsertCacheMut sync.RWMutex
configInsertCache = make(map[string]insertCache)
configUpdateCacheMut sync.RWMutex
configUpdateCache = make(map[string]updateCache)
configUpsertCacheMut sync.RWMutex
configUpsertCache = make(map[string]insertCache)
)
var (
// Force time package dependency for automated UpdatedAt/CreatedAt.
_ = time.Second
// Force qmhelper dependency for where clause generation (which doesn't
// always happen)
_ = qmhelper.Where
)
var configBeforeInsertHooks []ConfigHook
var configBeforeUpdateHooks []ConfigHook
var configBeforeDeleteHooks []ConfigHook
var configBeforeUpsertHooks []ConfigHook
var configAfterInsertHooks []ConfigHook
var configAfterSelectHooks []ConfigHook
var configAfterUpdateHooks []ConfigHook
var configAfterDeleteHooks []ConfigHook
var configAfterUpsertHooks []ConfigHook
// doBeforeInsertHooks executes all "before insert" hooks.
func (o *Config) doBeforeInsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) {
if boil.HooksAreSkipped(ctx) {
return nil
}
for _, hook := range configBeforeInsertHooks {
if err := hook(ctx, exec, o); err != nil {
return err
}
}
return nil
}
// doBeforeUpdateHooks executes all "before Update" hooks.
func (o *Config) doBeforeUpdateHooks(ctx context.Context, exec boil.ContextExecutor) (err error) {
if boil.HooksAreSkipped(ctx) {
return nil
}
for _, hook := range configBeforeUpdateHooks {
if err := hook(ctx, exec, o); err != nil {
return err
}
}
return nil
}
// doBeforeDeleteHooks executes all "before Delete" hooks.
func (o *Config) doBeforeDeleteHooks(ctx context.Context, exec boil.ContextExecutor) (err error) {
if boil.HooksAreSkipped(ctx) {
return nil
}
for _, hook := range configBeforeDeleteHooks {
if err := hook(ctx, exec, o); err != nil {
return err
}
}
return nil
}
// doBeforeUpsertHooks executes all "before Upsert" hooks.
func (o *Config) doBeforeUpsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) {
if boil.HooksAreSkipped(ctx) {
return nil
}
for _, hook := range configBeforeUpsertHooks {
if err := hook(ctx, exec, o); err != nil {
return err
}
}
return nil
}
// doAfterInsertHooks executes all "after Insert" hooks.
func (o *Config) doAfterInsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) {
if boil.HooksAreSkipped(ctx) {
return nil
}
for _, hook := range configAfterInsertHooks {
if err := hook(ctx, exec, o); err != nil {
return err
}
}
return nil
}
// doAfterSelectHooks executes all "after Select" hooks.
func (o *Config) doAfterSelectHooks(ctx context.Context, exec boil.ContextExecutor) (err error) {
if boil.HooksAreSkipped(ctx) {
return nil
}
for _, hook := range configAfterSelectHooks {
if err := hook(ctx, exec, o); err != nil {
return err
}
}
return nil
}
// doAfterUpdateHooks executes all "after Update" hooks.
func (o *Config) doAfterUpdateHooks(ctx context.Context, exec boil.ContextExecutor) (err error) {
if boil.HooksAreSkipped(ctx) {
return nil
}
for _, hook := range configAfterUpdateHooks {
if err := hook(ctx, exec, o); err != nil {
return err
}
}
return nil
}
// doAfterDeleteHooks executes all "after Delete" hooks.
func (o *Config) doAfterDeleteHooks(ctx context.Context, exec boil.ContextExecutor) (err error) {
if boil.HooksAreSkipped(ctx) {
return nil
}
for _, hook := range configAfterDeleteHooks {
if err := hook(ctx, exec, o); err != nil {
return err
}
}
return nil
}
// doAfterUpsertHooks executes all "after Upsert" hooks.
func (o *Config) doAfterUpsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) {
if boil.HooksAreSkipped(ctx) {
return nil
}
for _, hook := range configAfterUpsertHooks {
if err := hook(ctx, exec, o); err != nil {
return err
}
}
return nil
}
// AddConfigHook registers your hook function for all future operations.
func AddConfigHook(hookPoint boil.HookPoint, configHook ConfigHook) {
switch hookPoint {
case boil.BeforeInsertHook:
configBeforeInsertHooks = append(configBeforeInsertHooks, configHook)
case boil.BeforeUpdateHook:
configBeforeUpdateHooks = append(configBeforeUpdateHooks, configHook)
case boil.BeforeDeleteHook:
configBeforeDeleteHooks = append(configBeforeDeleteHooks, configHook)
case boil.BeforeUpsertHook:
configBeforeUpsertHooks = append(configBeforeUpsertHooks, configHook)
case boil.AfterInsertHook:
configAfterInsertHooks = append(configAfterInsertHooks, configHook)
case boil.AfterSelectHook:
configAfterSelectHooks = append(configAfterSelectHooks, configHook)
case boil.AfterUpdateHook:
configAfterUpdateHooks = append(configAfterUpdateHooks, configHook)
case boil.AfterDeleteHook:
configAfterDeleteHooks = append(configAfterDeleteHooks, configHook)
case boil.AfterUpsertHook:
configAfterUpsertHooks = append(configAfterUpsertHooks, configHook)
}
}
// One returns a single config record from the query.
func (q configQuery) One(ctx context.Context, exec boil.ContextExecutor) (*Config, error) {
o := &Config{}
queries.SetLimit(q.Query, 1)
err := q.Bind(ctx, exec, o)
if err != nil {
if errors.Cause(err) == sql.ErrNoRows {
return nil, sql.ErrNoRows
}
return nil, errors.Wrap(err, "models: failed to execute a one query for config")
}
if err := o.doAfterSelectHooks(ctx, exec); err != nil {
return o, err
}
return o, nil
}
// All returns all Config records from the query.
func (q configQuery) All(ctx context.Context, exec boil.ContextExecutor) (ConfigSlice, error) {
var o []*Config
err := q.Bind(ctx, exec, &o)
if err != nil {
return nil, errors.Wrap(err, "models: failed to assign all query results to Config slice")
}
if len(configAfterSelectHooks) != 0 {
for _, obj := range o {
if err := obj.doAfterSelectHooks(ctx, exec); err != nil {
return o, err
}
}
}
return o, nil
}
// Count returns the count of all Config records in the query.
func (q configQuery) Count(ctx context.Context, exec boil.ContextExecutor) (int64, error) {
var count int64
queries.SetSelect(q.Query, nil)
queries.SetCount(q.Query)
err := q.Query.QueryRowContext(ctx, exec).Scan(&count)
if err != nil {
return 0, errors.Wrap(err, "models: failed to count config rows")
}
return count, nil
}
// Exists checks if the row exists in the table.
func (q configQuery) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) {
var count int64
queries.SetSelect(q.Query, nil)
queries.SetCount(q.Query)
queries.SetLimit(q.Query, 1)
err := q.Query.QueryRowContext(ctx, exec).Scan(&count)
if err != nil {
return false, errors.Wrap(err, "models: failed to check if config exists")
}
return count > 0, nil
}
// Configs retrieves all the records using an executor.
func Configs(mods ...qm.QueryMod) configQuery {
mods = append(mods, qm.From("\"config\""))
return configQuery{NewQuery(mods...)}
}
// FindConfig retrieves a single record by ID with an executor.
// If selectCols is empty Find will return all columns.
func FindConfig(ctx context.Context, exec boil.ContextExecutor, iD int64, selectCols ...string) (*Config, error) {
configObj := &Config{}
sel := "*"
if len(selectCols) > 0 {
sel = strings.Join(strmangle.IdentQuoteSlice(dialect.LQ, dialect.RQ, selectCols), ",")
}
query := fmt.Sprintf(
"select %s from \"config\" where \"id\"=?", sel,
)
q := queries.Raw(query, iD)
err := q.Bind(ctx, exec, configObj)
if err != nil {
if errors.Cause(err) == sql.ErrNoRows {
return nil, sql.ErrNoRows
}
return nil, errors.Wrap(err, "models: unable to select from config")
}
return configObj, nil
}
// Insert a single record using an executor.
// See boil.Columns.InsertColumnSet documentation to understand column list inference for inserts.
func (o *Config) Insert(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) error {
if o == nil {
return errors.New("models: no config provided for insertion")
}
var err error
if err := o.doBeforeInsertHooks(ctx, exec); err != nil {
return err
}
nzDefaults := queries.NonZeroDefaultSet(configColumnsWithDefault, o)
key := makeCacheKey(columns, nzDefaults)
configInsertCacheMut.RLock()
cache, cached := configInsertCache[key]
configInsertCacheMut.RUnlock()
if !cached {
wl, returnColumns := columns.InsertColumnSet(
configAllColumns,
configColumnsWithDefault,
configColumnsWithoutDefault,
nzDefaults,
)
cache.valueMapping, err = queries.BindMapping(configType, configMapping, wl)
if err != nil {
return err
}
cache.retMapping, err = queries.BindMapping(configType, configMapping, returnColumns)
if err != nil {
return err
}
if len(wl) != 0 {
cache.query = fmt.Sprintf("INSERT INTO \"config\" (\"%s\") %%sVALUES (%s)%%s", strings.Join(wl, "\",\""), strmangle.Placeholders(dialect.UseIndexPlaceholders, len(wl), 1, 1))
} else {
cache.query = "INSERT INTO \"config\" %sDEFAULT VALUES%s"
}
var queryOutput, queryReturning string
if len(cache.retMapping) != 0 {
cache.retQuery = fmt.Sprintf("SELECT \"%s\" FROM \"config\" WHERE %s", strings.Join(returnColumns, "\",\""), strmangle.WhereClause("\"", "\"", 0, configPrimaryKeyColumns))
}
cache.query = fmt.Sprintf(cache.query, queryOutput, queryReturning)
}
value := reflect.Indirect(reflect.ValueOf(o))
vals := queries.ValuesFromMapping(value, cache.valueMapping)
if boil.IsDebug(ctx) {
writer := boil.DebugWriterFrom(ctx)
fmt.Fprintln(writer, cache.query)
fmt.Fprintln(writer, vals)
}
result, err := exec.ExecContext(ctx, cache.query, vals...)
if err != nil {
return errors.Wrap(err, "models: unable to insert into config")
}
var lastID int64
var identifierCols []interface{}
if len(cache.retMapping) == 0 {
goto CacheNoHooks
}
lastID, err = result.LastInsertId()
if err != nil {
return ErrSyncFail
}
o.ID = int64(lastID)
if lastID != 0 && len(cache.retMapping) == 1 && cache.retMapping[0] == configMapping["id"] {
goto CacheNoHooks
}
identifierCols = []interface{}{
o.ID,
}
if boil.IsDebug(ctx) {
writer := boil.DebugWriterFrom(ctx)
fmt.Fprintln(writer, cache.retQuery)
fmt.Fprintln(writer, identifierCols...)
}
err = exec.QueryRowContext(ctx, cache.retQuery, identifierCols...).Scan(queries.PtrsFromMapping(value, cache.retMapping)...)
if err != nil {
return errors.Wrap(err, "models: unable to populate default values for config")
}
CacheNoHooks:
if !cached {
configInsertCacheMut.Lock()
configInsertCache[key] = cache
configInsertCacheMut.Unlock()
}
return o.doAfterInsertHooks(ctx, exec)
}
// Update uses an executor to update the Config.
// See boil.Columns.UpdateColumnSet documentation to understand column list inference for updates.
// Update does not automatically update the record in case of default values. Use .Reload() to refresh the records.
func (o *Config) Update(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) (int64, error) {
var err error
if err = o.doBeforeUpdateHooks(ctx, exec); err != nil {
return 0, err
}
key := makeCacheKey(columns, nil)
configUpdateCacheMut.RLock()
cache, cached := configUpdateCache[key]
configUpdateCacheMut.RUnlock()
if !cached {
wl := columns.UpdateColumnSet(
configAllColumns,
configPrimaryKeyColumns,
)
if !columns.IsWhitelist() {
wl = strmangle.SetComplement(wl, []string{"created_at"})
}
if len(wl) == 0 {
return 0, errors.New("models: unable to update config, could not build whitelist")
}
cache.query = fmt.Sprintf("UPDATE \"config\" SET %s WHERE %s",
strmangle.SetParamNames("\"", "\"", 0, wl),
strmangle.WhereClause("\"", "\"", 0, configPrimaryKeyColumns),
)
cache.valueMapping, err = queries.BindMapping(configType, configMapping, append(wl, configPrimaryKeyColumns...))
if err != nil {
return 0, err
}
}
values := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), cache.valueMapping)
if boil.IsDebug(ctx) {
writer := boil.DebugWriterFrom(ctx)
fmt.Fprintln(writer, cache.query)
fmt.Fprintln(writer, values)
}
var result sql.Result
result, err = exec.ExecContext(ctx, cache.query, values...)
if err != nil {
return 0, errors.Wrap(err, "models: unable to update config row")
}
rowsAff, err := result.RowsAffected()
if err != nil {
return 0, errors.Wrap(err, "models: failed to get rows affected by update for config")
}
if !cached {
configUpdateCacheMut.Lock()
configUpdateCache[key] = cache
configUpdateCacheMut.Unlock()
}
return rowsAff, o.doAfterUpdateHooks(ctx, exec)
}
// UpdateAll updates all rows with the specified column values.
func (q configQuery) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) {
queries.SetUpdate(q.Query, cols)
result, err := q.Query.ExecContext(ctx, exec)
if err != nil {
return 0, errors.Wrap(err, "models: unable to update all for config")
}
rowsAff, err := result.RowsAffected()
if err != nil {
return 0, errors.Wrap(err, "models: unable to retrieve rows affected for config")
}
return rowsAff, nil
}
// UpdateAll updates all rows with the specified column values, using an executor.
func (o ConfigSlice) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) {
ln := int64(len(o))
if ln == 0 {
return 0, nil
}
if len(cols) == 0 {
return 0, errors.New("models: update all requires at least one column argument")
}
colNames := make([]string, len(cols))
args := make([]interface{}, len(cols))
i := 0
for name, value := range cols {
colNames[i] = name
args[i] = value
i++
}
// Append all of the primary key values for each column
for _, obj := range o {
pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), configPrimaryKeyMapping)
args = append(args, pkeyArgs...)
}
sql := fmt.Sprintf("UPDATE \"config\" SET %s WHERE %s",
strmangle.SetParamNames("\"", "\"", 0, colNames),
strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 0, configPrimaryKeyColumns, len(o)))
if boil.IsDebug(ctx) {
writer := boil.DebugWriterFrom(ctx)
fmt.Fprintln(writer, sql)
fmt.Fprintln(writer, args...)
}
result, err := exec.ExecContext(ctx, sql, args...)
if err != nil {
return 0, errors.Wrap(err, "models: unable to update all in config slice")
}
rowsAff, err := result.RowsAffected()
if err != nil {
return 0, errors.Wrap(err, "models: unable to retrieve rows affected all in update all config")
}
return rowsAff, nil
}
// Delete deletes a single Config record with an executor.
// Delete will match against the primary key column to find the record to delete.
func (o *Config) Delete(ctx context.Context, exec boil.ContextExecutor) (int64, error) {
if o == nil {
return 0, errors.New("models: no Config provided for delete")
}
if err := o.doBeforeDeleteHooks(ctx, exec); err != nil {
return 0, err
}
args := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), configPrimaryKeyMapping)
sql := "DELETE FROM \"config\" WHERE \"id\"=?"
if boil.IsDebug(ctx) {
writer := boil.DebugWriterFrom(ctx)
fmt.Fprintln(writer, sql)
fmt.Fprintln(writer, args...)
}
result, err := exec.ExecContext(ctx, sql, args...)
if err != nil {
return 0, errors.Wrap(err, "models: unable to delete from config")
}
rowsAff, err := result.RowsAffected()
if err != nil {
return 0, errors.Wrap(err, "models: failed to get rows affected by delete for config")
}
if err := o.doAfterDeleteHooks(ctx, exec); err != nil {
return 0, err
}
return rowsAff, nil
}
// DeleteAll deletes all matching rows.
func (q configQuery) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) {
if q.Query == nil {
return 0, errors.New("models: no configQuery provided for delete all")
}
queries.SetDelete(q.Query)
result, err := q.Query.ExecContext(ctx, exec)
if err != nil {
return 0, errors.Wrap(err, "models: unable to delete all from config")
}
rowsAff, err := result.RowsAffected()
if err != nil {
return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for config")
}
return rowsAff, nil
}
// DeleteAll deletes all rows in the slice, using an executor.
func (o ConfigSlice) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) {
if len(o) == 0 {
return 0, nil
}
if len(configBeforeDeleteHooks) != 0 {
for _, obj := range o {
if err := obj.doBeforeDeleteHooks(ctx, exec); err != nil {
return 0, err
}
}
}
var args []interface{}
for _, obj := range o {
pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), configPrimaryKeyMapping)
args = append(args, pkeyArgs...)
}
sql := "DELETE FROM \"config\" WHERE " +
strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 0, configPrimaryKeyColumns, len(o))
if boil.IsDebug(ctx) {
writer := boil.DebugWriterFrom(ctx)
fmt.Fprintln(writer, sql)
fmt.Fprintln(writer, args)
}
result, err := exec.ExecContext(ctx, sql, args...)
if err != nil {
return 0, errors.Wrap(err, "models: unable to delete all from config slice")
}
rowsAff, err := result.RowsAffected()
if err != nil {
return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for config")
}
if len(configAfterDeleteHooks) != 0 {
for _, obj := range o {
if err := obj.doAfterDeleteHooks(ctx, exec); err != nil {
return 0, err
}
}
}
return rowsAff, nil
}
// Reload refetches the object from the database
// using the primary keys with an executor.
func (o *Config) Reload(ctx context.Context, exec boil.ContextExecutor) error {
ret, err := FindConfig(ctx, exec, o.ID)
if err != nil {
return err
}
*o = *ret
return nil
}
// ReloadAll refetches every row with matching primary key column values
// and overwrites the original object slice with the newly updated slice.
func (o *ConfigSlice) ReloadAll(ctx context.Context, exec boil.ContextExecutor) error {
if o == nil || len(*o) == 0 {
return nil
}
slice := ConfigSlice{}
var args []interface{}
for _, obj := range *o {
pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), configPrimaryKeyMapping)
args = append(args, pkeyArgs...)
}
sql := "SELECT \"config\".* FROM \"config\" WHERE " +
strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 0, configPrimaryKeyColumns, len(*o))
q := queries.Raw(sql, args...)
err := q.Bind(ctx, exec, &slice)
if err != nil {
return errors.Wrap(err, "models: unable to reload all in ConfigSlice")
}
*o = slice
return nil
}
// ConfigExists checks if the Config row exists.
func ConfigExists(ctx context.Context, exec boil.ContextExecutor, iD int64) (bool, error) {
var exists bool
sql := "select exists(select 1 from \"config\" where \"id\"=? limit 1)"
if boil.IsDebug(ctx) {
writer := boil.DebugWriterFrom(ctx)
fmt.Fprintln(writer, sql)
fmt.Fprintln(writer, iD)
}
row := exec.QueryRowContext(ctx, sql, iD)
err := row.Scan(&exists)
if err != nil {
return false, errors.Wrap(err, "models: unable to check if config exists")
}
return exists, nil
}

View File

@ -1,13 +1,13 @@
// SPDX-License-Identifier: MIT
// Package sqlite implements the SQLite backend of the admindb interfaces.
// Package sqlite implements the SQLite backend of the roomdb interfaces.
//
// It uses sql-migrate (github.com/rubenv/sql-migrate) for it's schema definition and maintainace.
// It uses sql-migrate (github.com/rubenv/sql-migrate) for it's schema definition and maintenance.
// For query construction/ORM it uses SQLBoiler (https://github.com/volatiletech/sqlboiler).
//
// The process of updating the schema and ORM can be summarized as follows:
//
// 1. Make changes to the interfaces in package admindb
// 1. Make changes to the interfaces in package roomdb
// 2. Add a new migration to the 'migrations' folder
// 3. Run 'go test -run Simple', which applies all the migrations
// 4. Run sqlboiler to generate package models
@ -38,6 +38,7 @@ type Database struct {
Members Members
Aliases Aliases
Invites Invites
Config Config
DeniedKeys DeniedKeys
@ -52,7 +53,7 @@ func Open(r repo.Interface) (*Database, error) {
if dir := filepath.Dir(fname); dir != "" {
err := os.MkdirAll(dir, 0700)
if err != nil && !os.IsExist(err) {
return nil, fmt.Errorf("admindb: failed to create folder for database (%q): %w", dir, err)
return nil, fmt.Errorf("roomdb: failed to create folder for database (%q): %w", dir, err)
}
}
@ -61,20 +62,20 @@ func Open(r repo.Interface) (*Database, error) {
db, err := sql.Open("sqlite3", fname)
if err != nil {
return nil, fmt.Errorf("admindb: failed to open sqlite database: %w", err)
return nil, fmt.Errorf("roomdb: failed to open sqlite database: %w", err)
}
if err := db.Ping(); err != nil {
return nil, fmt.Errorf("admindb: sqlite ping failed: %w", err)
return nil, fmt.Errorf("roomdb: sqlite ping failed: %w", err)
}
n, err := migrate.Exec(db, "sqlite3", migrationSource, migrate.Up)
if err != nil {
return nil, fmt.Errorf("admindb: failed to apply database mirations: %w", err)
return nil, fmt.Errorf("roomdb: failed to apply database mirations: %w", err)
}
if n > 0 {
// TODO: hook up logging
log.Printf("admindb: applied %d migrations", n)
log.Printf("roomdb: applied %d migrations", n)
}
if err := deleteConsumedInvites(db); err != nil {
@ -90,19 +91,20 @@ func Open(r repo.Interface) (*Database, error) {
})
if err != nil {
// TODO: hook up logging
log.Printf("admindb: failed to clean up old invites: %s", err.Error())
log.Printf("roomdb: failed to clean up old invites: %s", err.Error())
}
}
}()
ml := Members{db}
admindb := &Database{
roomdb := &Database{
db: db,
Aliases: Aliases{db},
AuthFallback: AuthFallback{db},
AuthWithSSB: AuthWithSSB{db},
Config: Config{db},
DeniedKeys: DeniedKeys{db},
Invites: Invites{db: db, members: ml},
Notices: Notices{db},
@ -110,7 +112,7 @@ func Open(r repo.Interface) (*Database, error) {
PinnedNotices: PinnedNotices{db},
}
return admindb, nil
return roomdb, nil
}
// Close closes the contained sql database object

View File

@ -0,0 +1,77 @@
// SPDX-License-Identifier: MIT
package sqlite
import (
"context"
"database/sql"
"fmt"
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb/sqlite/models"
"github.com/volatiletech/sqlboiler/v4/boil"
)
var _ roomdb.RoomConfig = (*Config)(nil)
// the database will only ever store one row, which contains all the room settings
const configRowID = 0
/* Config basically enables long-term memory for the server when it comes to storing settings. Currently, the only
* stored settings is the privacy mode of the room.
*/
type Config struct {
db *sql.DB
}
func (c Config) GetPrivacyMode(ctx context.Context) (roomdb.PrivacyMode, error) {
config, err := models.FindConfig(ctx, c.db, configRowID)
if err != nil {
return roomdb.ModeUnknown, err
}
// use a type conversion to tell compiler the returned value is a roomdb.PrivacyMode
pm := (roomdb.PrivacyMode)(config.PrivacyMode)
err = pm.IsValid()
if err != nil {
return roomdb.ModeUnknown, err
}
return pm, nil
}
func (c Config) SetPrivacyMode(ctx context.Context, pm roomdb.PrivacyMode) error {
// make sure the privacy mode is an ok value
err := pm.IsValid()
if err != nil {
return err
}
// cblgh: a walkthrough of this step (again, now that i have some actual context) would be real good :)
err = transact(c.db, func(tx *sql.Tx) error {
// get the settings row
config, err := models.FindConfig(ctx, tx, configRowID)
if err != nil {
return err
}
// set the new privacy mode
config.PrivacyMode = int64(pm)
// issue update stmt
rowsAffected, err := config.Update(ctx, tx, boil.Infer())
if err != nil {
return err
}
if rowsAffected == 0 {
return fmt.Errorf("setting privacy mode should have update the settings row, instead 0 rows were updated")
}
return nil
})
if err != nil {
return err
}
return nil // alles gut!!
}

View File

@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT
package sqlite
import (
"context"
"os"
"path/filepath"
"testing"
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
"github.com/ssb-ngi-pointer/go-ssb-room/internal/repo"
"github.com/stretchr/testify/require"
)
func TestRoomConfig(t *testing.T) {
r := require.New(t)
ctx := context.Background()
testRepo := filepath.Join("testrun", t.Name())
os.RemoveAll(testRepo)
tr := repo.New(testRepo)
db, err := Open(tr)
r.NoError(err)
// test setting a valid privacy mode
err = db.Config.SetPrivacyMode(ctx, roomdb.ModeCommunity)
r.NoError(err)
// make sure the mode was set correctly by getting it
pm, err := db.Config.GetPrivacyMode(ctx)
r.NoError(err)
r.Equal(pm, roomdb.ModeCommunity, "privacy mode was unknown")
// test setting an invalid privacy mode
err = db.Config.SetPrivacyMode(ctx, 1337)
r.Error(err)
}

View File

@ -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")

View File

@ -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(

View File

@ -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

View File

@ -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

View File

@ -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
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))

View File

@ -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 {

View File

@ -30,6 +30,7 @@ type testSession struct {
Router *mux.Router
AliasesDB *mockdb.FakeAliasesService
ConfigDB *mockdb.FakeRoomConfig
DeniedKeysDB *mockdb.FakeDeniedKeysService
InvitesDB *mockdb.FakeInvitesService
NoticeDB *mockdb.FakeNoticesService
@ -48,6 +49,9 @@ func newSession(t *testing.T) *testSession {
// fake dbs
ts.AliasesDB = new(mockdb.FakeAliasesService)
ts.ConfigDB = new(mockdb.FakeRoomConfig)
// default mode for all tests
ts.ConfigDB.GetPrivacyModeReturns(roomdb.ModeCommunity, nil)
ts.DeniedKeysDB = new(mockdb.FakeDeniedKeysService)
ts.MembersDB = new(mockdb.FakeMembersService)
ts.PinnedDB = new(mockdb.FakePinnedNoticesService)
@ -103,6 +107,7 @@ func newSession(t *testing.T) *testSession {
ts.RoomState,
Databases{
Aliases: ts.AliasesDB,
Config: ts.ConfigDB,
DeniedKeys: ts.DeniedKeysDB,
Members: ts.MembersDB,
Invites: ts.InvitesDB,

View File

@ -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,10 +35,20 @@ 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)
ar.UpdateRoomInfo(h.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) {
@ -45,7 +56,7 @@ func (a aliasHandler) resolve(rw http.ResponseWriter, req *http.Request) {
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

View File

@ -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,9 @@ func New(
var ih = inviteHandler{
render: r,
invites: dbs.Invites,
config: dbs.Config,
pinnedNotices: dbs.PinnedNotices,
invites: dbs.Invites,
networkInfo: netInfo,
}

View File

@ -29,6 +29,7 @@ type inviteHandler struct {
invites roomdb.InvitesService
pinnedNotices roomdb.PinnedNoticesService
config roomdb.RoomConfig
networkInfo network.ServerEndpointDetails
}

View File

@ -39,6 +39,7 @@ type testSession struct {
AuthFallbackDB *mockdb.FakeAuthFallbackService
AuthWithSSB *mockdb.FakeAuthWithSSBService
AliasesDB *mockdb.FakeAliasesService
ConfigDB *mockdb.FakeRoomConfig
MembersDB *mockdb.FakeMembersService
InvitesDB *mockdb.FakeInvitesService
DeniedKeysDB *mockdb.FakeDeniedKeysService
@ -76,6 +77,9 @@ func setup(t *testing.T) *testSession {
ts.AuthWithSSB = new(mockdb.FakeAuthWithSSBService)
ts.AliasesDB = new(mockdb.FakeAliasesService)
ts.MembersDB = new(mockdb.FakeMembersService)
ts.ConfigDB = new(mockdb.FakeRoomConfig)
// default mode for all tests
ts.ConfigDB.GetPrivacyModeReturns(roomdb.ModeCommunity, nil)
ts.InvitesDB = new(mockdb.FakeInvitesService)
ts.DeniedKeysDB = new(mockdb.FakeDeniedKeysService)
ts.PinnedDB = new(mockdb.FakePinnedNoticesService)
@ -118,6 +122,7 @@ func setup(t *testing.T) *testSession {
Aliases: ts.AliasesDB,
AuthFallback: ts.AuthFallbackDB,
AuthWithSSB: ts.AuthWithSSB,
Config: ts.ConfigDB,
Members: ts.MembersDB,
Invites: ts.InvitesDB,
DeniedKeys: ts.DeniedKeysDB,