Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Add individual record permissions to Solr index and search

  • Loading branch information...
commit 8334c16a7ebbf31ab799eb5dd8545f8c99de116b 1 parent 46f91a8
Andreas Knab authored
View
33 rooibos/solr/__init__.py
@@ -11,9 +11,35 @@
from rooibos.contrib.tagging.models import Tag, TaggedItem
from pysolr import Solr
from rooibos.util.progressbar import ProgressBar
+from rooibos.access.models import AccessControl
SOLR_EMPTY_FIELD_VALUE = 'unspecified'
+
+def object_acl_to_solr(obj):
+ content_type = ContentType.objects.get_for_model(obj)
+ acl = AccessControl.objects.filter(
+ content_type=content_type,
+ object_id=obj.id,
+ ).values_list('user_id', 'usergroup_id', 'read', 'write', 'manage')
+ result = dict(read=[], write=[], manage=[])
+ for user, group, read, write, manage in acl:
+ acct = 'u%d' % user if user else 'g%d' % group if group else 'anon'
+ if read != None:
+ result['read'].append(acct if read else acct.upper())
+ if write != None:
+ result['write'].append(acct if write else acct.upper())
+ if manage != None:
+ result['manage'].append(acct if manage else acct.upper())
+ if not result['read']:
+ result['read'].append('default')
+ if not result['write']:
+ result['write'].append('default')
+ if not result['manage']:
+ result['manage'].append('default')
+ return result
+
+
class SolrIndex():
def __init__(self):
@@ -176,7 +202,9 @@ def _record_to_solr(self, record, core_fields, groups, fieldvalues, media):
all_parents = [g.collection_id for g in groups]
parents = [g.collection_id for g in groups if not g.hidden]
# Combine the direct parents with (great-)grandparents
+ # 'collections' is used for access control, hidden collections deny access
doc['collections'] = list(reduce(lambda x, y: set(x) | set(y), [self.parent_groups[p] for p in parents], parents))
+ # 'allcollections' is used for collection filtering, can filter by hidden collections
doc['allcollections'] = list(reduce(lambda x, y: set(x) | set(y), [self.parent_groups[p] for p in all_parents], all_parents))
doc['presentations'] = record.presentationitem_set.all().distinct().values_list('presentation_id', flat=True)
if record.owner_id:
@@ -192,6 +220,11 @@ def _record_to_solr(self, record, core_fields, groups, fieldvalues, media):
# Creation and modification dates
doc['created'] = record.created
doc['modified'] = record.modified
+ # Access control
+ acl = object_acl_to_solr(record)
+ doc['acl_read'] = acl['read']
+ doc['acl_write'] = acl['write']
+ doc['acl_manage'] = acl['manage']
return doc
def _clean_string(self, s):
View
20 rooibos/solr/views.py
@@ -244,10 +244,24 @@ def build_keywords(q, k):
if not user.is_superuser:
collections = ' '.join(map(str, accessible_ids(user, Collection)))
c = []
- if collections: c.append('collections:(%s)' % collections)
- if user.id: c.append('owner:%s' % user.id)
+ if collections:
+ # access through readable collection when no record ACL set
+ c.append('collections:(%s) AND acl_read:default' % collections)
+ if user.id:
+ # access through ownership
+ c.append('owner:%s' % user.id)
+ # access through record ACL
+ groups = ' '.join(
+ 'g%d' % id for id in user.groups.values_list('id', flat=True)
+ )
+ if groups:
+ groups = '((%s) AND NOT (%s)) OR ' % (groups, groups.upper())
+ c.append('acl_read:((%su%d) AND NOT U%d)' % (groups, user.id, user.id))
+ else:
+ # access through record ACL
+ c.append('acl_read:anon')
if c:
- query = '(%s) AND %s' % (' OR '.join(c), query)
+ query = '((%s)) AND %s' % (') OR ('.join(c), query)
else:
query = 'id:"-1"'
View
5 solr/solr/conf/schema.xml
@@ -266,6 +266,11 @@
<field name="spell" type="textSpell" indexed="true" stored="true" multiValued="true"/>
+ <!-- Record-level access control fields -->
+ <field name="acl_read" type="string" indexed="true" stored="true" multiValued="true" />
+ <field name="acl_write" type="string" indexed="true" stored="true" multiValued="true" />
+ <field name="acl_manage" type="string" indexed="true" stored="true" multiValued="true" />
+
<!-- Dynamic field definitions. If a field name is not found, dynamicFields
will be used if the name matches any of the patterns.
RESTRICTION: the glob-like pattern in the name attribute must have
Please sign in to comment.
Something went wrong with that request. Please try again.