Merge pull request #200 from ssb-ngi-pointer/privacy-mode-tests

Privacy mode tests 💯 💯
This commit is contained in:
Alexander Cobleigh 2021-04-26 09:34:55 +02:00 committed by GitHub
commit fa6fa3267e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 226 additions and 23 deletions

View File

@ -84,3 +84,141 @@ func TestConnEstablishmentDeniedKey(t *testing.T) {
cancel() cancel()
} }
func TestConnEstablishmentDenyNonMembersRestrictedRoom(t *testing.T) {
// defer leakcheck.Check(t)
ctx, cancel := context.WithCancel(context.Background())
theBots := createServerAndBots(t, ctx, 2)
r := require.New(t)
a := assert.New(t)
const (
indexSrv = iota
indexA
indexB
)
serv := theBots[indexSrv].srv
botA := theBots[indexA].srv
botB := theBots[indexB].srv
// make sure we are running in restricted mode on the server
err := serv.Config.SetPrivacyMode(ctx, roomdb.ModeRestricted)
r.NoError(err)
// allow A, deny B
theBots[indexSrv].srv.Members.Add(ctx, botA.Whoami(), roomdb.RoleMember)
// since we want to verify denying connections for non members in a restricted room, let us:
// a) NOT add B as a member
// theBots[indexSrv].srv.Members.Add(ctx, botB.Whoami(), roomdb.RoleMember)
// hack: allow bots to dial the server
theBots[indexA].srv.Members.Add(ctx, serv.Whoami(), roomdb.RoleMember)
theBots[indexB].srv.Members.Add(ctx, serv.Whoami(), roomdb.RoleMember)
// dial up B->A and C->A
// should work (we allowed A)
err = botA.Network.Connect(ctx, serv.Network.GetListenAddr())
r.NoError(err, "connect A to the Server")
// shouldn't work (we disallow B in restricted mode)
err = botB.Network.Connect(ctx, serv.Network.GetListenAddr())
r.NoError(err, "connect B to the Server") // we dont see an error because it just establishes the tcp connection
t.Log("letting handshaking settle..")
time.Sleep(1 * time.Second)
var srvWho struct {
ID refs.FeedRef
}
endpointB, has := botB.Network.GetEndpointFor(serv.Whoami())
r.False(has, "botB has an endpoint for the server!")
if endpointB != nil {
a.Nil(endpointB, "should not have an endpoint on B (B is not a member, and the server is restricted)")
err = endpointB.Async(ctx, &srvWho, muxrpc.TypeJSON, muxrpc.Method{"whoami"})
r.Error(err)
t.Log(srvWho.ID.Ref())
}
endpointA, has := botA.Network.GetEndpointFor(serv.Whoami())
r.True(has, "botA has no endpoint for the server")
err = endpointA.Async(ctx, &srvWho, muxrpc.TypeJSON, muxrpc.Method{"whoami"})
r.NoError(err)
t.Log("server whoami:", srvWho.ID.Ref())
a.True(serv.Whoami().Equal(&srvWho.ID))
cancel()
}
func TestConnEstablishmentAllowNonMembersCommunityRoom(t *testing.T) {
// defer leakcheck.Check(t)
ctx, cancel := context.WithCancel(context.Background())
theBots := createServerAndBots(t, ctx, 2)
r := require.New(t)
a := assert.New(t)
const (
indexSrv = iota
indexA
indexB
)
serv := theBots[indexSrv].srv
botA := theBots[indexA].srv
botB := theBots[indexB].srv
// make sure we are running in community mode on the server
err := serv.Config.SetPrivacyMode(ctx, roomdb.ModeCommunity)
r.NoError(err)
pm, err := serv.Config.GetPrivacyMode(ctx)
r.NoError(err)
r.EqualValues(roomdb.ModeCommunity, pm)
// allow A, allow B
theBots[indexSrv].srv.Members.Add(ctx, botA.Whoami(), roomdb.RoleMember)
// since we want to verify allowing connections for non members in a community room, let us:
// a) NOT add B as a member
// theBots[indexSrv].srv.Members.Add(ctx, botB.Whoami(), roomdb.RoleMember)
// hack: allow bots to dial the server
theBots[indexA].srv.Members.Add(ctx, serv.Whoami(), roomdb.RoleMember)
theBots[indexB].srv.Members.Add(ctx, serv.Whoami(), roomdb.RoleMember)
// dial up B->A and C->A
// should work (we allowed A)
err = botA.Network.Connect(ctx, serv.Network.GetListenAddr())
r.NoError(err, "connect A to the Server")
// should work (we don't disallow B in community mode)
err = botB.Network.Connect(ctx, serv.Network.GetListenAddr())
r.NoError(err, "connect B to the Server")
t.Log("letting handshaking settle..")
time.Sleep(1 * time.Second)
var srvWho struct {
ID refs.FeedRef
}
endpointB, has := botB.Network.GetEndpointFor(serv.Whoami())
r.True(has, "botB has no endpoint for the server")
err = endpointB.Async(ctx, &srvWho, muxrpc.TypeJSON, muxrpc.Method{"whoami"})
r.NoError(err)
t.Log(srvWho.ID.Ref())
endpointA, has := botA.Network.GetEndpointFor(serv.Whoami())
r.True(has, "botA has no endpoint for the server")
err = endpointA.Async(ctx, &srvWho, muxrpc.TypeJSON, muxrpc.Method{"whoami"})
r.NoError(err)
t.Log("server whoami:", srvWho.ID.Ref())
a.True(serv.Whoami().Equal(&srvWho.ID))
cancel()
}

View File

@ -35,7 +35,7 @@ func (s *Server) initNetwork() error {
// if privacy mode is restricted, deny connections from non-members // if privacy mode is restricted, deny connections from non-members
if pm == roomdb.ModeRestricted { if pm == roomdb.ModeRestricted {
if _, err := s.authorizer.GetByFeed(s.rootCtx, *remote); err != nil { if _, err := s.Members.GetByFeed(s.rootCtx, *remote); err != nil {
return nil, fmt.Errorf("access restricted to members") return nil, fmt.Errorf("access restricted to members")
} }
} }

View File

@ -61,8 +61,6 @@ type Server struct {
public typemux.HandlerMux public typemux.HandlerMux
master typemux.HandlerMux master typemux.HandlerMux
authorizer roomdb.MembersService
StateManager *roomstate.Manager StateManager *roomstate.Manager
Members roomdb.MembersService Members roomdb.MembersService
@ -89,7 +87,6 @@ func New(
opts ...Option, opts ...Option,
) (*Server, error) { ) (*Server, error) {
var s Server var s Server
s.authorizer = membersdb
s.Members = membersdb s.Members = membersdb
s.DeniedKeys = deniedkeysdb s.DeniedKeys = deniedkeysdb

View File

@ -34,7 +34,7 @@ func (eh *ErrorHandler) SetRenderer(r *render.Renderer) {
func (eh *ErrorHandler) Handle(rw http.ResponseWriter, req *http.Request, code int, err error) { func (eh *ErrorHandler) Handle(rw http.ResponseWriter, req *http.Request, code int, err error) {
log := logging.FromContext(req.Context()) log := logging.FromContext(req.Context())
level.Error(log).Log("event", "handling error","path", req.URL.Path, "err",err) level.Error(log).Log("event", "handling error", "path", req.URL.Path, "err", err)
var redirectErr ErrRedirect var redirectErr ErrRedirect
if errors.As(err, &redirectErr) { if errors.As(err, &redirectErr) {
if redirectErr.Reason != nil { if redirectErr.Reason != nil {

View File

@ -53,7 +53,13 @@ func TestAliasesRevoke(t *testing.T) {
urlRevoke := ts.URLTo(router.AdminAliasesRevoke) urlRevoke := ts.URLTo(router.AdminAliasesRevoke)
overviewURL := ts.URLTo(router.AdminMembersOverview) overviewURL := ts.URLTo(router.AdminMembersOverview)
aliasEntry := roomdb.Alias{
ID: ts.User.ID,
Feed: ts.User.PubKey,
Name: "Blobby",
}
ts.AliasesDB.RevokeReturns(nil) ts.AliasesDB.RevokeReturns(nil)
ts.AliasesDB.ResolveReturns(aliasEntry, nil)
addVals := url.Values{"name": []string{"the-name"}} addVals := url.Values{"name": []string{"the-name"}}
rec := ts.Client.PostForm(urlRevoke, addVals) rec := ts.Client.PostForm(urlRevoke, addVals)

View File

@ -76,11 +76,11 @@ func (h invitesHandler) create(w http.ResponseWriter, req *http.Request) (interf
case roomdb.ModeOpen: case roomdb.ModeOpen:
case roomdb.ModeCommunity: case roomdb.ModeCommunity:
if member.Role == roomdb.RoleUnknown { if member.Role == roomdb.RoleUnknown {
return nil, fmt.Errorf("warning: member with unknown role tried to create an invite") return nil, weberrors.ErrNotAuthorized
} }
case roomdb.ModeRestricted: case roomdb.ModeRestricted:
if member.Role == roomdb.RoleMember || member.Role == roomdb.RoleUnknown { if member.Role == roomdb.RoleMember || member.Role == roomdb.RoleUnknown {
return nil, fmt.Errorf("warning: non-admin/mod user tried to create an invite") return nil, weberrors.ErrNotAuthorized
} }
} }

View File

@ -2,6 +2,7 @@ package admin
import ( import (
"net/http" "net/http"
"net/http/httptest"
"net/url" "net/url"
"testing" "testing"
@ -143,17 +144,28 @@ func TestInvitesCreate(t *testing.T) {
a := assert.New(t) a := assert.New(t)
r := require.New(t) r := require.New(t)
urlRemove := ts.URLTo(router.AdminInvitesCreate) urlCreate := ts.URLTo(router.AdminInvitesCreate)
testInvite := "your-fake-test-invite" testInvite := "your-fake-test-invite"
ts.InvitesDB.CreateReturns(testInvite, nil) ts.InvitesDB.CreateReturns(testInvite, nil)
rec := ts.Client.PostForm(urlRemove, url.Values{}) totalCreateCallCount := 0
a.Equal(http.StatusOK, rec.Code) createInviteShouldWork := func(works bool) *httptest.ResponseRecorder {
rec := ts.Client.PostForm(urlCreate, url.Values{})
if works {
totalCreateCallCount += 1
a.Equal(http.StatusOK, rec.Code)
r.Equal(totalCreateCallCount, ts.InvitesDB.CreateCallCount())
_, userID := ts.InvitesDB.CreateArgsForCall(totalCreateCallCount - 1)
a.EqualValues(ts.User.ID, userID)
} else {
a.Equal(http.StatusForbidden, rec.Code)
r.Equal(totalCreateCallCount, ts.InvitesDB.CreateCallCount())
}
return rec
}
r.Equal(1, ts.InvitesDB.CreateCallCount(), "expected one invites.Create call") rec := createInviteShouldWork(true)
_, userID := ts.InvitesDB.CreateArgsForCall(0)
a.EqualValues(ts.User.ID, userID)
doc, err := goquery.NewDocumentFromReader(rec.Body) doc, err := goquery.NewDocumentFromReader(rec.Body)
require.NoError(t, err, "failed to parse response") require.NoError(t, err, "failed to parse response")
@ -167,4 +179,34 @@ func TestInvitesCreate(t *testing.T) {
shownLink := doc.Find("#invite-facade-link").Text() shownLink := doc.Find("#invite-facade-link").Text()
a.Equal(wantURL.String(), shownLink) a.Equal(wantURL.String(), shownLink)
memberUser := roomdb.Member{
ID: 7331,
Role: roomdb.RoleMember,
PubKey: generatePubKey(),
}
modUser := roomdb.Member{
ID: 9001,
Role: roomdb.RoleModerator,
PubKey: generatePubKey(),
}
adminUser := roomdb.Member{
ID: 1337,
Role: roomdb.RoleAdmin,
PubKey: generatePubKey(),
}
/* test invite creation under various restricted mode with the roles member, mod, admin */
modes := []roomdb.PrivacyMode{roomdb.ModeRestricted, roomdb.ModeCommunity}
for _, mode := range modes {
ts.ConfigDB.GetPrivacyModeReturns(mode, nil)
ts.User = memberUser
// members can only invite in community rooms
createInviteShouldWork(mode == roomdb.ModeCommunity)
// mods & admins can always invite
ts.User = modUser
createInviteShouldWork(true)
ts.User = adminUser
createInviteShouldWork(true)
}
} }

View File

@ -89,7 +89,7 @@ func TestNoticeAddLanguageOnlyAllowsPost(t *testing.T) {
// verify that a GET request is no bueno // verify that a GET request is no bueno
u := ts.URLTo(router.AdminNoticeAddTranslation, "name", roomdb.NoticeNews.String()) u := ts.URLTo(router.AdminNoticeAddTranslation, "name", roomdb.NoticeNews.String())
_, resp := ts.Client.GetHTML(u) _, resp := ts.Client.GetHTML(u)
a.Equal(http.StatusMethodNotAllowed, resp.Code, "GET should not be allowed for this route") a.Equal(http.StatusBadRequest, resp.Code, "GET should not be allowed for this route")
// next up, we verify that a correct POST request actually works: // next up, we verify that a correct POST request actually works:
id := []string{"1"} id := []string{"1"}

View File

@ -56,6 +56,14 @@ type testSession struct {
RoomState *roomstate.Manager RoomState *roomstate.Manager
} }
var pubKeyCount byte
func generatePubKey() refs.FeedRef {
pk := refs.FeedRef{Algo: "ed25519", ID: bytes.Repeat([]byte{pubKeyCount}, 32)}
pubKeyCount++
return pk
}
func newSession(t *testing.T) *testSession { func newSession(t *testing.T) *testSession {
var ts testSession var ts testSession
@ -76,7 +84,7 @@ func newSession(t *testing.T) *testSession {
ts.netInfo = network.ServerEndpointDetails{ ts.netInfo = network.ServerEndpointDetails{
Domain: randutil.String(10), Domain: randutil.String(10),
RoomID: refs.FeedRef{Algo: "ed25519", ID: bytes.Repeat([]byte{0}, 32)}, RoomID: generatePubKey(),
UseSubdomainForAliases: true, UseSubdomainForAliases: true,
} }
@ -97,8 +105,9 @@ func newSession(t *testing.T) *testSession {
// fake user // fake user
ts.User = roomdb.Member{ ts.User = roomdb.Member{
ID: 1234, ID: 1234,
Role: roomdb.RoleModerator, Role: roomdb.RoleModerator,
PubKey: generatePubKey(),
} }
testPath := filepath.Join("testrun", t.Name()) testPath := filepath.Join("testrun", t.Name())
@ -121,10 +130,10 @@ func newSession(t *testing.T) *testSession {
os.MkdirAll(sessionsPath, 0700) os.MkdirAll(sessionsPath, 0700)
fsStore := sessions.NewFilesystemStore(sessionsPath, authKey, encKey) fsStore := sessions.NewFilesystemStore(sessionsPath, authKey, encKey)
// setup rendering
flashHelper := weberrs.NewFlashHelper(fsStore, locHelper) flashHelper := weberrs.NewFlashHelper(fsStore, locHelper)
// setup rendering // template funcs
// TODO: make testing utils and move these there // TODO: make testing utils and move these there
testFuncs := web.TemplateFuncs(router, ts.netInfo) testFuncs := web.TemplateFuncs(router, ts.netInfo)
testFuncs["current_page_is"] = func(routeName string) bool { return true } testFuncs["current_page_is"] = func(routeName string) bool { return true }
@ -143,11 +152,13 @@ func newSession(t *testing.T) *testSession {
testFuncs["list_languages"] = func(*url.URL, string) string { return "" } testFuncs["list_languages"] = func(*url.URL, string) string { return "" }
testFuncs["relative_time"] = func(when time.Time) string { return humanize.Time(when) } testFuncs["relative_time"] = func(when time.Time) string { return humanize.Time(when) }
eh := weberrs.NewErrorHandler(locHelper, flashHelper)
renderOpts := []render.Option{ renderOpts := []render.Option{
render.SetLogger(log), render.SetLogger(log),
render.BaseTemplates("base.tmpl", "menu.tmpl", "flashes.tmpl"), render.BaseTemplates("base.tmpl", "menu.tmpl", "flashes.tmpl"),
render.AddTemplates(append(HTMLTemplates, "error.tmpl")...), render.AddTemplates(append(HTMLTemplates, "error.tmpl")...),
render.ErrorTemplate("error.tmpl"), render.SetErrorHandler(eh.Handle),
render.FuncMap(testFuncs), render.FuncMap(testFuncs),
} }
renderOpts = append(renderOpts, locHelper.GetRenderFuncs()...) renderOpts = append(renderOpts, locHelper.GetRenderFuncs()...)
@ -157,6 +168,8 @@ func newSession(t *testing.T) *testSession {
t.Fatal(errors.Wrap(err, "setup: render init failed")) t.Fatal(errors.Wrap(err, "setup: render init failed"))
} }
eh.SetRenderer(r)
handler := Handler( handler := Handler(
ts.netInfo, ts.netInfo,
r, r,
@ -174,7 +187,7 @@ func newSession(t *testing.T) *testSession {
}, },
) )
handler = members.MiddlewareForTests(ts.User)(handler) handler = members.MiddlewareForTests(&ts.User)(handler)
ts.Mux = http.NewServeMux() ts.Mux = http.NewServeMux()
ts.Mux.Handle("/", handler) ts.Mux.Handle("/", handler)

View File

@ -88,4 +88,11 @@ func TestAliasResolve(t *testing.T) {
a.Equal(testAlias.Feed.Ref(), ar.UserID, "wrong user feed on response") a.Equal(testAlias.Feed.Ref(), ar.UserID, "wrong user feed on response")
a.Equal(ts.NetworkInfo.RoomID.Ref(), ar.RoomID, "wrong room feed on response") a.Equal(ts.NetworkInfo.RoomID.Ref(), ar.RoomID, "wrong room feed on response")
a.Equal(ts.NetworkInfo.MultiserverAddress(), ar.MultiserverAddress) a.Equal(ts.NetworkInfo.MultiserverAddress(), ar.MultiserverAddress)
/* alias resolving should not work for restricted rooms */
ts.ConfigDB.GetPrivacyModeReturns(roomdb.ModeRestricted, nil)
htmlURL, err = routes.Get(router.CompleteAliasResolve).URL("alias", testAlias.Name)
r.NoError(err)
html, resp = ts.Client.GetHTML(htmlURL)
a.Equal(http.StatusInternalServerError, resp.Code)
} }

View File

@ -13,10 +13,10 @@ import (
// This is part of testing.go because we need to use roomMemberContextKey, which shouldn't be exported either. // This is part of testing.go because we need to use roomMemberContextKey, which shouldn't be exported either.
// TODO: could be protected with an extra build tag. // TODO: could be protected with an extra build tag.
// (Sadly +build test does not exist https://github.com/golang/go/issues/21360 ) // (Sadly +build test does not exist https://github.com/golang/go/issues/21360 )
func MiddlewareForTests(m roomdb.Member) func(http.Handler) http.Handler { func MiddlewareForTests(m *roomdb.Member) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
ctx := context.WithValue(req.Context(), roomMemberContextKey, &m) ctx := context.WithValue(req.Context(), roomMemberContextKey, m)
next.ServeHTTP(w, req.WithContext(ctx)) next.ServeHTTP(w, req.WithContext(ctx))
}) })
} }