diff --git a/pkg/config/admin_scopes.go b/pkg/config/admin_scopes.go index 07c7508..c06654a 100644 --- a/pkg/config/admin_scopes.go +++ b/pkg/config/admin_scopes.go @@ -35,18 +35,51 @@ const ( ScopeUserInsight = "admin.user.insights" ScopeUserImpersonate = "admin.user.impersonate" ScopeUserBan = "admin.user.ban" - ScopeUserPromote = "admin.user.promote" ScopeUserDelete = "admin.user.delete" + ScopeUserPromote = "admin.user.promote" + + // Other admin views + ScopeFeedbackAndReports = "admin.feedback" + ScopeChangeLog = "admin.changelog" + ScopeUserNotes = "admin.user.notes" // Admins with this scope can not be blocked by users. ScopeUnblockable = "admin.unblockable" // Special scope to mark an admin automagically in the Inner Circle ScopeIsInnerCircle = "admin.override.inner-circle" + + // The global wildcard scope gets all available permissions. + ScopeSuperuser = "*" ) +// Friendly description for each scope. +var AdminScopeDescriptions = map[string]string{ + ScopeChatModerator: "Have operator controls in the chat room (can mark cameras as explicit, or kick/ban people from chat).", + ScopeForumModerator: "Ability to moderate the forum (edit or delete posts).", + ScopePhotoModerator: "Ability to moderate photo galleries (can see all private or friends-only photos, and edit or delete them).", + ScopeCircleModerator: "Ability to remove members from the inner circle.", + ScopeCertificationApprove: "Ability to see pending certification pictures and approve or reject them.", + ScopeCertificationList: "Ability to see existing certification pictures that have already been approved or rejected.", + ScopeCertificationView: "Ability to see and double check a specific user's certification picture on demand.", + ScopeForumAdmin: "Ability to manage forums themselves (add or remove forums, edit their properties).", + ScopeAdminScopeAdmin: "Ability to manage admin permissions for other admin accounts.", + ScopeMaintenance: "Ability to activate maintenance mode functions of the website (turn features on or off, disable signups or logins, etc.)", + ScopeUserInsight: "Ability to see admin insights about a user profile (e.g. their block lists and who blocks them).", + ScopeUserImpersonate: "Ability to log in as any user account (note: this action is logged and notifies all admins when it happens. Admins must write a reason and it is used to diagnose customer support issues, help with their certification picture, or investigate a reported Direct Message conversation they had).", + ScopeUserBan: "Ability to ban or unban user accounts.", + ScopeUserDelete: "Ability to fully delete user accounts on their behalf.", + ScopeUserPromote: "Ability to add or remove the admin status flag on a user profile.", + ScopeFeedbackAndReports: "Ability to see admin reports and user feedback.", + ScopeChangeLog: "Ability to see website change logs (e.g. history of a certification photo, gallery photo settings, etc.)", + ScopeUserNotes: "Ability to see all notes written about a user, or to see all notes written by admins.", + ScopeUnblockable: "This admin can not be added to user block lists.", + ScopeIsInnerCircle: "This admin is automatically part of the inner circle.", + ScopeSuperuser: "This admin has access to ALL admin features on the website.", +} + // Number of expected scopes for unit test and validation. -const QuantityAdminScopes = 16 +const QuantityAdminScopes = 20 // The specially named Superusers group. const AdminGroupSuperusers = "Superusers" @@ -63,12 +96,20 @@ func ListAdminScopes() []string { ScopeCertificationView, ScopeForumAdmin, ScopeAdminScopeAdmin, + ScopeMaintenance, ScopeUserInsight, ScopeUserImpersonate, ScopeUserBan, ScopeUserDelete, ScopeUserPromote, + ScopeFeedbackAndReports, + ScopeChangeLog, + ScopeUserNotes, ScopeUnblockable, ScopeIsInnerCircle, } } + +func AdminScopeDescription(scope string) string { + return AdminScopeDescriptions[scope] +} diff --git a/pkg/config/admin_scopes_test.go b/pkg/config/admin_scopes_test.go index 1d582f1..c50da0c 100644 --- a/pkg/config/admin_scopes_test.go +++ b/pkg/config/admin_scopes_test.go @@ -10,7 +10,7 @@ import ( // returned by the scope list function. func TestAdminScopesCount(t *testing.T) { var scopes = config.ListAdminScopes() - if len(scopes) != config.QuantityAdminScopes { + if len(scopes) != config.QuantityAdminScopes || len(scopes) != len(config.AdminScopeDescriptions) { t.Errorf( "The list of scopes returned by ListAdminScopes doesn't match the expected count. "+ "Expected %d, got %d", diff --git a/pkg/controller/account/user_note.go b/pkg/controller/account/user_note.go index 76ad901..4c7e0a3 100644 --- a/pkg/controller/account/user_note.go +++ b/pkg/controller/account/user_note.go @@ -201,7 +201,7 @@ func MyNotes() http.HandlerFunc { } // Admin notes? - if adminNotes && !currentUser.IsAdmin { + if adminNotes && !currentUser.HasAdminScope(config.ScopeUserNotes) { adminNotes = false } diff --git a/pkg/controller/admin/transparency.go b/pkg/controller/admin/transparency.go new file mode 100644 index 0000000..2b76c2a --- /dev/null +++ b/pkg/controller/admin/transparency.go @@ -0,0 +1,43 @@ +package admin + +import ( + "net/http" + + "code.nonshy.com/nonshy/website/pkg/config" + "code.nonshy.com/nonshy/website/pkg/models" + "code.nonshy.com/nonshy/website/pkg/templates" +) + +// Admin transparency page that lists the scopes and permissions an admin account has for all to see. +func Transparency() http.HandlerFunc { + tmpl := templates.Must("admin/transparency.html") + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var ( + username = r.PathValue("username") + ) + + // Get this user. + user, err := models.FindUser(username) + if err != nil { + templates.NotFoundPage(w, r) + return + } + + // Only for admin user accounts. + if !user.IsAdmin { + templates.NotFoundPage(w, r) + return + } + + // Template variables. + var vars = map[string]interface{}{ + "User": user, + "AdminScopes": config.ListAdminScopes(), + } + + if err := tmpl.Execute(w, r, vars); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + }) +} diff --git a/pkg/router/router.go b/pkg/router/router.go index abbaee0..ab91332 100644 --- a/pkg/router/router.go +++ b/pkg/router/router.go @@ -78,6 +78,7 @@ func New() http.Handler { mux.Handle("GET /admin/unimpersonate", middleware.LoginRequired(admin.Unimpersonate())) mux.Handle("GET /inner-circle", middleware.LoginRequired(account.InnerCircle())) mux.Handle("/inner-circle/invite", middleware.LoginRequired(account.InviteCircle())) + mux.Handle("GET /admin/transparency/{username}", middleware.LoginRequired(admin.Transparency())) // Certification Required. Pages that only full (verified) members can access. mux.Handle("GET /photo/gallery", middleware.CertRequired(photo.SiteGallery())) @@ -95,14 +96,14 @@ func New() http.Handler { mux.Handle("GET /admin", middleware.AdminRequired("", admin.Dashboard())) mux.Handle("/admin/scopes", middleware.AdminRequired("", admin.Scopes())) mux.Handle("/admin/photo/certification", middleware.AdminRequired("", photo.AdminCertification())) - mux.Handle("/admin/feedback", middleware.AdminRequired("", admin.Feedback())) + mux.Handle("/admin/feedback", middleware.AdminRequired(config.ScopeFeedbackAndReports, admin.Feedback())) mux.Handle("/admin/user-action", middleware.AdminRequired("", admin.UserActions())) mux.Handle("/admin/maintenance", middleware.AdminRequired(config.ScopeMaintenance, admin.Maintenance())) mux.Handle("/forum/admin", middleware.AdminRequired(config.ScopeForumAdmin, forum.Manage())) mux.Handle("/forum/admin/edit", middleware.AdminRequired(config.ScopeForumAdmin, forum.AddEdit())) mux.Handle("/inner-circle/remove", middleware.LoginRequired(account.RemoveCircle())) mux.Handle("/admin/photo/mark-explicit", middleware.AdminRequired(config.ScopePhotoModerator, admin.MarkPhotoExplicit())) - mux.Handle("GET /admin/changelog", middleware.AdminRequired("", admin.ChangeLog())) + mux.Handle("GET /admin/changelog", middleware.AdminRequired(config.ScopeChangeLog, admin.ChangeLog())) // JSON API endpoints. mux.HandleFunc("GET /v1/version", api.Version()) diff --git a/pkg/templates/template_funcs.go b/pkg/templates/template_funcs.go index 011eac9..a90bd67 100644 --- a/pkg/templates/template_funcs.go +++ b/pkg/templates/template_funcs.go @@ -71,6 +71,9 @@ func TemplateFuncs(r *http.Request) template.FuncMap { // Test if a photo should be blurred ({{BlurExplicit .Photo}}) "BlurExplicit": BlurExplicit(r), + + // Get a description for an admin scope (e.g. for transparency page). + "AdminScopeDescription": config.AdminScopeDescription, } } diff --git a/pkg/templates/template_vars.go b/pkg/templates/template_vars.go index f8d8a72..eab1e34 100644 --- a/pkg/templates/template_vars.go +++ b/pkg/templates/template_vars.go @@ -104,11 +104,20 @@ func MergeUserVars(r *http.Request, m map[string]interface{}) { log.Error("MergeUserVars: couldn't CountFriendRequests for %d: %s", user.ID, err) } - // Are we admin? + // Are we admin? Add notification counts if the current admin can respond to them. if user.IsAdmin { + var countCertPhotos, countFeedback int64 + // Any pending certification photos or feedback? - countCertPhotos = models.CountCertificationPhotosNeedingApproval() - countFeedback = models.CountUnreadFeedback() + if user.HasAdminScope(config.ScopeCertificationApprove) { + countCertPhotos = models.CountCertificationPhotosNeedingApproval() + } + + // Admin feedback available? + if user.HasAdminScope(config.ScopeFeedbackAndReports) { + countFeedback = models.CountUnreadFeedback() + } + m["NavCertificationPhotos"] = countCertPhotos m["NavAdminFeedback"] = countFeedback diff --git a/web/templates/account/my_user_notes.html b/web/templates/account/my_user_notes.html index cc66489..26742cb 100644 --- a/web/templates/account/my_user_notes.html +++ b/web/templates/account/my_user_notes.html @@ -52,7 +52,7 @@ - {{if .CurrentUser.IsAdmin}} + {{if .CurrentUser.HasAdminScope "admin.user.notes"}}
diff --git a/web/templates/account/profile.html b/web/templates/account/profile.html index 057e4d0..25895fa 100644 --- a/web/templates/account/profile.html +++ b/web/templates/account/profile.html @@ -134,10 +134,13 @@ {{if .User.IsAdmin}}
- - - - Admin + + + + + Admin + +
{{end}} diff --git a/web/templates/account/user_notes.html b/web/templates/account/user_notes.html index 54ab7fc..8b798ab 100644 --- a/web/templates/account/user_notes.html +++ b/web/templates/account/user_notes.html @@ -123,7 +123,7 @@ - {{if .CurrentUser.IsAdmin}} + {{if .CurrentUser.HasAdminScope "admin.user.notes"}}

@@ -176,7 +176,7 @@

- {{if .CurrentUser.IsAdmin}} + {{if .CurrentUser.HasAdminScope "admin.feedback"}}
diff --git a/web/templates/admin/dashboard.html b/web/templates/admin/dashboard.html index 3b70f40..45b9015 100644 --- a/web/templates/admin/dashboard.html +++ b/web/templates/admin/dashboard.html @@ -12,105 +12,6 @@
-
-
-
-

- - Admin Guidelines NEW! -

-
- -
-

Respect the privacy of our users

- -
    -
  • - We do not snoop on our users' Direct Messages unless they report a conversation - for us to check out. The only way to access DMs is to impersonate a - user, which can't be done in secret. -
  • -
  • - We treat the Certification Photos as sensitive information. Go there only when a certification - photo is pending approval (red notification badges will guide the way). Do not download or leak - these images; be respectful. -
  • -
- -

What we moderate

- - Admin users are only expected to help moderate the following areas of the site: - -

1. User profile photos

- -

- Every picture uploaded to a user's profile page can be seen by admin users. The - admin gallery view can find all - user photos, whether private or friends-only, whether opted-in for the Site Gallery or not. -

- -

- Be careful not to "Like" or comment on a picture if the user marked it - "Friends only" or "Private" and they wouldn't expect you to have been able to see it. "Like" - and "Comment" buttons are hidden in the admin gallery view to reduce accidents but they are - functional on the user's own gallery page. -

- -

2. The Forums

- -

- Keep up with the newest forum posts and generally make sure - people aren't fighting or uploading inappropriate photos to one of the few photo boards. -

- -

3. Reported DMs only

- -

- If a user reports a Direct Message conversation - they're having, a link to view that chat thread will be available from the report. - This will impersonate the reporter and will be logged - - see "Impersonating users," below. -

- -

- DMs are text-based only, so users won't be sending any image attachments that need - moderating and their privacy is to be respected. A user may report a problematic - conversation for us to take a look at. -

- -
- -

Impersonating users

- -

- From a user's profile page you can "impersonate," or log in as them. You should almost - never need to do this, and only to diagnose a reported issue from their account or - something like that. -

- -

- You will need to write a reason for impersonating a user. The event is - logged and will be e-mailed to the admin team along with your reason. The admin team is - alerted any time an Impersonate action is performed. -

- -

- Note: when you impersonate, their "Last logged in at" time is not updated by your actions. - So if a user were last seen a month ago, they will still appear last seen a month ago. - But other interactions you make as their account (e.g. marking notifications read, reading - their unread DMs) will work and may be noticed. -

- -
- -

- -

- -
-
-
-
+ +
+
+
+

+ + Admin Guidelines +

+
+ +
+

+ Table of contents: +

+ + +

What is my role as an admin?

+ +

+ Please see your Admin Transparency page to + review the list of permissions and roles you are capable of. Then, review the relevant sections + below for some guidelines and information relating to that role. +

+ +

Respect the privacy of our users

+ +
    +
  • + We do not snoop on our users' Direct Messages unless they report a conversation + for us to check out. The only way to access DMs is to impersonate a + user, which can't be done in secret. +
  • +
  • + We treat the Certification Photos as sensitive information. Go there only when a certification + photo is pending approval (green notification badges will guide the way). Do not download or leak + these images; be respectful. +
  • +
+ +

What we moderate

+ + Admin users are only expected to help moderate the following areas of the site: + +

1. User photo galleries

+ +

+ Every picture uploaded to a user's profile page can be seen by (some) admin users. The + admin gallery view can find all + user photos, whether private or friends-only, whether opted-in for the Site Gallery or not. +

+ +

+ Notice: the website will not allow you to accidentally "like" a photo + that you should not have been able to see. "Like" buttons are hidden during the admin view + of the Site Gallery, but in case you click it on their profile page, the website will display + an error message "You aren't allowed to like that photo" so the user isn't alarmed. +

+ +

2. The Forums

+ +

+ Keep up with the newest forum posts and generally make sure + people aren't fighting or uploading inappropriate photos to one of the few photo boards. +

+ +

3. The Chat Room

+ +

+ If you are moderating the chat rooms, your main responsibilities are to: +

+ +
    +
  • + Ensure that people mark their webcams as 'Explicit' / red if they are jerking off + or being sexual. Use the /nsfw command to set their camera to + red. +
      +
    1. + The first action is to mark their camera red for them - sometimes people just + forget! +
    2. +
    3. + If the user fights you and removes the 'explicit' flag on their camera, you + may send them a verbal warning or kick them from the room. Please wait until + they have fought your flag at least two times: sometimes users + are confused by the wording of the "your cam was marked explicit" message and + accidentally un-mark their camera. +
    4. +
    5. + If users are being difficult and insisting on keeping an explicit camera blue, + kicking or banning them from the room is OK. +
    6. +
    +
  • +
  • + Ensure that people are behaving themselves: not spamming or breaking the rules + by posting explicit photos in non-explicit channels. As above: a gentle warning is + often the first step before you kick or ban problematic people. +
  • +
+ +

4. Reported DMs only

+ +

+ If a user reports a Direct Message conversation + they're having, a link to view that chat thread will be available from the report. + This will impersonate the reporter and will be logged + - see "Impersonating users," below. +

+ +

+ DMs are text-based only, so users won't be sending any image attachments that need + moderating and their privacy is to be respected. A user may report a problematic + conversation for us to take a look at. +

+ +
+ +

Impersonating users

+ +

+ From a user's profile page you can "impersonate," or log in as them. You should almost + never need to do this, and only to diagnose a reported issue from their account or + something like that. +

+ +

+ You will need to write a reason for impersonating a user. The event is + logged and will be e-mailed to the admin team along with your reason. The admin team is + alerted any time an Impersonate action is performed. +

+ +

+ Note: when you impersonate, their "Last logged in at" time is not updated by your actions. + So if a user were last seen a month ago, they will still appear last seen a month ago. + But other interactions you make as their account (e.g. marking notifications read, reading + their unread DMs) will work and may be noticed. +

+ +
+ +

Chat room commands

+ +

+ If you are moderating the chat room, these are useful commands to know. You can type + these commands into the message box on the room. +

+ +
+
/help
+
Shows a reminder in chat about the available operator commands.
+ +
/nsfw username
+
Mark a user's webcam as 'explicit' on their behalf (turning it from a blue cam into a red cam).
+ +
/kick username
+
Kick a user from the chat room. They will be able to log back in.
+ +
/ban username
+
Temporarily ban the user from the chat room for 24 hours (by default).
+ +
/ban username hours
+
Provide a number of hours to set a different ban duration than the default (see examples below).
+ +
/bans
+
Get a list of currently active chat room bans.
+ +
/unban username
+
Immediately remove the ban flag on this username.
+
+ +

+ Important: + + The /help command lists some additional options that you generally should + never need to use. Be very careful not to issue commands like shutdown + which will reboot the chat server, as this can be very disruptive to our members. +

+ +

+ Generally, as a volunteer chat moderator you will only be using the /nsfw, /kick and /ban + commands as needed. +

+ +

Examples

+ +
    +
  • /kick {{.CurrentUser.Username}}
  • +
  • /ban {{.CurrentUser.Username}}
  • +
  • /ban {{.CurrentUser.Username}} 12
  • +
+ +

+ Note: the @ symbol should NOT appear in front of the username, the chat server currently + won't recognize the user. If you use the @ mention auto-complete, be sure to remove the @ + symbol before sending the command. This is a bug that will probably be fixed soon. +

+ +
+
+
+
diff --git a/web/templates/admin/transparency.html b/web/templates/admin/transparency.html new file mode 100644 index 0000000..f1315a3 --- /dev/null +++ b/web/templates/admin/transparency.html @@ -0,0 +1,167 @@ +{{define "title"}}Admin Transparency for: {{.User.Username}}{{end}} +{{define "content"}} +
+
+
+
+

+ Admin Transparency +

+

Scopes & permissions available to: {{.User.Username}}

+
+
+
+ + {{$Root := .}} + +
+

+ This web page provides transparency for the website administrators and what their specific + responsibilities and capabilities are. +

+ +

+ Administrators on {{PrettyTitle}} do not automatically have access to "god mode" powers to + use every admin feature across the entire website. Instead, admin accounts are assigned to + specific limited roles with related, narrowly scoped, permissions related to that role. + For example: an admin who only moderates the chat room will not have access + to see certification pictures or your private gallery pictures. +

+ +

+ This enables {{PrettyTitle}} to recruit help from volunteer moderators to help with very + specific tasks (such as chat room or forum moderation) while keeping their permissions locked + down so they can't access other sensitive areas of the admin website. +

+
+ +
+ +
+
+ +
+ +
+ +
+
+ {{template "avatar-64x64" .User}} +
+
+

{{.User.NameOrUsername}}

+

+ + {{.User.Username}} + + Admin + +

+
+
+ +
+ +

+ Admin accounts on {{PrettyTitle}} are assigned permissions based on the "groups" + they are in: each group relates to a specific role (such as chat moderator) and + grants the specific website permissions related to that role. +

+ +

+ @{{.User.Username}} is a member of {{len .User.AdminGroups}} admin group{{Pluralize (len .User.AdminGroups)}}: +

+ + {{if eq (len .User.AdminGroups) 0}} +
+ They are not assigned to any admin groups and so they have no special permissions aside + from the 'Admin' badge appearing on their profile page. +
+ {{end}} + + {{range .User.AdminGroups}} +
+

{{.Name}}

+ +

+ Permission scopes: +

+ +
+ {{range .Scopes}} +
+ + {{.Scope}} + {{if eq .Scope "*"}} + (wildcard scope that grants all permissions) + {{end}} + +
+
{{AdminScopeDescription .Scope}}
+ {{end}} +
+ {{end}} + +
+ +
+
+ +
+ +
+ +
+ +

+ For context to the above, the following is the complete and exhaustive list of + {{PrettyTitle}} admin capabilities that could be granted to an admin account. +

+ +

+ Permissions that this admin has will be highlighted in + green, + and permissions they do not have will be in + red. +

+ +
+ +
+ {{range .AdminScopes}} +
+ {{if $Root.User.HasAdminScope .}} + + + {{.}} + + {{else}} + + + {{.}} + + {{end}} +
+
{{AdminScopeDescription .}}
+ {{end}} +
+ +
+ +
+
+ +
+
+
+ +
+{{end}} \ No newline at end of file