From 345285d7a3b2e27f2660631838893086ada91e1b Mon Sep 17 00:00:00 2001
From: Noah Petherbridge
Date: Sat, 24 Dec 2022 23:00:59 -0800
Subject: [PATCH] Add Admin Guidelines to dashboard
* A reason must be entered to impersonate a user, and it triggers a
Report and email notification to the admin.
* User gallery pages will show at the top whether the user had granted
you access to their private photos.
---
pkg/controller/admin/feedback.go | 2 +-
pkg/controller/admin/user_actions.go | 3 +-
pkg/controller/photo/user_gallery.go | 1 +
pkg/session/session.go | 37 +++++-
web/templates/admin/dashboard.html | 126 ++++++++++++++++++---
web/templates/admin/user_actions.html | 29 +++++
web/templates/email/admin_impersonate.html | 32 ++++++
web/templates/photo/gallery.html | 20 +++-
8 files changed, 225 insertions(+), 25 deletions(-)
create mode 100644 web/templates/email/admin_impersonate.html
diff --git a/pkg/controller/admin/feedback.go b/pkg/controller/admin/feedback.go
index a429ca4..b8e7a06 100644
--- a/pkg/controller/admin/feedback.go
+++ b/pkg/controller/admin/feedback.go
@@ -80,7 +80,7 @@ func Feedback() http.HandlerFunc {
if err != nil {
session.FlashError(w, r, "Couldn't get reporting user ID %d: %s", fb.UserID, err)
} else {
- if err := session.ImpersonateUser(w, r, user, currentUser); err != nil {
+ if err := session.ImpersonateUser(w, r, user, currentUser, "Clicked from user reported Message via admin dashboard"); err != nil {
session.FlashError(w, r, "Couldn't impersonate user: %s", err)
} else {
// Redirect to the thread.
diff --git a/pkg/controller/admin/user_actions.go b/pkg/controller/admin/user_actions.go
index fa456b6..d8166df 100644
--- a/pkg/controller/admin/user_actions.go
+++ b/pkg/controller/admin/user_actions.go
@@ -17,6 +17,7 @@ func UserActions() http.HandlerFunc {
var (
intent = r.FormValue("intent")
confirm = r.Method == http.MethodPost
+ reason = r.FormValue("reason") // for impersonation
userId uint64
)
@@ -47,7 +48,7 @@ func UserActions() http.HandlerFunc {
switch intent {
case "impersonate":
if confirm {
- if err := session.ImpersonateUser(w, r, user, currentUser); err != nil {
+ if err := session.ImpersonateUser(w, r, user, currentUser, reason); err != nil {
session.FlashError(w, r, "Failed to impersonate user: %s", err)
} else {
session.Flash(w, r, "You are now impersonating %s", user.Username)
diff --git a/pkg/controller/photo/user_gallery.go b/pkg/controller/photo/user_gallery.go
index 0ad749e..ae0157d 100644
--- a/pkg/controller/photo/user_gallery.go
+++ b/pkg/controller/photo/user_gallery.go
@@ -108,6 +108,7 @@ func UserPhotos() http.HandlerFunc {
var vars = map[string]interface{}{
"IsOwnPhotos": currentUser.ID == user.ID,
"IsMyPrivateUnlockedFor": isGranted, // have WE granted THIS USER to see our private pics?
+ "AreWeGrantedPrivate": isGrantee, // have THEY granted US private photo access.
"User": user,
"Photos": photos,
"PhotoCount": models.CountPhotos(user.ID),
diff --git a/pkg/session/session.go b/pkg/session/session.go
index 0458261..515dd35 100644
--- a/pkg/session/session.go
+++ b/pkg/session/session.go
@@ -11,6 +11,7 @@ import (
"code.nonshy.com/nonshy/website/pkg/config"
"code.nonshy.com/nonshy/website/pkg/log"
+ "code.nonshy.com/nonshy/website/pkg/mail"
"code.nonshy.com/nonshy/website/pkg/models"
"code.nonshy.com/nonshy/website/pkg/redis"
"github.com/google/uuid"
@@ -175,7 +176,7 @@ func LoginUser(w http.ResponseWriter, r *http.Request, u *models.User) error {
}
// ImpersonateUser assumes the role of the user impersonated by an admin uid.
-func ImpersonateUser(w http.ResponseWriter, r *http.Request, u *models.User, impersonator *models.User) error {
+func ImpersonateUser(w http.ResponseWriter, r *http.Request, u *models.User, impersonator *models.User, reason string) error {
if u == nil || u.ID == 0 {
return errors.New("not a valid user account")
}
@@ -189,6 +190,40 @@ func ImpersonateUser(w http.ResponseWriter, r *http.Request, u *models.User, imp
sess.Impersonator = impersonator.ID
sess.Save(w)
+ // Issue an admin notification that this has happened.
+ // NOTE: not DRY compared to contact.go
+ fb := &models.Feedback{
+ Intent: "report",
+ Subject: "'Impersonate user' has been used",
+ TableName: "users",
+ TableID: impersonator.ID,
+ Message: fmt.Sprintf(
+ "The admin user **%s** (id:%d) has impersonated user **%s** (id:%d)\n\n"+
+ "The reason they have given:\n\n%s",
+ impersonator.Username, impersonator.ID,
+ u.Username, u.ID, reason,
+ ),
+ }
+
+ if err := models.CreateFeedback(fb); err != nil {
+ FlashError(w, r, "Couldn't create admin notification: %s", err)
+ }
+
+ // Email the admins.
+ if err := mail.Send(mail.Message{
+ To: config.Current.AdminEmail,
+ Subject: "Admin 'user impersonate' has been used",
+ Template: "email/admin_impersonate.html",
+ Data: map[string]interface{}{
+ "Impersonator": impersonator,
+ "User": u,
+ "Reason": reason,
+ "AdminURL": config.Current.BaseURL + "/admin/feedback",
+ },
+ }); err != nil {
+ log.Error("/contact page: couldn't send email: %s", err)
+ }
+
return u.Save()
}
diff --git a/web/templates/admin/dashboard.html b/web/templates/admin/dashboard.html
index ed28c30..cb272a6 100644
--- a/web/templates/admin/dashboard.html
+++ b/web/templates/admin/dashboard.html
@@ -11,12 +11,112 @@
+
+
+
+
+
+
+ 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.
+
diff --git a/web/templates/admin/user_actions.html b/web/templates/admin/user_actions.html
index dbcdb5b..8c40018 100644
--- a/web/templates/admin/user_actions.html
+++ b/web/templates/admin/user_actions.html
@@ -65,8 +65,37 @@
Please respect user privacy and only impersonate an account as needed to diagnose
a customer support issue or similar.
+
+
+ This event is logged and will be noticed.
+
+ Write an explanation below why you are impersonating this user. It will
+ be e-mailed to the admin mailing list and trigger an admin notification
+ and be logged as a Report to
+ the admin dashboard. Reports can be acknowledged, but not deleted.
+
+
+ Good reasons may include:
+
+
+ I need to diagnose a bug report given by one of our users
+ (briefly describe what the bug is; e.g. user saw a database error
+ at the top of a page).
+
+
+ A user has reported a Direct Message conversation and I need to
+ take a look at the context. (There is no other way to read user DMs)
+
+
+
+
+
- {{else if and (not .IsSiteGallery) (not .IsMyPrivateUnlockedFor)}}
+ {{else if not .IsSiteGallery}}