Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfixing #207

Merged
merged 5 commits into from
May 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
6 changes: 5 additions & 1 deletion muxrpc/handlers/tunnel/server/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
6 changes: 0 additions & 6 deletions muxrpc/handlers/tunnel/server/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion web/handlers/admin/aliases.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
39 changes: 35 additions & 4 deletions web/handlers/admin/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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) {
Expand All @@ -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

Expand Down
88 changes: 88 additions & 0 deletions web/handlers/notices_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -131,3 +132,90 @@ 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")

// 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.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, "ErrorNotAuthorized")

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

}
10 changes: 9 additions & 1 deletion web/handlers/setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package handlers
import (
"bytes"
"net/http"
"net/url"
"os"
"path/filepath"
"testing"
Expand Down Expand Up @@ -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()

Expand Down
3 changes: 3 additions & 0 deletions web/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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{}
Expand Down