From 2ecf61f1235f99c7dca21044d230bffbb0e7e7b5 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 10 May 2021 08:01:09 +0200 Subject: [PATCH 1/5] don't allow making tunnels to initiator --- muxrpc/handlers/tunnel/server/connect.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/muxrpc/handlers/tunnel/server/connect.go b/muxrpc/handlers/tunnel/server/connect.go index cb226cbb..c7465c3b 100644 --- a/muxrpc/handlers/tunnel/server/connect.go +++ b/muxrpc/handlers/tunnel/server/connect.go @@ -71,8 +71,12 @@ func (h connectHandler) HandleDuplex(ctx context.Context, req *muxrpc.Request, p return err } - // see if we have and endpoint for the target + // make sure they dont want to connect to themselves + if caller.Equal(&arg.Target) { + return fmt.Errorf("can't connect to self") + } + // see if we have and endpoint for the target edp, has := h.state.Has(arg.Target) if !has { return fmt.Errorf("could not connect to:%s", arg.Target.Ref()) From 800023b9fc9c0e98260763527fd0b7f85242f94a Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 10 May 2021 08:01:44 +0200 Subject: [PATCH 2/5] update muxrpc --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 7100ab3a..ee40b1d1 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/volatiletech/sqlboiler-sqlite3 v0.0.0-20210314195744-a1c697a68aef // indirect github.com/volatiletech/sqlboiler/v4 v4.5.0 github.com/volatiletech/strmangle v0.0.1 - go.cryptoscope.co/muxrpc/v2 v2.0.2 + go.cryptoscope.co/muxrpc/v2 v2.0.4 go.cryptoscope.co/netwrap v0.1.1 go.cryptoscope.co/secretstream v1.2.2 go.mindeco.de v1.11.0 diff --git a/go.sum b/go.sum index 1dfd0dda..d06888f9 100644 --- a/go.sum +++ b/go.sum @@ -501,6 +501,8 @@ go.cryptoscope.co/muxrpc/v2 v2.0.1 h1:U1dS1dsFk7/LygH8o2qsWy8dHB5g3YeLtm0lsN7c1C go.cryptoscope.co/muxrpc/v2 v2.0.1/go.mod h1:MgaeojIkWY3lLuoNw1mlMT3b3jiZwOj/fgsoGZp/VNA= go.cryptoscope.co/muxrpc/v2 v2.0.2 h1:UdlGHY+EEYZpJz5HWnWz0r34pULYxJHfFTeqLvv+7sA= go.cryptoscope.co/muxrpc/v2 v2.0.2/go.mod h1:MgaeojIkWY3lLuoNw1mlMT3b3jiZwOj/fgsoGZp/VNA= +go.cryptoscope.co/muxrpc/v2 v2.0.4 h1:NLN//zPt9UKFelnPNBh3fefrQ/TFylCflPZhKiDtK3U= +go.cryptoscope.co/muxrpc/v2 v2.0.4/go.mod h1:MgaeojIkWY3lLuoNw1mlMT3b3jiZwOj/fgsoGZp/VNA= go.cryptoscope.co/netwrap v0.1.0/go.mod h1:7zcYswCa4CT+ct54e9uH9+IIbYYETEMHKDNpzl8Ukew= go.cryptoscope.co/netwrap v0.1.1 h1:JLzzGKEvrUrkKzu3iM0DhpHmt+L/gYqmpcf1lJMUyFs= go.cryptoscope.co/netwrap v0.1.1/go.mod h1:7zcYswCa4CT+ct54e9uH9+IIbYYETEMHKDNpzl8Ukew= From 691f0e75b942e34eeb13d14f374c52133cdf786d Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 10 May 2021 08:41:57 +0200 Subject: [PATCH 3/5] add test for notice creation role check updates #176 --- web/handlers/notices_test.go | 74 ++++++++++++++++++++++++++++++++++++ web/handlers/setup_test.go | 10 ++++- web/utils.go | 3 ++ 3 files changed, 86 insertions(+), 1 deletion(-) diff --git a/web/handlers/notices_test.go b/web/handlers/notices_test.go index fc6ef27b..23362ec8 100644 --- a/web/handlers/notices_test.go +++ b/web/handlers/notices_test.go @@ -7,6 +7,7 @@ import ( "github.com/ssb-ngi-pointer/go-ssb-room/roomdb" "github.com/ssb-ngi-pointer/go-ssb-room/web/router" + "github.com/ssb-ngi-pointer/go-ssb-room/web/webassert" "github.com/stretchr/testify/assert" ) @@ -131,3 +132,76 @@ func TestNoticesEditButtonVisible(t *testing.T) { a.EqualValues(1, doc.Find(editButtonSelector).Length()) } + +func TestNoticesCreateOnlyModsAndHigher(t *testing.T) { + ts := setup(t) + a := assert.New(t) + + ts.ConfigDB.GetPrivacyModeReturns(roomdb.ModeCommunity, nil) + + // first, we confirm that we can't access the page when not logged in + draftNotice := ts.URLTo(router.AdminNoticeDraftTranslation, "name", roomdb.NoticeNews) + + doc, resp := ts.Client.GetHTML(draftNotice) + a.Equal(http.StatusForbidden, resp.Code) + + // start preparing the ~login dance~ + // TODO: make this code reusable and share it with the login => /dashboard http:200 test + // TODO: refactor login dance for re-use in testing / across tests + + // when dealing with cookies we also need to have an Host and URL-Scheme + // for the jar to save and load them correctly + formEndpoint := ts.URLTo(router.AuthFallbackLogin) + + doc, resp = ts.Client.GetHTML(formEndpoint) + a.Equal(http.StatusOK, resp.Code) + + csrfCookie := resp.Result().Cookies() + a.True(len(csrfCookie) > 0, "should have one cookie for CSRF protection validation") + + csrfTokenElem := doc.Find(`form#password-fallback input[type="hidden"]`) + a.Equal(1, csrfTokenElem.Length()) + + csrfName, has := csrfTokenElem.Attr("name") + a.True(has, "should have a name attribute") + + csrfValue, has := csrfTokenElem.Attr("value") + a.True(has, "should have value attribute") + + loginVals := url.Values{ + "user": []string{"test"}, + "pass": []string{"test"}, + + csrfName: []string{csrfValue}, + } + + // have the database return okay for any user + testUser := roomdb.Member{ID: 123, Role: roomdb.RoleMember} + ts.AuthFallbackDB.CheckReturns(testUser.ID, nil) + ts.MembersDB.GetByIDReturns(testUser, nil) + + postEndpoint := ts.URLTo(router.AuthFallbackFinalize) + + // construct HTTP Header with Referer and Cookie + var refererHeader = make(http.Header) + refererHeader.Set("Referer", "https://localhost") + ts.Client.SetHeaders(refererHeader) + + resp = ts.Client.PostForm(postEndpoint, loginVals) + a.Equal(http.StatusSeeOther, resp.Code, "wrong HTTP status code for sign in") + + cnt := ts.MembersDB.GetByIDCallCount() + + // now we are logged in, but we shouldn't be able to get the draft page + doc, resp = ts.Client.GetHTML(draftNotice) + a.Equal(http.StatusTemporaryRedirect, resp.Code) + + dashboardURL := ts.URLTo(router.AdminDashboard) + a.Equal(dashboardURL.Path, resp.Header().Get("Location")) + a.True(len(resp.Result().Cookies()) > 0, "got a cookie") + + webassert.HasFlashMessages(t, ts.Client, dashboardURL, "AdminMemberDetailsAliasRevoked") + + a.Equal(cnt+1, ts.MembersDB.GetByIDCallCount()) + +} diff --git a/web/handlers/setup_test.go b/web/handlers/setup_test.go index fba8047e..da9b004c 100644 --- a/web/handlers/setup_test.go +++ b/web/handlers/setup_test.go @@ -5,6 +5,7 @@ package handlers import ( "bytes" "net/http" + "net/url" "os" "path/filepath" "testing" @@ -99,7 +100,14 @@ func setup(t *testing.T) *testSession { // instantiate the urlTo helper (constructs urls for us!) // the cookiejar in our custom http/tester needs a non-empty domain and scheme - ts.URLTo = web.NewURLTo(router.CompleteApp(), ts.NetworkInfo) + mkUrl := web.NewURLTo(router.CompleteApp(), ts.NetworkInfo) + ts.URLTo = func(name string, vals ...interface{}) *url.URL { + u := mkUrl(name, vals...) + if u.Path == "" || u.Host == "" { + t.Fatal("failed to make URL for: ", name, vals) + } + return u + } ts.SignalBridge = signinwithssb.NewSignalBridge() diff --git a/web/utils.go b/web/utils.go index 87575164..47a67748 100644 --- a/web/utils.go +++ b/web/utils.go @@ -21,6 +21,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/roomdb" refs "go.mindeco.de/ssb-refs" ) @@ -69,6 +70,8 @@ func NewURLTo(appRouter *mux.Router, netInfo network.ServerEndpointDetails) URLM params = append(params, strconv.FormatInt(v, 10)) case refs.FeedRef: params = append(params, v.Ref()) + case roomdb.PinnedNoticeName: + params = append(params, string(v)) default: level.Error(l).Log("msg", "invalid param type", "param", fmt.Sprintf("%T", p), "route", routeName) return &url.URL{} From 5385ef65bb86fccb5864e6bd7e3d93d0f80348cb Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 10 May 2021 09:06:47 +0200 Subject: [PATCH 4/5] add middleware to check roles fixes #176 --- web/handlers/admin/aliases.go | 2 +- web/handlers/admin/handler.go | 39 +++++++++++++++++++++++++++++++---- web/handlers/notices_test.go | 24 ++++++++++++++++----- 3 files changed, 55 insertions(+), 10 deletions(-) diff --git a/web/handlers/admin/aliases.go b/web/handlers/admin/aliases.go index 5cfe30cf..fdf17b47 100644 --- a/web/handlers/admin/aliases.go +++ b/web/handlers/admin/aliases.go @@ -92,7 +92,7 @@ func (h aliasesHandler) revoke(rw http.ResponseWriter, req *http.Request) { return } - status := http.StatusTemporaryRedirect + status := http.StatusTemporaryRedirect // TODO: should be SeeOther because it's method POST coming in err = h.db.Revoke(ctx, aliasName) if err != nil { h.flashes.AddError(rw, req, err) diff --git a/web/handlers/admin/handler.go b/web/handlers/admin/handler.go index 80053ba3..02b2db24 100644 --- a/web/handlers/admin/handler.go +++ b/web/handlers/admin/handler.go @@ -22,6 +22,7 @@ import ( "github.com/ssb-ngi-pointer/go-ssb-room/web" weberrors "github.com/ssb-ngi-pointer/go-ssb-room/web/errors" "github.com/ssb-ngi-pointer/go-ssb-room/web/i18n" + "github.com/ssb-ngi-pointer/go-ssb-room/web/members" "github.com/ssb-ngi-pointer/go-ssb-room/web/router" ) @@ -155,10 +156,11 @@ func Handler( noticeDB: dbs.Notices, pinnedDB: dbs.PinnedNotices, } - mux.HandleFunc("/notice/edit", r.HTML("admin/notice-edit.tmpl", nh.edit)) - mux.HandleFunc("/notice/translation/draft", r.HTML("admin/notice-edit.tmpl", nh.draftTranslation)) - mux.HandleFunc("/notice/translation/add", nh.addTranslation) - mux.HandleFunc("/notice/save", nh.save) + onlyModsAndAdmins := checkMemberRole(r.Error, roomdb.RoleModerator, roomdb.RoleAdmin) + mux.Handle("/notice/edit", onlyModsAndAdmins(r.HTML("admin/notice-edit.tmpl", nh.edit))) + mux.Handle("/notice/translation/draft", onlyModsAndAdmins(r.HTML("admin/notice-edit.tmpl", nh.draftTranslation))) + mux.Handle("/notice/translation/add", onlyModsAndAdmins(http.HandlerFunc(nh.addTranslation))) + mux.Handle("/notice/save", onlyModsAndAdmins(http.HandlerFunc(nh.save))) // path:/ matches everything that isn't registerd (ie. its the "Not Found handler") mux.HandleFunc("/", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { @@ -168,6 +170,35 @@ func Handler( return customStripPrefix("/admin", mux) } +func checkMemberRole(eh render.ErrorHandlerFunc, roles ...roomdb.Role) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + currentMember := members.FromContext(req.Context()) + if currentMember == nil { + err := weberrors.ErrRedirect{Path: "/admin/dashboard", Reason: fmt.Errorf("not an member")} + eh(rw, req, http.StatusSeeOther, err) + return + } + + var roleMatched = false + for _, r := range roles { + if currentMember.Role == r { + roleMatched = true + break + } + } + + if !roleMatched { + err := weberrors.ErrRedirect{Path: "/admin/dashboard", Reason: weberrors.ErrNotAuthorized} + eh(rw, req, http.StatusSeeOther, err) + return + } + + next.ServeHTTP(rw, req) + }) + } +} + // how many elements does a paginated page have by default const defaultPageSize = 20 diff --git a/web/handlers/notices_test.go b/web/handlers/notices_test.go index 23362ec8..92966036 100644 --- a/web/handlers/notices_test.go +++ b/web/handlers/notices_test.go @@ -190,18 +190,32 @@ func TestNoticesCreateOnlyModsAndHigher(t *testing.T) { resp = ts.Client.PostForm(postEndpoint, loginVals) a.Equal(http.StatusSeeOther, resp.Code, "wrong HTTP status code for sign in") - cnt := ts.MembersDB.GetByIDCallCount() - // now we are logged in, but we shouldn't be able to get the draft page doc, resp = ts.Client.GetHTML(draftNotice) - a.Equal(http.StatusTemporaryRedirect, resp.Code) + a.Equal(http.StatusSeeOther, resp.Code) dashboardURL := ts.URLTo(router.AdminDashboard) a.Equal(dashboardURL.Path, resp.Header().Get("Location")) a.True(len(resp.Result().Cookies()) > 0, "got a cookie") - webassert.HasFlashMessages(t, ts.Client, dashboardURL, "AdminMemberDetailsAliasRevoked") + webassert.HasFlashMessages(t, ts.Client, dashboardURL, "ErrorNotAuthorized") - a.Equal(cnt+1, ts.MembersDB.GetByIDCallCount()) + // also shouldnt be allowed to save/post + id := []string{"1"} + title := []string{"SSB Breaking News: This Test Is Great"} + content := []string{"Absolutely Thrilling Content"} + language := []string{"en-GB"} + + // POST a correct request to the save handler, and verify that the save was handled using the mock database) + u := ts.URLTo(router.AdminNoticeSave) + formValues := url.Values{"id": id, "title": title, "content": content, "language": language, csrfName: []string{csrfValue}} + resp = ts.Client.PostForm(u, formValues) + a.Equal(http.StatusSeeOther, resp.Code, "POST should work") + a.Equal(0, ts.NoticeDB.SaveCallCount(), "noticedb should not save the notice") + + a.Equal(dashboardURL.Path, resp.Header().Get("Location")) + a.True(len(resp.Result().Cookies()) > 0, "got a cookie") + + webassert.HasFlashMessages(t, ts.Client, dashboardURL, "ErrorNotAuthorized") } From cf3cf1791eb619e592f2eb42195f740683d633dd Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 10 May 2021 13:53:04 +0200 Subject: [PATCH 5/5] reduce tunnel debug logging --- muxrpc/handlers/tunnel/server/state.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/muxrpc/handlers/tunnel/server/state.go b/muxrpc/handlers/tunnel/server/state.go index a508181b..a851d867 100644 --- a/muxrpc/handlers/tunnel/server/state.go +++ b/muxrpc/handlers/tunnel/server/state.go @@ -15,7 +15,6 @@ import ( refs "go.mindeco.de/ssb-refs" kitlog "github.com/go-kit/kit/log" - "github.com/go-kit/kit/log/level" "go.cryptoscope.co/muxrpc/v2" ) @@ -29,18 +28,15 @@ type Handler struct { } func (h *Handler) isRoom(context.Context, *muxrpc.Request) (interface{}, error) { - level.Debug(h.logger).Log("called", "isRoom") return true, nil } func (h *Handler) ping(context.Context, *muxrpc.Request) (interface{}, error) { now := time.Now().UnixNano() / 1000 - level.Debug(h.logger).Log("called", "ping") return now, nil } func (h *Handler) announce(_ context.Context, req *muxrpc.Request) (interface{}, error) { - level.Debug(h.logger).Log("called", "announce") ref, err := network.GetFeedRefFromAddr(req.RemoteAddr()) if err != nil { return nil, err @@ -63,8 +59,6 @@ func (h *Handler) leave(_ context.Context, req *muxrpc.Request) (interface{}, er } func (h *Handler) endpoints(ctx context.Context, req *muxrpc.Request, snk *muxrpc.ByteSink) error { - level.Debug(h.logger).Log("called", "endpoints") - toPeer := newForwarder(snk) // for future updates