prep hashed ip and id
This commit is contained in:
@@ -126,7 +126,7 @@ func (a *admin) checkBlocked(siteID string, user store.User) bool {
|
||||
return a.dataService.IsBlocked(siteID, user.ID)
|
||||
}
|
||||
|
||||
// processes comments and hides text of all comments for blocked users.
|
||||
// post-processes comments, hides text of all comments for blocked users,
|
||||
// resets score and votes too. Also hides sensitive info for non-admin users
|
||||
func (a *admin) alterComments(comments []store.Comment, r *http.Request) (res []store.Comment) {
|
||||
res = make([]store.Comment, len(comments))
|
||||
@@ -145,11 +145,6 @@ func (a *admin) alterComments(comments []store.Comment, r *http.Request) (res []
|
||||
c.Deleted = true
|
||||
}
|
||||
|
||||
// set default avatar
|
||||
if c.User.Picture == "" {
|
||||
c.User.Picture = a.defAvatarURL
|
||||
}
|
||||
|
||||
// hide info from non-admins
|
||||
if !isAdmin {
|
||||
c.User.IP = ""
|
||||
|
||||
+14
-15
@@ -36,8 +36,8 @@ type Rest struct {
|
||||
Cache rest.LoadingCache
|
||||
WebRoot string
|
||||
|
||||
httpServer *http.Server
|
||||
mod admin
|
||||
httpServer *http.Server
|
||||
amdminService admin
|
||||
}
|
||||
|
||||
// Run the lister and request's router, activate rest server
|
||||
@@ -48,6 +48,10 @@ func (s *Rest) Run(port int) {
|
||||
log.Printf("[DEBUG] admins %+v", s.Authenticator.Admins)
|
||||
}
|
||||
|
||||
s.amdminService = admin{dataService: s.DataService, exporter: s.Exporter, cache: s.Cache,
|
||||
defAvatarURL: s.Authenticator.AvatarProxy.Default(),
|
||||
}
|
||||
|
||||
router := chi.NewRouter()
|
||||
router.Use(middleware.RealIP, Recoverer)
|
||||
router.Use(middleware.Throttle(1000), middleware.Timeout(60*time.Second))
|
||||
@@ -92,14 +96,9 @@ func (s *Rest) Run(port int) {
|
||||
rauth.Put("/comment/{id}", s.updateCommentCtrl)
|
||||
rauth.Get("/user", s.userInfoCtrl)
|
||||
rauth.Put("/vote/{id}", s.voteCtrl)
|
||||
|
||||
// admin routes, admin users only
|
||||
s.mod = admin{
|
||||
dataService: s.DataService,
|
||||
exporter: s.Exporter,
|
||||
cache: s.Cache,
|
||||
defAvatarURL: s.Authenticator.AvatarProxy.Default(),
|
||||
}
|
||||
rauth.Mount("/admin", s.mod.routes(s.Authenticator.AdminOnly))
|
||||
rauth.Mount("/admin", s.amdminService.routes(s.Authenticator.AdminOnly))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -129,14 +128,14 @@ func (s *Rest) createCommentCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
comment.Prepare() // clean all fields user not suppoed to set
|
||||
comment.PrepareUntrusted() // clean all fields user not suppoed to set
|
||||
comment.User = user
|
||||
comment.User.IP = strings.Split(r.RemoteAddr, ":")[0]
|
||||
comment.Text = string(blackfriday.Run([]byte(comment.Text), blackfriday.WithNoExtensions())) // render markdown
|
||||
log.Printf("[DEBUG] create comment %+v", comment)
|
||||
|
||||
// check if user blocked
|
||||
if s.mod.checkBlocked(comment.Locator.SiteID, comment.User) {
|
||||
if s.amdminService.checkBlocked(comment.Locator.SiteID, comment.User) {
|
||||
rest.SendErrorJSON(w, r, http.StatusForbidden, errors.New("rejected"), "user blocked")
|
||||
return
|
||||
}
|
||||
@@ -220,7 +219,7 @@ func (s *Rest) findCommentsCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
maskedComments := s.mod.alterComments(comments, r)
|
||||
maskedComments := s.amdminService.alterComments(comments, r)
|
||||
var b []byte
|
||||
switch r.URL.Query().Get("format") {
|
||||
case "tree":
|
||||
@@ -253,7 +252,7 @@ func (s *Rest) lastCommentsCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
comments = s.mod.alterComments(comments, r)
|
||||
comments = s.amdminService.alterComments(comments, r)
|
||||
return encodeJSONWithHTML(comments)
|
||||
})
|
||||
|
||||
@@ -278,7 +277,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.alterComments([]store.Comment{comment}, r)[0]
|
||||
comment = s.amdminService.alterComments([]store.Comment{comment}, r)[0]
|
||||
render.Status(r, http.StatusOK)
|
||||
renderJSONWithHTML(w, r, comment)
|
||||
}
|
||||
@@ -306,7 +305,7 @@ func (s *Rest) findUserCommentsCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
comments = s.mod.alterComments(comments, r)
|
||||
comments = s.amdminService.alterComments(comments, r)
|
||||
resp.Comments, resp.Count = comments, count
|
||||
return encodeJSONWithHTML(resp)
|
||||
})
|
||||
|
||||
+26
-3
@@ -1,7 +1,11 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
"hash/crc64"
|
||||
"html/template"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -59,9 +63,9 @@ type BlockedUser struct {
|
||||
Timestamp time.Time `json:"time"`
|
||||
}
|
||||
|
||||
// Prepare comment received from untrusted source by clearing all autogen fields and
|
||||
// reset everyting users not supposed to provide
|
||||
func (c *Comment) Prepare() {
|
||||
// PrepareUntrusted preprocess comment received from untrusted source by clearing all
|
||||
// autogen fields and reset everyting users not supposed to provide
|
||||
func (c *Comment) PrepareUntrusted() {
|
||||
c.ID = "" // don't allow user to define ID, force auto-gen
|
||||
c.Timestamp = time.Time{} // reset time, force auto-gen
|
||||
c.Votes = make(map[string]bool)
|
||||
@@ -84,6 +88,25 @@ func (c *Comment) Sanitize() {
|
||||
c.Text = strings.Replace(c.Text, "\t", "", -1)
|
||||
}
|
||||
|
||||
// HashUserFields replace sensitive fields with hashes
|
||||
func (c *Comment) HashUserFields() {
|
||||
|
||||
hashVal := func(val string) string {
|
||||
if _, err := strconv.ParseUint(val, 16, 64); err == nil || val == "" {
|
||||
return val // already hashed
|
||||
}
|
||||
h := sha1.New()
|
||||
if _, err := h.Write([]byte(val)); err != nil {
|
||||
// fail back to crc64
|
||||
return fmt.Sprintf("%x", crc64.Checksum([]byte(val), crc64.MakeTable(crc64.ECMA)))
|
||||
}
|
||||
return fmt.Sprintf("%x", h.Sum(nil))
|
||||
}
|
||||
|
||||
c.User.IP = hashVal(c.User.IP)
|
||||
c.User.ID = hashVal(c.User.ID)
|
||||
}
|
||||
|
||||
// SetDeleted clears comment info, reset to deleted state
|
||||
func (c *Comment) SetDeleted() {
|
||||
c.Text = ""
|
||||
|
||||
@@ -32,7 +32,7 @@ func TestComment_Sanitize(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestComment_Prepare(t *testing.T) {
|
||||
func TestComment_PrepareUntrusted(t *testing.T) {
|
||||
comment := Comment{
|
||||
Text: `blah`,
|
||||
User: User{ID: "username"},
|
||||
@@ -46,7 +46,7 @@ func TestComment_Prepare(t *testing.T) {
|
||||
Votes: map[string]bool{"uu": true},
|
||||
}
|
||||
|
||||
comment.Prepare()
|
||||
comment.PrepareUntrusted()
|
||||
assert.Equal(t, "", comment.ID)
|
||||
assert.Equal(t, "p123", comment.ParentID)
|
||||
assert.Equal(t, "blah", comment.Text)
|
||||
@@ -58,3 +58,27 @@ func TestComment_Prepare(t *testing.T) {
|
||||
assert.Equal(t, User{ID: "username"}, comment.User)
|
||||
|
||||
}
|
||||
|
||||
func TestComment_HashUserFields(t *testing.T) {
|
||||
tbl := []struct {
|
||||
inp Comment
|
||||
out Comment
|
||||
}{
|
||||
{inp: Comment{}, out: Comment{}},
|
||||
{
|
||||
inp: Comment{
|
||||
Text: "blah",
|
||||
User: User{ID: "my id", IP: "127.0.0.1"},
|
||||
},
|
||||
out: Comment{
|
||||
Text: "blah",
|
||||
User: User{ID: "de58071dda71e1783b6deb931ddb48bb66966f79", IP: "4b84b15bff6ee5796152495a230e45e3d7e947d9"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for n, tt := range tbl {
|
||||
tt.inp.HashUserFields()
|
||||
assert.Equal(t, tt.out, tt.inp, "check #%d", n)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user