diff --git a/app/rest/server/admin.go b/app/rest/server/admin.go index bd3a0134..b2bcf504 100644 --- a/app/rest/server/admin.go +++ b/app/rest/server/admin.go @@ -126,14 +126,28 @@ func (a *admin) checkBlocked(siteID string, user store.User) bool { } // processes comments and hides text of all comments for blocked users. -// resets score and votes too -func (a *admin) maskBlockedUsers(comments []store.Comment) (res []store.Comment) { +// resets score and votes too. Also hides sensitive info for non-admin users +func (a *admin) maskInfo(comments []store.Comment, r *http.Request) (res []store.Comment) { res = make([]store.Comment, len(comments)) + + isAdmin := false + if user, err := rest.GetUserInfo(r); err == nil && user.Admin { // make seprate cache key for admins + isAdmin = true + } + for i, c := range comments { + + // process blocked users if a.dataService.IsBlocked(c.Locator.SiteID, c.User.ID) { c.Mask() c.User.Blocked = true } + + // hide info from non-admins + if !isAdmin { + c.User.IP = "" + } + res[i] = c } return res diff --git a/app/rest/server/rest.go b/app/rest/server/rest.go index 617cf13b..249e7646 100644 --- a/app/rest/server/rest.go +++ b/app/rest/server/rest.go @@ -228,12 +228,12 @@ func (s *Rest) findCommentsCtrl(w http.ResponseWriter, r *http.Request) { locator := store.Locator{SiteID: r.URL.Query().Get("site"), URL: r.URL.Query().Get("url")} log.Printf("[DEBUG] get comments for %+v", locator) - data, err := s.Cache.Get(r.URL.String(), time.Hour, func() ([]byte, error) { + data, err := s.Cache.Get(s.urlKey(r), time.Hour, func() ([]byte, error) { comments, e := s.DataService.Find(locator, r.URL.Query().Get("sort")) if e != nil { return nil, e } - maskedComments := s.mod.maskBlockedUsers(comments) + maskedComments := s.mod.maskInfo(comments, r) var b []byte switch r.URL.Query().Get("format") { case "tree": @@ -261,12 +261,12 @@ func (s *Rest) lastCommentsCtrl(w http.ResponseWriter, r *http.Request) { max = 0 } - data, err := s.Cache.Get(r.URL.String(), time.Hour, func() ([]byte, error) { + data, err := s.Cache.Get(s.urlKey(r), time.Hour, func() ([]byte, error) { comments, e := s.DataService.Last(r.URL.Query().Get("site"), max) if e != nil { return nil, e } - comments = s.mod.maskBlockedUsers(comments) + comments = s.mod.maskInfo(comments, r) return encodeJSONWithHTML(comments) }) @@ -291,7 +291,7 @@ func (s *Rest) commentByIDCtrl(w http.ResponseWriter, r *http.Request) { rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get comment by id") return } - comment = s.mod.maskBlockedUsers([]store.Comment{comment})[0] + comment = s.mod.maskInfo([]store.Comment{comment}, r)[0] render.Status(r, http.StatusOK) renderJSONWithHTML(w, r, comment) } @@ -309,12 +309,12 @@ func (s *Rest) findUserCommentsCtrl(w http.ResponseWriter, r *http.Request) { log.Printf("[DEBUG] get comments for userID %s, %s", userID, siteID) - data, err := s.Cache.Get(r.URL.String(), time.Hour, func() ([]byte, error) { + data, err := s.Cache.Get(s.urlKey(r), time.Hour, func() ([]byte, error) { comments, count, e := s.DataService.User(siteID, userID) if e != nil { return nil, e } - comments = s.mod.maskBlockedUsers(comments) + comments = s.mod.maskInfo(comments, r) resp.Comments, resp.Count = comments, count return encodeJSONWithHTML(resp) }) @@ -374,7 +374,7 @@ func (s *Rest) countCtrl(w http.ResponseWriter, r *http.Request) { func (s *Rest) listCtrl(w http.ResponseWriter, r *http.Request) { siteID := r.URL.Query().Get("site") - data, err := s.Cache.Get(r.URL.String(), 8*time.Hour, func() ([]byte, error) { + data, err := s.Cache.Get(s.urlKey(r), 8*time.Hour, func() ([]byte, error) { posts, e := s.DataService.List(siteID) if e != nil { return nil, e @@ -471,6 +471,16 @@ func (s *Rest) addFileServer(r chi.Router, path string, root http.FileSystem) { })) } +// urlKey gets url from request to use is as cache key +// admins will have separate keys in order tp prevent leak of admin-only data to regular users +func (s *Rest) urlKey(r *http.Request) string { + key := r.URL.String() + if user, err := rest.GetUserInfo(r); err == nil && user.Admin { // make seprate cache key for admins + key = "admin!!" + key + } + return key +} + // renderJSONWithHTML allows html tags and forces charset=utf-8 func renderJSONWithHTML(w http.ResponseWriter, r *http.Request, v interface{}) { data, err := encodeJSONWithHTML(v) diff --git a/app/rest/server/rest_test.go b/app/rest/server/rest_test.go index ee085a95..826cd62a 100644 --- a/app/rest/server/rest_test.go +++ b/app/rest/server/rest_test.go @@ -82,7 +82,7 @@ func TestServer_CreateAndGet(t *testing.T) { assert.Equal(t, "

test 123 http://radio-t.com

", comment.Text) assert.Equal(t, store.User{Name: "developer one", ID: "dev", Picture: "https://friends.radio-t.com/resources/images/rt_logo_64.png", - Profile: "https://radio-t.com/info/", Admin: true, Blocked: false, IP: ""}, + Profile: "https://radio-t.com/info/", Admin: true, Blocked: false, IP: "127.0.0.1"}, comment.User) t.Logf("%+v", comment) } diff --git a/app/store/comment.go b/app/store/comment.go index ebdd5df0..100fb765 100644 --- a/app/store/comment.go +++ b/app/store/comment.go @@ -34,10 +34,10 @@ type User struct { Name string `json:"name"` ID string `json:"id"` Picture string `json:"picture"` - Profile string `json:"profile"` + Profile string `json:"profile,omitempty"` Admin bool `json:"admin"` Blocked bool `json:"block,omitempty"` - IP string `json:"-"` + IP string `json:"ip,omitempty"` } // Edit indication @@ -64,6 +64,16 @@ type NotifUser struct { Destination string `json:"destination"` } +// NotifScope defines "enum" of notification scopes +type NotifScope int + +// All NotifScope values +const ( + ScopeSite NotifScope = 1 + ScopePost NotifScope = 2 + ScopeReply NotifScope = 3 +) + // Sanitize clean dangerous html/js from the comment func (c *Comment) Sanitize() { p := bluemonday.UGCPolicy() diff --git a/app/store/store.go b/app/store/store.go index d4753c37..2ea988ff 100644 --- a/app/store/store.go +++ b/app/store/store.go @@ -33,23 +33,13 @@ type Admin interface { Blocked(siteID string) ([]BlockedUser, error) // get list of blocked users } -// Notifier defines all store ops for update modifications +// Notifier defines all store ops to store/retrive notification info for users type Notifier interface { Set(locator Locator, user NotifUser, scope NotifScope, status bool) error // subscribe / unsubscribe user to locator updates Status(locator Locator, userID string) bool // get subscription status for user & locator List(locator Locator) ([]NotifUser, error) // list all subscribed users } -// NotifScope defines "enum" of notification scopes -type NotifScope int - -// All NotifScope values -const ( - ScopeSite NotifScope = 1 - ScopePost NotifScope = 2 - ScopeReply NotifScope = 3 -) - func sortComments(comments []Comment, sortFld string) []Comment { sort.Slice(comments, func(i, j int) bool { switch sortFld {