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"}}
- 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.
-
+ 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.
+
+
+ The first action is to mark their camera red for them - sometimes people just
+ forget!
+
+
+ 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.
+
+
+ If users are being difficult and insisting on keeping an explicit camera blue,
+ kicking or banning them from the room is OK.
+
+
+
+
+ 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 usernamehours
+
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.
+
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.
+
+ 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}}
+
+
+
+
+
+
+
+
+
+ All Possible Admin Permissions
+
+
+
+
+
+
+
+ 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.
+