1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-07-23 23:29:42 +02:00

Implement category-based permissioning for search results

Only see what you can see.

Co-Authored-By: Saul S <sauls8t@users.noreply.github.com>
This commit is contained in:
HarveyKandola 2018-06-22 17:01:26 +01:00
parent ae50b889c5
commit 467acec3c4
5 changed files with 95 additions and 9 deletions

View file

@ -77,8 +77,8 @@ func (s Scope) GetAllBySpace(ctx domain.RequestContext, spaceID string) (c []cat
WHERE orgid=? AND labelid=? WHERE orgid=? AND labelid=?
AND labelid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN ( AND labelid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN (
SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='space' AND action='view' UNION ALL SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='space' AND action='view' UNION ALL
SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.who='role' AND p.location='space' SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid
AND p.action='view' AND (r.userid=? OR r.userid='0') WHERE p.orgid=? AND p.who='role' AND p.location='space' AND p.action='view' AND (r.userid=? OR r.userid='0')
)) ))
ORDER BY category`, ctx.OrgID, spaceID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID) ORDER BY category`, ctx.OrgID, spaceID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
@ -92,6 +92,28 @@ func (s Scope) GetAllBySpace(ctx domain.RequestContext, spaceID string) (c []cat
return return
} }
// GetByOrg returns all categories accessible by user for their org.
func (s Scope) GetByOrg(ctx domain.RequestContext, userID string) (c []category.Category, err error) {
err = s.Runtime.Db.Select(&c, `
SELECT id, refid, orgid, labelid, category, created, revised FROM category
WHERE orgid=?
AND refid IN (SELECT refid FROM permission WHERE orgid=? AND location='category' AND refid IN (
SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='category' UNION ALL
SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid
WHERE p.orgid=? AND p.who='role' AND p.location='category' AND (r.userid=? OR r.userid='0')
))
ORDER BY category`, ctx.OrgID, ctx.OrgID, ctx.OrgID, userID, ctx.OrgID, userID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute select categories for org %s", ctx.OrgID))
}
return
}
// Update saves category name change. // Update saves category name change.
func (s Scope) Update(ctx domain.RequestContext, c category.Category) (err error) { func (s Scope) Update(ctx domain.RequestContext, c category.Category) (err error) {
c.Revised = time.Now().UTC() c.Revised = time.Now().UTC()
@ -255,3 +277,25 @@ func (s Scope) GetSpaceCategoryMembership(ctx domain.RequestContext, spaceID str
return return
} }
// GetOrgCategoryMembership returns category/document associations within organization.
func (s Scope) GetOrgCategoryMembership(ctx domain.RequestContext, userID string) (c []category.Member, err error) {
err = s.Runtime.Db.Select(&c, `
SELECT id, refid, orgid, labelid, categoryid, documentid, created, revised FROM categorymember
WHERE orgid=?
AND labelid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN (
SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='space' AND action='view' UNION ALL
SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.who='role' AND p.location='space'
AND p.action='view' AND (r.userid=? OR r.userid='0')
))
ORDER BY documentid`, ctx.OrgID, ctx.OrgID, ctx.OrgID, userID, ctx.OrgID, userID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("select all category/document membership for organization %s", ctx.OrgID))
}
return
}

View file

@ -418,6 +418,7 @@ func (h *Handler) SearchDocuments(w http.ResponseWriter, r *http.Request) {
return return
} }
// Get search criteria.
options := search.QueryOptions{} options := search.QueryOptions{}
err = json.Unmarshal(body, &options) err = json.Unmarshal(body, &options)
if err != nil { if err != nil {
@ -425,24 +426,30 @@ func (h *Handler) SearchDocuments(w http.ResponseWriter, r *http.Request) {
h.Runtime.Log.Error(method, err) h.Runtime.Log.Error(method, err)
return return
} }
options.Keywords = strings.TrimSpace(options.Keywords) options.Keywords = strings.TrimSpace(options.Keywords)
// Get documents for search criteria.
results, err := h.Store.Search.Documents(ctx, options) results, err := h.Store.Search.Documents(ctx, options)
if err != nil { if err != nil {
h.Runtime.Log.Error(method, err) h.Runtime.Log.Error(method, err)
} }
// Put in slugs for easy UI display of search URL // Generate slugs for search URL.
for key, result := range results { for key, result := range results {
results[key].DocumentSlug = stringutil.MakeSlug(result.Document) results[key].DocumentSlug = stringutil.MakeSlug(result.Document)
results[key].SpaceSlug = stringutil.MakeSlug(result.Space) results[key].SpaceSlug = stringutil.MakeSlug(result.Space)
} }
// Record user search history // Remove documents that cannot be seen due to lack of
// category view/access permission.
cats, err := h.Store.Category.GetByOrg(ctx, ctx.UserID)
members, err := h.Store.Category.GetOrgCategoryMembership(ctx, ctx.UserID)
filtered := indexer.FilterCategoryProtected(results, cats, members)
// Record user search history.
if !options.SkipLog { if !options.SkipLog {
if len(results) > 0 { if len(filtered) > 0 {
go h.recordSearchActivity(ctx, results, options.Keywords) go h.recordSearchActivity(ctx, filtered, options.Keywords)
} else { } else {
ctx.Transaction, err = h.Runtime.Db.Beginx() ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil { if err != nil {
@ -468,7 +475,7 @@ func (h *Handler) SearchDocuments(w http.ResponseWriter, r *http.Request) {
h.Store.Audit.Record(ctx, audit.EventTypeSearch) h.Store.Audit.Record(ctx, audit.EventTypeSearch)
response.WriteJSON(w, results) response.WriteJSON(w, filtered)
} }
// Record search request once per document. // Record search request once per document.

View file

@ -151,7 +151,7 @@ func (s Scope) DeleteContent(ctx domain.RequestContext, pageID string) (err erro
} }
// Documents searches the documents that the client is allowed to see, using the keywords search string, then audits that search. // Documents searches the documents that the client is allowed to see, using the keywords search string, then audits that search.
// Visible documents include both those in the client's own organisation and those that are public, or whose visibility includes the client. // Visible documents include both those in the client's own organization and those that are public, or whose visibility includes the client.
func (s Scope) Documents(ctx domain.RequestContext, q search.QueryOptions) (results []search.QueryResult, err error) { func (s Scope) Documents(ctx domain.RequestContext, q search.QueryOptions) (results []search.QueryResult, err error) {
q.Keywords = strings.TrimSpace(q.Keywords) q.Keywords = strings.TrimSpace(q.Keywords)
if len(q.Keywords) == 0 { if len(q.Keywords) == 0 {

View file

@ -14,8 +14,10 @@ package search
import ( import (
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/model/attachment" "github.com/documize/community/model/attachment"
"github.com/documize/community/model/category"
"github.com/documize/community/model/doc" "github.com/documize/community/model/doc"
"github.com/documize/community/model/page" "github.com/documize/community/model/page"
sm "github.com/documize/community/model/search"
) )
// IndexDocument adds search indesd entries for document inserting title, tags and attachments as // IndexDocument adds search indesd entries for document inserting title, tags and attachments as
@ -103,3 +105,34 @@ func (m *Indexer) DeleteContent(ctx domain.RequestContext, pageID string) {
ctx.Transaction.Commit() ctx.Transaction.Commit()
} }
// FilterCategoryProtected removes search results that cannot be seen by user
// due to document cateogory viewing permissions.
func FilterCategoryProtected(results []sm.QueryResult, cats []category.Category, members []category.Member) (filtered []sm.QueryResult) {
filtered = []sm.QueryResult{}
for _, result := range results {
hasCategory := false
canSeeCategory := false
OUTER:
for _, m := range members {
if m.DocumentID == result.DocumentID {
hasCategory = true
for _, cat := range cats {
if cat.RefID == m.CategoryID {
canSeeCategory = true
continue OUTER
}
}
}
}
if !hasCategory || canSeeCategory {
filtered = append(filtered, result)
}
}
return
}

View file

@ -81,6 +81,8 @@ type CategoryStorer interface {
GetSpaceCategoryMembership(ctx RequestContext, spaceID string) (c []category.Member, err error) GetSpaceCategoryMembership(ctx RequestContext, spaceID string) (c []category.Member, err error)
RemoveDocumentCategories(ctx RequestContext, documentID string) (rows int64, err error) RemoveDocumentCategories(ctx RequestContext, documentID string) (rows int64, err error)
RemoveSpaceCategoryMemberships(ctx RequestContext, spaceID string) (rows int64, err error) RemoveSpaceCategoryMemberships(ctx RequestContext, spaceID string) (rows int64, err error)
GetByOrg(ctx RequestContext, userID string) (c []category.Category, err error)
GetOrgCategoryMembership(ctx RequestContext, userID string) (c []category.Member, err error)
} }
// PermissionStorer defines required methods for space/document permission management // PermissionStorer defines required methods for space/document permission management