From 2c9fdcb98e2cd26c70c2a8b096af99e597af1917 Mon Sep 17 00:00:00 2001 From: cblgh Date: Wed, 21 Apr 2021 13:52:44 +0200 Subject: [PATCH 1/2] update HTML UI to reflect role access restrictions * disable ui if user is unelevated * disable revoke button if unelevated and not own invite * improve styling of disabled elements * remove revoke if alias not made my current user --- web/handlers/admin/setup_test.go | 4 ++ web/handlers/http.go | 25 ++++++++ web/handlers/notices_test.go | 2 +- web/i18n/defaults/active.de.toml | 1 + web/i18n/defaults/active.en.toml | 1 + web/members/helper.go | 20 +++++- web/templates/admin/denied-keys.tmpl | 19 ++++-- web/templates/admin/invite-list.tmpl | 25 ++++++-- web/templates/admin/member-list.tmpl | 15 +++-- web/templates/admin/member.tmpl | 94 ++++++++++++++++------------ web/templates/admin/settings.tmpl | 12 ++++ web/templates/landing/index.tmpl | 4 +- web/templates/notice/list.tmpl | 4 +- web/templates/notice/show.tmpl | 4 +- 14 files changed, 167 insertions(+), 63 deletions(-) diff --git a/web/handlers/admin/setup_test.go b/web/handlers/admin/setup_test.go index c8bb2f8..eeac3fb 100644 --- a/web/handlers/admin/setup_test.go +++ b/web/handlers/admin/setup_test.go @@ -133,6 +133,10 @@ func newSession(t *testing.T) *testSession { testFuncs["urlToNotice"] = func(name string) string { return "" } testFuncs["language_count"] = func() int { return 1 } testFuncs["list_languages"] = func(*url.URL, string) string { return "" } + testFuncs["member_is_elevated"] = func() bool { return true } + testFuncs["member_is_admin"] = func() bool { return true } + testFuncs["member_can_invite"] = func() bool { return true } + testFuncs["list_languages"] = func(*url.URL, string) string { return "" } testFuncs["relative_time"] = func(when time.Time) string { return humanize.Time(when) } renderOpts := []render.Option{ diff --git a/web/handlers/http.go b/web/handlers/http.go index ed6fefd..5233860 100644 --- a/web/handlers/http.go +++ b/web/handlers/http.go @@ -123,6 +123,31 @@ func New( } }), + render.InjectTemplateFunc("member_can_invite", func(r *http.Request) interface{} { + return func() (bool, error) { + member := members.FromContext(r.Context()) + if member == nil { + return false, nil + } + + pm, err := dbs.Config.GetPrivacyMode(r.Context()) + if err != nil { + return false, err + } + + switch pm { + case roomdb.ModeOpen: + return true, nil + case roomdb.ModeCommunity: + return member.Role > roomdb.RoleUnknown && member.Role <= roomdb.RoleAdmin, nil + case roomdb.ModeRestricted: + return member.Role == roomdb.RoleAdmin || member.Role == roomdb.RoleModerator, nil + default: + return false, nil + } + } + }), + render.InjectTemplateFunc("language_count", func(r *http.Request) interface{} { return func() int { return len(locHelper.ListLanguages()) diff --git a/web/handlers/notices_test.go b/web/handlers/notices_test.go index 3208bf8..fc6ef27 100644 --- a/web/handlers/notices_test.go +++ b/web/handlers/notices_test.go @@ -109,7 +109,7 @@ func TestNoticesEditButtonVisible(t *testing.T) { } // have the database return okay for any user - testUser := roomdb.Member{ID: 23} + testUser := roomdb.Member{ID: 23, Role: roomdb.RoleAdmin} ts.AuthFallbackDB.CheckReturns(testUser.ID, nil) ts.MembersDB.GetByIDReturns(testUser, nil) diff --git a/web/i18n/defaults/active.de.toml b/web/i18n/defaults/active.de.toml index 6bfea8d..2490c94 100644 --- a/web/i18n/defaults/active.de.toml +++ b/web/i18n/defaults/active.de.toml @@ -117,6 +117,7 @@ AdminMemberDetailsRemove = "Mitglied entfernen" AdminMemberAdded = "Mitglied erfolgreich hinzugefügt." AdminMemberUpdated = "Mitglied aktualisiert." AdminMemberRemoved = "Mitglied entfernt." +AdminAddNewMemberTitle = "Add a new member" AdminAliasesRevoke = "Widerrufen" AdminAliasesRevokeConfirmTitle = "Alias ​​widerrufen" diff --git a/web/i18n/defaults/active.en.toml b/web/i18n/defaults/active.en.toml index cd222a3..40c7f2b 100644 --- a/web/i18n/defaults/active.en.toml +++ b/web/i18n/defaults/active.en.toml @@ -124,6 +124,7 @@ AdminMemberDetailsRemove = "Remove member" AdminMemberAdded = "Member added successfully." AdminMemberUpdated = "Member updated." AdminMemberRemoved = "Member removed." +AdminAddNewMemberTitle = "Add a new member" AdminAliasesRevoke = "Revoke" AdminAliasesRevokeConfirmTitle = "Revoke Alias" diff --git a/web/members/helper.go b/web/members/helper.go index 1e2bf98..8677d53 100644 --- a/web/members/helper.go +++ b/web/members/helper.go @@ -92,13 +92,15 @@ func ContextInjecter(mdb roomdb.MembersService, withPassword *auth.Handler, with } // TemplateHelpers returns functions to be used with the go.mindeco.de/http/render package. -// Each has to return a function twice because the first is evaluated with the request before it gets passed onto html/template's FuncMap. +// Each helper has to return a function twice because the first is evaluated with the request before it gets passed onto html/template's FuncMap. // -// {{ is_logged_in }} returns true or false depending if the user is logged in +// {{ is_logged_in }} returns true or false depending on if the user is logged in // // {{ member_has_role "string" }} returns a boolean which confrms wether the member has a certain role (RoleMemeber, RoleAdmin, etc) // // {{ member_is_admin }} is a shortcut for {{ member_has_role "RoleAdmin" }} +// +// {{ member_is_admin }} is a shortcut for {{ or member_has_role "RoleAdmin" member_has_role "RoleModerator"}} func TemplateHelpers() []render.Option { return []render.Option{ @@ -143,5 +145,19 @@ func TemplateHelpers() []render.Option { return member.Role == roomdb.RoleAdmin } }), + + // shorthand for is admin || mod (used for editing notices, managing users, managing aliases) + render.InjectTemplateFunc("member_is_elevated", func(r *http.Request) interface{} { + no := func() bool { return false } + + member := FromContext(r.Context()) + if member == nil { + return no + } + + return func() bool { + return member.Role == roomdb.RoleAdmin || member.Role == roomdb.RoleModerator + } + }), } } diff --git a/web/templates/admin/denied-keys.tmpl b/web/templates/admin/denied-keys.tmpl index 803b757..175bea9 100644 --- a/web/templates/admin/denied-keys.tmpl +++ b/web/templates/admin/denied-keys.tmpl @@ -22,21 +22,32 @@ {{ .csrfField }}
@@ -98,4 +109,4 @@ {{end}} {{end}} -{{end}} \ No newline at end of file +{{end}} diff --git a/web/templates/admin/invite-list.tmpl b/web/templates/admin/invite-list.tmpl index 928389a..7f60984 100644 --- a/web/templates/admin/invite-list.tmpl +++ b/web/templates/admin/invite-list.tmpl @@ -28,8 +28,15 @@ > {{ .csrfField }} @@ -44,6 +51,8 @@ {{range .Entries}} + {{ $user := is_logged_in }} + {{$hasCreatedInvite := eq $user.PubKey.Ref .CreatedBy.PubKey.Ref }} {{$creator := .CreatedBy.PubKey.Ref}} {{$creatorIsAlias := false}} {{range $index, $alias := .CreatedBy.Aliases}} @@ -67,10 +76,12 @@ {{end}} + {{ if or member_is_elevated $hasCreatedInvite }} {{i18n "AdminInviteRevoke"}} + {{ end }} @@ -84,10 +95,12 @@ >{{$creator}}, {{human_time .CreatedAt}} {{end}} - {{i18n "AdminInviteRevoke"}} + {{ if or member_is_elevated $hasCreatedInvite }} + {{i18n "AdminInviteRevoke"}} + {{ end }} {{end}} @@ -134,4 +147,4 @@ {{end}} {{end}} -{{end}} \ No newline at end of file +{{end}} diff --git a/web/templates/admin/member-list.tmpl b/web/templates/admin/member-list.tmpl index 44e7465..9fa5415 100644 --- a/web/templates/admin/member-list.tmpl +++ b/web/templates/admin/member-list.tmpl @@ -14,16 +14,23 @@ method="POST" > {{ .csrfField }} - +
+ class="w-8/12 self-stretch shadow rounded border border-transparent h-10 p-1 pl-4 font-mono truncate + text-purple-600 focus:outline-none focus:ring-2 focus:ring-purple-400 focus:border-transparent + {{ if member_is_elevated }} {{ else }} shadow ring-1 ring-gray-300 opacity-50 bg-gray-200 cursor-not-allowed {{ end }} + ">
@@ -106,4 +113,4 @@ {{end}} {{end}} -{{end}} \ No newline at end of file +{{end}} diff --git a/web/templates/admin/member.tmpl b/web/templates/admin/member.tmpl index b89301e..d3d923e 100644 --- a/web/templates/admin/member.tmpl +++ b/web/templates/admin/member.tmpl @@ -8,45 +8,55 @@

{{.Member.PubKey.Ref}}

-
- + {{ $user := is_logged_in }} + {{ $aliasBelongsToUser := eq $user.PubKey.Ref .Member.PubKey.Ref }} + {{ if member_is_elevated }} +
+ + {{range $.AllRoles}} + {{if eq . $.Member.Role}} + {{i18n .String}} + {{end}} + {{end}} + + +
+ {{range $.AllRoles}} + {{if ne . $.Member.Role}} +
+ {{$.csrfField}} + + +
+ {{else}} +
+
+ + + +
+ {{i18n .String}} +
+ {{end}} + {{end}} +
+
+ {{ else }} {{range $.AllRoles}} {{if eq . $.Member.Role}} - {{i18n .String}} - {{end}} +

{{i18n .String}}

+ {{end}} {{end}} -
- -
- {{range $.AllRoles}} - {{if ne . $.Member.Role}} -
- {{$.csrfField}} - - -
- {{else}} -
-
- - - -
- {{i18n .String}} -
- {{end}} - {{end}} -
-
+ {{ end }} {{$aliasCount := len .Member.Aliases}} {{if gt $aliasCount 0}} @@ -59,19 +69,23 @@ >{{.Name}} - ({{i18n "AdminMemberDetailsAliasRevoke"}}) + {{end}} {{end}} {{end}} + {{ if member_is_elevated }} {{i18n "AdminMemberDetailsRemove"}} + {{ end }} -{{end}} \ No newline at end of file +{{end}} diff --git a/web/templates/admin/settings.tmpl b/web/templates/admin/settings.tmpl index 3fb1171..388acb8 100644 --- a/web/templates/admin/settings.tmpl +++ b/web/templates/admin/settings.tmpl @@ -11,6 +11,7 @@ {{ i18n "RoomsSpecification" }}.

{{ i18n "SetPrivacyModeTitle" }}

+ {{ if member_is_admin }}
{{ i18n .CurrentMode.String }} @@ -44,6 +45,11 @@ {{end}}
+ {{ else }} + + {{ end }}
{{ i18n "ModeOpen" }}
{{ i18n "ExplanationOpen" }}
@@ -59,6 +65,7 @@ {{ i18n "ExplanationDefaultLanguage" }}

{{ i18n "SetDefaultLanguageTitle" }}

+ {{ if member_is_admin }}
{{ $.CurrentLanguage }} @@ -68,6 +75,11 @@ {{ list_languages $adminSetLanguageUrl "pl-10 pr-3 py-2 w-full text-left bg-white text-gray-700 hover:text-gray-900 hover:bg-gray-50 cursor-pointer" }}
+ {{ else }} + + {{ end }} diff --git a/web/templates/landing/index.tmpl b/web/templates/landing/index.tmpl index 40a67df..2896cae 100644 --- a/web/templates/landing/index.tmpl +++ b/web/templates/landing/index.tmpl @@ -9,11 +9,11 @@
- {{if is_logged_in}} + {{if and is_logged_in member_is_elevated }} {{i18n "NoticeEditTitle"}} {{end}} -{{end}} \ No newline at end of file +{{end}} diff --git a/web/templates/notice/list.tmpl b/web/templates/notice/list.tmpl index c73a1b8..55747f4 100644 --- a/web/templates/notice/list.tmpl +++ b/web/templates/notice/list.tmpl @@ -20,7 +20,7 @@ >{{.Language}} {{end}} - {{if is_logged_in}} + {{if and is_logged_in member_is_elevated }} {{end}} -{{end}} \ No newline at end of file +{{end}} diff --git a/web/templates/notice/show.tmpl b/web/templates/notice/show.tmpl index f512a66..09687e7 100644 --- a/web/templates/notice/show.tmpl +++ b/web/templates/notice/show.tmpl @@ -12,11 +12,11 @@
- {{if is_logged_in}} + {{if and is_logged_in member_is_elevated }}
{{i18n "NoticeEditTitle"}} {{end}} -{{end}} \ No newline at end of file +{{end}} From cec5f93fb603e8c26b0aa314d100ebbf7eef4e70 Mon Sep 17 00:00:00 2001 From: cblgh Date: Thu, 22 Apr 2021 10:36:59 +0200 Subject: [PATCH 2/2] tests for restricted UI behavior * add tests for privacy mode settings * test privacy mode settings for member role * test default language settings * test denied keys interface for each role * test adding new member interface depending on role * test member details depending on role * test invite button is disabled pending on user role --- web/handlers/admin/denied_keys_test.go | 80 +++++++++++++++++++ web/handlers/admin/invites_test.go | 44 +++++++++++ web/handlers/admin/members_test.go | 87 +++++++++++++++++++-- web/handlers/admin/set_language_test.go | 5 ++ web/handlers/admin/settings_test.go | 100 ++++++++++++++++++++++++ web/handlers/admin/setup_test.go | 11 ++- web/members/helper.go | 2 +- web/templates/admin/denied-keys.tmpl | 2 +- web/templates/admin/member-list.tmpl | 2 +- web/templates/admin/settings.tmpl | 6 +- 10 files changed, 322 insertions(+), 17 deletions(-) create mode 100644 web/handlers/admin/settings_test.go diff --git a/web/handlers/admin/denied_keys_test.go b/web/handlers/admin/denied_keys_test.go index 678f41d..cc38289 100644 --- a/web/handlers/admin/denied_keys_test.go +++ b/web/handlers/admin/denied_keys_test.go @@ -8,6 +8,7 @@ import ( "net/url" "testing" + "github.com/PuerkitoBio/goquery" "github.com/stretchr/testify/assert" "github.com/ssb-ngi-pointer/go-ssb-room/roomdb" @@ -32,6 +33,85 @@ func TestDeniedKeysEmpty(t *testing.T) { }) } +func TestDeniedKeysDisabledInterface(t *testing.T) { + ts := newSession(t) + a := assert.New(t) + + listURL := ts.URLTo(router.AdminDeniedKeysOverview) + + ts.User = roomdb.Member{ + ID: 1234, + Role: roomdb.RoleAdmin, + } + + html, resp := ts.Client.GetHTML(listURL) + a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code") + + formSelection := html.Find("form#add-entry") + a.EqualValues(1, formSelection.Length()) + + method, ok := formSelection.Attr("method") + a.True(ok, "form has method set") + a.Equal("POST", method) + + action, ok := formSelection.Attr("action") + a.True(ok, "form has action set") + + addURL := ts.URLTo(router.AdminDeniedKeysAdd) + a.Equal(addURL.String(), action) + + webassert.ElementsInForm(t, formSelection, []webassert.FormElement{ + {Name: "pub_key", Type: "text"}, + {Name: "comment", Type: "text"}, + }) + + newKey := "@x7iOLUcq3o+sjGeAnipvWeGzfuYgrXl8L4LYlxIhwDc=.ed25519" + addVals := url.Values{ + "comment": []string{"some comment"}, + // just any key that looks valid + "pub_key": []string{newKey}, + } + rec := ts.Client.PostForm(addURL, addVals) + a.Equal(http.StatusTemporaryRedirect, rec.Code) + + a.Equal(1, ts.DeniedKeysDB.AddCallCount()) + _, addedKey, addedComment := ts.DeniedKeysDB.AddArgsForCall(0) + a.Equal(newKey, addedKey.Ref()) + a.Equal("some comment", addedComment) + + /* Verify that the inputs are visible/hidden depending on user roles */ + checkInputsAreDisabled := func(shouldBeDisabled bool) { + html, resp = ts.Client.GetHTML(listURL) + a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code") + inputContainer := html.Find("#denied-keys-input-container") + a.Equal(1, inputContainer.Length()) + inputs := inputContainer.Find("input") + // pubkey, comment, submit button + a.Equal(3, inputs.Length()) + inputs.Each(func(i int, el *goquery.Selection) { + _, disabled := el.Attr("disabled") + a.Equal(shouldBeDisabled, disabled) + }) + } + + // verify that inputs are enabled for RoleAdmin + checkInputsAreDisabled(false) + + // verify that inputs are enabled for RoleModerator + ts.User = roomdb.Member{ + ID: 9001, + Role: roomdb.RoleModerator, + } + checkInputsAreDisabled(false) + + // verify that inputs are disabled for RoleMember + ts.User = roomdb.Member{ + ID: 7331, + Role: roomdb.RoleMember, + } + checkInputsAreDisabled(true) +} + func TestDeniedKeysAdd(t *testing.T) { ts := newSession(t) a := assert.New(t) diff --git a/web/handlers/admin/invites_test.go b/web/handlers/admin/invites_test.go index 2a32ceb..c074060 100644 --- a/web/handlers/admin/invites_test.go +++ b/web/handlers/admin/invites_test.go @@ -64,6 +64,50 @@ func TestInvitesOverview(t *testing.T) { a.True(yes, "a-tag has href attribute") wantURL := ts.URLTo(router.AdminInvitesRevokeConfirm, "id", 666) a.Equal(wantURL.String(), link) + + testInviteButtonDisabled := func(shouldBeDisabled bool) { + html, resp = ts.Client.GetHTML(invitesOverviewURL) + a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code") + inviteButton := html.Find("#create-invite button") + _, disabled := inviteButton.Attr("disabled") + a.EqualValues(shouldBeDisabled, disabled, "invite button should be disabled") + } + + // member, mod, admin should all be able to invite in ModeCommunity + ts.ConfigDB.GetPrivacyModeReturns(roomdb.ModeCommunity, nil) + ts.User = roomdb.Member{ + ID: 1234, + Role: roomdb.RoleAdmin, + } + testInviteButtonDisabled(false) + ts.User = roomdb.Member{ + ID: 7331, + Role: roomdb.RoleModerator, + } + testInviteButtonDisabled(false) + ts.User = roomdb.Member{ + ID: 9001, + Role: roomdb.RoleMember, + } + testInviteButtonDisabled(false) + + // mod and admin should be able to invite, member should not + ts.ConfigDB.GetPrivacyModeReturns(roomdb.ModeRestricted, nil) + ts.User = roomdb.Member{ + ID: 1234, + Role: roomdb.RoleAdmin, + } + testInviteButtonDisabled(false) + ts.User = roomdb.Member{ + ID: 7331, + Role: roomdb.RoleModerator, + } + testInviteButtonDisabled(false) + ts.User = roomdb.Member{ + ID: 9001, + Role: roomdb.RoleMember, + } + testInviteButtonDisabled(true) } func TestInvitesCreateForm(t *testing.T) { diff --git a/web/handlers/admin/members_test.go b/web/handlers/admin/members_test.go index 0d870f4..ac24bb0 100644 --- a/web/handlers/admin/members_test.go +++ b/web/handlers/admin/members_test.go @@ -7,6 +7,7 @@ import ( "net/url" "testing" + "github.com/PuerkitoBio/goquery" "github.com/stretchr/testify/assert" "github.com/ssb-ngi-pointer/go-ssb-room/roomdb" @@ -35,6 +36,11 @@ func TestMembersAdd(t *testing.T) { ts := newSession(t) a := assert.New(t) + ts.User = roomdb.Member{ + ID: 1234, + Role: roomdb.RoleAdmin, + } + listURL := ts.URLTo(router.AdminMembersOverview) html, resp := ts.Client.GetHTML(listURL) @@ -70,6 +76,43 @@ func TestMembersAdd(t *testing.T) { a.Equal(newKey, addedPubKey.Ref()) a.Equal(roomdb.RoleMember, addedRole) + /* Verify that the inputs are visible/hidden depending on user roles */ + checkInputsAreDisabled := func(shouldBeDisabled bool) { + html, resp = ts.Client.GetHTML(listURL) + a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code") + inputContainer := html.Find("#add-member-input-container") + a.Equal(1, inputContainer.Length()) + inputs := inputContainer.Find("input") + // pubkey + a.Equal(1, inputs.Length()) + inputs.Each(func(i int, el *goquery.Selection) { + _, disabled := el.Attr("disabled") + a.Equal(shouldBeDisabled, disabled) + }) + button := inputContainer.Find("button") + a.Equal(1, button.Length()) + button.Each(func(i int, el *goquery.Selection) { + _, disabled := el.Attr("disabled") + a.Equal(shouldBeDisabled, disabled) + }) + } + + // verify that inputs are enabled for RoleAdmin + checkInputsAreDisabled(false) + + // verify that inputs are enabled for RoleModerator + ts.User = roomdb.Member{ + ID: 9001, + Role: roomdb.RoleModerator, + } + checkInputsAreDisabled(false) + + // verify that inputs are disabled for RoleMember + ts.User = roomdb.Member{ + ID: 7331, + Role: roomdb.RoleMember, + } + checkInputsAreDisabled(true) } func TestMembersDontAddInvalid(t *testing.T) { @@ -173,6 +216,11 @@ func TestMemberDetails(t *testing.T) { memberURL := ts.URLTo(router.AdminMemberDetails, "id", "1") + ts.User = roomdb.Member{ + ID: 1234, + Role: roomdb.RoleAdmin, + } + html, resp := ts.Client.GetHTML(memberURL) a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code") @@ -180,14 +228,6 @@ func TestMemberDetails(t *testing.T) { {"title", "AdminMemberDetailsTitle"}, }) - // check for SSB ID - ssbID := html.Find("#ssb-id") - a.Equal(feedRef.Ref(), ssbID.Text()) - - // check for change-role dropdown - roleDropdown := html.Find("#change-role") - a.EqualValues(roleDropdown.Length(), 1) - aliasList := html.Find("#alias-list").Find("a") // check for link to resolve 1st Alias @@ -220,6 +260,37 @@ func TestMemberDetails(t *testing.T) { a.True(yes, "a-tag has href attribute") wantLink = ts.URLTo(router.AdminMembersRemoveConfirm, "id", 1) a.Equal(wantLink.String(), removeLink) + + testDisabledBehaviour := func(isElevated bool) { + html, resp := ts.Client.GetHTML(memberURL) + a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code") + // check for SSB ID + ssbID := html.Find("#ssb-id") + a.Equal(feedRef.Ref(), ssbID.Text()) + + // check for change-role dropdown + roleDropdown := html.Find("#change-role") + if isElevated { + a.Equal(1, roleDropdown.Length()) + } else { + a.Equal(0, roleDropdown.Length()) + } + } + testDisabledBehaviour(true) + + /* Now: verify that moderators cannot make room settings changes */ + ts.User = roomdb.Member{ + ID: 7331, + Role: roomdb.RoleModerator, + } + testDisabledBehaviour(true) + + /* Finally: verify that members cannot make room settings changes */ + ts.User = roomdb.Member{ + ID: 9001, + Role: roomdb.RoleMember, + } + testDisabledBehaviour(false) } func TestMembersRemoveConfirmation(t *testing.T) { diff --git a/web/handlers/admin/set_language_test.go b/web/handlers/admin/set_language_test.go index 9d57d11..af5b5a1 100644 --- a/web/handlers/admin/set_language_test.go +++ b/web/handlers/admin/set_language_test.go @@ -5,6 +5,7 @@ import ( "strings" "testing" + "github.com/ssb-ngi-pointer/go-ssb-room/roomdb" "github.com/ssb-ngi-pointer/go-ssb-room/web/router" "github.com/stretchr/testify/assert" ) @@ -33,6 +34,10 @@ func TestLanguageSetDefaultLanguage(t *testing.T) { a := assert.New(t) ts.ConfigDB.GetDefaultLanguageReturns("de", nil) + ts.User = roomdb.Member{ + ID: 1234, + Role: roomdb.RoleAdmin, + } u := ts.URLTo(router.AdminSettings) html, resp := ts.Client.GetHTML(u) diff --git a/web/handlers/admin/settings_test.go b/web/handlers/admin/settings_test.go new file mode 100644 index 0000000..c05c3a8 --- /dev/null +++ b/web/handlers/admin/settings_test.go @@ -0,0 +1,100 @@ +package admin + +import ( + "net/http" + "strings" + "testing" + + "github.com/PuerkitoBio/goquery" + "github.com/stretchr/testify/assert" + + "github.com/ssb-ngi-pointer/go-ssb-room/roomdb" + "github.com/ssb-ngi-pointer/go-ssb-room/web/router" +) + +func TestSettingsOverview(t *testing.T) { + ts := newSession(t) + a := assert.New(t) + + /* First: make sure everything renders correctly for admins */ + ts.User = roomdb.Member{ + ID: 1234, + Role: roomdb.RoleAdmin, + } + + ts.ConfigDB.GetPrivacyModeReturns(roomdb.ModeCommunity, nil) + ts.ConfigDB.GetDefaultLanguageReturns("en", nil) + + settingsURL := ts.URLTo(router.AdminSettings) + + html, resp := ts.Client.GetHTML(settingsURL) + a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code") + + // the privacy mode form & its summary/details container should exist + privacyFormContainer := html.Find("#change-privacy") + a.Equal(1, privacyFormContainer.Length()) + a.Equal(1, privacyFormContainer.Find("summary").Length()) + // chosen privacy mode is ModeCommunity (english translation will only be the name of the label, due to testing suite is set up atm) + a.Equal("ModeCommunity", strings.TrimSpace(privacyFormContainer.Find("summary").Text())) + // details-dropdown should have two forms, one for each of the other two privacy modes + // that can be selected (ModeOpen, ModeRestricted) + a.Equal(2, privacyFormContainer.Find("form").Length()) + // and one span, showing the selected mode + a.Equal(1, privacyFormContainer.Find("#selected-mode").Length()) + inputs := privacyFormContainer.Find("input") + // verify none of the privacy mode container's inputs are disabled + inputs.Each(func(i int, el *goquery.Selection) { + _, exists := el.Attr("disabled") + a.False(exists) + }) + + // verify that the change language form exists & is enabled + languageFormContainer := html.Find("#change-language-container") + a.Equal(1, languageFormContainer.Length()) + a.Equal(1, languageFormContainer.Find("summary").Length()) + // (english translation will only be the name of the label, due to testing suite is set up atm) + a.Equal("LanguageName", strings.TrimSpace(languageFormContainer.Find("summary").Text())) + + testDisabledBehaviour := func() { + settingsURL := ts.URLTo(router.AdminSettings) + html, resp := ts.Client.GetHTML(settingsURL) + a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code") + + // we do not have the summary/details hack if the forms are hidden + privacyFormContainer := html.Find("#change-privacy") + a.Equal(0, privacyFormContainer.Length()) + // the should still be the parent container, however + privacyContainer := html.Find("#privacy-mode-container") + a.Equal(1, privacyContainer.Length()) + // there should only be one input in the privacy mode container now + inputs := privacyContainer.Find("input") + a.Equal(1, inputs.Length()) + // the input should be disabled + _, disabled := inputs.Attr("disabled") + a.True(disabled) + + // next, verify that the change language setting is disabled + languageContainer := html.Find("#change-language-container") + a.Equal(1, languageContainer.Length()) + // there should only be one input in the language mode container now + inputs = languageContainer.Find("input") + a.Equal(1, inputs.Length()) + // the input should be disabled + _, disabled = inputs.Attr("disabled") + a.True(disabled) + } + + /* Now: verify that moderators cannot make room settings changes */ + ts.User = roomdb.Member{ + ID: 7331, + Role: roomdb.RoleModerator, + } + testDisabledBehaviour() + + /* Finally: verify that members cannot make room settings changes */ + ts.User = roomdb.Member{ + ID: 9001, + Role: roomdb.RoleMember, + } + testDisabledBehaviour() +} diff --git a/web/handlers/admin/setup_test.go b/web/handlers/admin/setup_test.go index eeac3fb..07ce1e1 100644 --- a/web/handlers/admin/setup_test.go +++ b/web/handlers/admin/setup_test.go @@ -133,9 +133,14 @@ func newSession(t *testing.T) *testSession { testFuncs["urlToNotice"] = func(name string) string { return "" } testFuncs["language_count"] = func() int { return 1 } testFuncs["list_languages"] = func(*url.URL, string) string { return "" } - testFuncs["member_is_elevated"] = func() bool { return true } - testFuncs["member_is_admin"] = func() bool { return true } - testFuncs["member_can_invite"] = func() bool { return true } + testFuncs["member_is_elevated"] = func() bool { return ts.User.Role == roomdb.RoleAdmin || ts.User.Role == roomdb.RoleModerator } + testFuncs["member_is_admin"] = func() bool { return ts.User.Role == roomdb.RoleAdmin } + testFuncs["member_can_invite"] = func() bool { + pm, _ := ts.ConfigDB.GetPrivacyMode(ctx) + memberElevated := ts.User.Role == roomdb.RoleAdmin || ts.User.Role == roomdb.RoleModerator + memberCanInvite := ts.User.Role == roomdb.RoleMember && (pm == roomdb.ModeCommunity || pm == roomdb.ModeOpen) + return memberElevated || memberCanInvite + } testFuncs["list_languages"] = func(*url.URL, string) string { return "" } testFuncs["relative_time"] = func(when time.Time) string { return humanize.Time(when) } diff --git a/web/members/helper.go b/web/members/helper.go index 8677d53..42542ed 100644 --- a/web/members/helper.go +++ b/web/members/helper.go @@ -100,7 +100,7 @@ func ContextInjecter(mdb roomdb.MembersService, withPassword *auth.Handler, with // // {{ member_is_admin }} is a shortcut for {{ member_has_role "RoleAdmin" }} // -// {{ member_is_admin }} is a shortcut for {{ or member_has_role "RoleAdmin" member_has_role "RoleModerator"}} +// {{ member_is_elevated }} is a shortcut for {{ or member_has_role "RoleAdmin" member_has_role "RoleModerator"}} func TemplateHelpers() []render.Option { return []render.Option{ diff --git a/web/templates/admin/denied-keys.tmpl b/web/templates/admin/denied-keys.tmpl index 175bea9..c082bfc 100644 --- a/web/templates/admin/denied-keys.tmpl +++ b/web/templates/admin/denied-keys.tmpl @@ -20,7 +20,7 @@ method="POST" > {{ .csrfField }} -
+
{{ .csrfField }} -
+
{{ i18n "Settings" }} -
+

{{ i18n "PrivacyModesTitle" }}

{{ i18n "ExplanationPrivacyModes" }} @@ -39,7 +39,7 @@

- {{ i18n .String }} + {{ i18n .String }}
{{end}} {{end}} @@ -59,7 +59,7 @@
{{ i18n "ExplanationRestricted" }}
-
+

{{ i18n "DefaultLanguageTitle" }}

{{ i18n "ExplanationDefaultLanguage" }}