Skip to content

Commit 972c16d

Browse files
authored
Merge pull request #66 from diging/develop
Develop
2 parents d329a0d + 225d9ed commit 972c16d

32 files changed

+1180
-956
lines changed

cookies/authorization.py

Lines changed: 80 additions & 174 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from guardian.shortcuts import (get_perms, remove_perm, assign_perm,
1010
get_objects_for_user)
1111
from guardian.utils import get_user_obj_perms_model
12+
from guardian.models import Permission, UserObjectPermission
1213

1314
from cookies.models import *
1415
from collections import defaultdict
@@ -54,7 +55,11 @@ def is_owner(user, obj):
5455
-------
5556
bool
5657
"""
57-
return getattr(obj, 'created_by', None) == user or user.is_superuser
58+
return (isinstance(obj, Collection) and getattr(obj, 'created_by', None) == user) or user.is_superuser
59+
60+
61+
def is_public(obj):
62+
return getattr(obj, 'public', False)
5863

5964

6065
def check_authorization(auth, user, obj):
@@ -71,10 +76,39 @@ def check_authorization(auth, user, obj):
7176
-------
7277
bool
7378
"""
79+
7480
if auth == 'is_owner':
7581
return is_owner(user, obj)
76-
auth = auth_label(auth, obj)
77-
return user.is_superuser or is_owner(user, obj) or user.has_perm(auth, obj)
82+
83+
if isinstance(obj, Resource):
84+
if getattr(obj, 'belongs_to', False):
85+
auth = auth_label(auth, obj.belongs_to)
86+
_authorized = check_authorization(auth, user, obj.belongs_to)
87+
else:
88+
# If the Resource has no Collection, only the owner or admin can
89+
# access it.
90+
_authorized = False
91+
elif isinstance(obj, ConceptEntity):
92+
resource_type = ContentType.objects.get_for_model(Resource)
93+
resource = obj.relations_to.filter(source_type=resource_type).first().source
94+
_authorized = check_authorization(auth, user, resource)
95+
elif isinstance(obj, Relation):
96+
_authorized = check_authorization(auth, user, obj.source)
97+
elif isinstance(obj, Value):
98+
_check = lambda o: check_authorization(auth, user, o)
99+
_sources = [relation.source for relation in obj.relations_to.all() if not isinstance(relation.source, Value)]
100+
_targets = [relation.target for relation in obj.relations_from.all() if not isinstance(relation.target, Value)]
101+
_authorized = all(map(_check, _sources)) and all(map(_check, _targets)) and (_sources or _targets)
102+
elif obj is None:
103+
_authorized = False
104+
else:
105+
auth = auth_label(auth, obj)
106+
_authorized = user.has_perm(auth, obj)
107+
return user.is_superuser or is_owner(user, obj) or _authorized or (is_public(obj) and 'view' in auth)
108+
109+
110+
def label_authorizations(auths, obj):
111+
return [auth_label(auth.split('_')[0], obj) for auth in auths]
78112

79113

80114
def auth_label(auth, obj):
@@ -98,167 +132,20 @@ def auth_label(auth, obj):
98132
_auth_map = {
99133
'view_conceptentity': 'view_entity',
100134
}
101-
if auth in SHARED_AUTHORIZATIONS or '_' in auth: # Already labeled.
135+
if auth in SHARED_AUTHORIZATIONS: # Already labeled.
102136
return _auth_map.get(auth, auth)
103-
if isinstance(obj, ConceptEntity) and auth == 'view':
137+
auth = auth.split('_')[0]
138+
if (isinstance(obj, ConceptEntity) or getattr(obj, 'model', None) is ConceptEntity) and auth == 'view':
104139
return 'view_entity'
105-
elif isinstance(obj, Collection) and auth == 'view':
140+
elif (isinstance(obj, Collection) or getattr(obj, 'model', None) is Collection) and auth == 'view':
106141
return 'view_resource'
107-
model_label = type(obj).__name__.lower()
142+
if isinstance(obj, QuerySet):
143+
model_label = obj.model.__name__.lower()
144+
else:
145+
model_label = type(obj).__name__.lower()
108146
return '%s_%s' % (auth, model_label)
109147

110148

111-
def _propagate_to_resources(auths, user, obj, **kwargs):
112-
"""
113-
Propagate authorizations from :class:`.Collection` instances to its
114-
related :class:`.Resource` instances.
115-
116-
Parameters
117-
----------
118-
auths : list
119-
A list of authorizations (str). Any authorizations not in this list
120-
will be removed from ``obj`` for ``user``.
121-
user : :class:`.User`
122-
obj : :class:`.Collection`
123-
by_user : :class:`.User`
124-
If provided, the ``change_authorizations`` auth will be enforced for
125-
this user.
126-
propagate : bool
127-
If ``True`` (default), authorizations will propagate to "children"
128-
of ``obj``. i.e. Collection -> Resource -> Relation -> ConceptEntity.
129-
130-
Returns
131-
-------
132-
None
133-
"""
134-
logger.debug('_propagate_to_resources: %s' % ', '.join(auths))
135-
by_user = kwargs.get('by_user', None)
136-
child_auths = map(lambda a: a.replace('collection', 'resource'), auths)
137-
children = obj.resources.all()
138-
if by_user:
139-
children = apply_filter(by_user, 'change_authorizations', children)
140-
logger.debug('child auths %s' % ', '.join(child_auths))
141-
update_authorizations(child_auths, user, children, **kwargs)
142-
143-
144-
def _propagate_to_relations(auths, user, obj, **kwargs):
145-
"""
146-
Propagate authorizations from :class:`.Resource` instances to its
147-
related :class:`.Relation` instances.
148-
149-
Parameters
150-
----------
151-
auths : list
152-
A list of authorizations (str). Any authorizations not in this list
153-
will be removed from ``obj`` for ``user``.
154-
user : :class:`.User`
155-
obj : :class:`.Resource`
156-
by_user : :class:`.User`
157-
If provided, the ``change_authorizations`` auth will be enforced for
158-
this user.
159-
propagate : bool
160-
If ``True`` (default), authorizations will propagate to "children"
161-
of ``obj``. i.e. Collection -> Resource -> Relation -> ConceptEntity.
162-
163-
Returns
164-
-------
165-
None
166-
"""
167-
by_user = kwargs.get('by_user', None)
168-
child_auths = map(lambda a: a.replace('resource', 'relation'), auths)
169-
children_from = obj.relations_from.all()
170-
children_to = obj.relations_to.all()
171-
if by_user:
172-
children_from = apply_filter(by_user, 'change_authorizations', children_from)
173-
children_to = apply_filter(by_user, 'change_authorizations', children_to)
174-
175-
update_authorizations(child_auths, user, children_from, **kwargs)
176-
update_authorizations(child_auths, user, children_to, **kwargs)
177-
178-
179-
def _propagate_to_content(auths, user, obj, **kwargs):
180-
by_user = kwargs.get('by_user', None)
181-
logger.debug(repr(kwargs))
182-
logger.debug(repr(auths))
183-
for relation in obj.content.all():
184-
update_authorizations(auths, user, relation.content_resource, **kwargs)
185-
186-
187-
188-
def _propagate_to_entities(auths, user, obj, **kwargs):
189-
"""
190-
Propagate authorizations from :class:`.Relation` instances to ``source``
191-
and/or ``target`` :class:`.ConceptEntity` instances.
192-
193-
Parameters
194-
----------
195-
auths : list
196-
A list of authorizations (str). Any authorizations not in this list
197-
will be removed from ``obj`` for ``user``.
198-
user : :class:`.User`
199-
obj : :class:`.Relation`
200-
by_user : :class:`.User`
201-
If provided, the ``change_authorizations`` auth will be enforced for
202-
this user.
203-
propagate : bool
204-
If ``True`` (default), authorizations will propagate to "children"
205-
of ``obj``. i.e. Collection -> Resource -> Relation -> ConceptEntity.
206-
207-
Returns
208-
-------
209-
None
210-
"""
211-
by_user = kwargs.get('by_user', None)
212-
child_auths = map(lambda a: a.replace('relation', 'conceptentity'), auths)
213-
for field in ['source', 'target']:
214-
child = getattr(obj, field)
215-
if isinstance(child, ConceptEntity):
216-
if by_user:
217-
if check_authorization('change_authorizations', by_user, child):
218-
continue
219-
update_authorizations(child_auths, user, child, **kwargs)
220-
221-
222-
def _propagate_to_children(auths, user, obj, **kwargs):
223-
"""
224-
Propagate authorizations to child objects.
225-
226-
Parameters
227-
----------
228-
auths : list
229-
A list of authorizations (str). Any authorizations not in this list
230-
will be removed from ``obj`` for ``user``.
231-
user : :class:`.User`
232-
obj : Model instance or :class:`.QuerySet`
233-
by_user : :class:`.User`
234-
If provided, the ``change_authorizations`` auth will be enforced for
235-
this user.
236-
propagate : bool
237-
If ``True`` (default), authorizations will propagate to "children"
238-
of ``obj``. i.e. Collection -> Resource -> Relation -> ConceptEntity.
239-
240-
Returns
241-
-------
242-
None
243-
"""
244-
if isinstance(obj, Collection):
245-
logger.debug('Collection -> Resources')
246-
_propagate_to_resources(auths, user, obj, **kwargs)
247-
elif isinstance(obj, Resource):
248-
logger.debug('Resource -> Relation')
249-
_propagate_to_relations(auths, user, obj, **kwargs)
250-
251-
logger.debug('Resource -> Content')
252-
_propagate_to_content(auths, user, obj, **kwargs)
253-
elif isinstance(obj, Relation):
254-
logger.debug('Relation -> ConceptEntity')
255-
_propagate_to_entities(auths, user, obj, **kwargs)
256-
elif isinstance(obj, ConceptEntity):
257-
logger.debug('ConceptEntity -> Relation')
258-
kwargs.pop('propagate', None)
259-
_propagate_to_relations(auths, user, obj, **kwargs)
260-
261-
262149
def update_authorizations(auths, user, obj, **kwargs):
263150
"""
264151
Replace the current authorizations for ``user`` on ``obj`` with ``auths``.
@@ -273,21 +160,17 @@ def update_authorizations(auths, user, obj, **kwargs):
273160
by_user : :class:`.User`
274161
If provided, the ``change_authorizations`` auth will be enforced for
275162
this user.
276-
propagate : bool
277-
If ``True`` (default), authorizations will propagate to "children"
278-
of ``obj``. i.e. Collection -> Resource -> Relation -> ConceptEntity.
279163
280164
Returns
281165
-------
282166
None
283167
"""
284-
168+
285169
logger.debug('update authorizations for %s with %s for %s' % \
286170
(repr(obj), ' '.join(auths), repr(user)))
287171

288172
# ``auths`` may or may not have model-specific auth labels.
289-
labeled_auths = [auth_label(auth, obj) for auth in auths]
290-
173+
labeled_auths = label_authorizations(auths, obj)
291174
by_user = kwargs.get('by_user', None)
292175
if by_user and isinstance(obj, QuerySet):
293176
obj = apply_filter(by_user, 'change_authorizations', obj)
@@ -311,10 +194,6 @@ def update_authorizations(auths, user, obj, **kwargs):
311194
msg = '"%s" not a valid auth for %s' % (auth, repr(obj))
312195
raise ValueError(msg)
313196

314-
if kwargs.get('propagate', True):
315-
logger.debug('propagate')
316-
_propagate_to_children(auths, user, obj, **kwargs)
317-
318197

319198
def list_authorizations(obj, user=None):
320199
"""
@@ -362,6 +241,9 @@ def apply_filter(user, auth, queryset):
362241
"""
363242
Limit ``queryset`` to those objects for which ``user`` has ``permission``.
364243
244+
As of 0.4 this depends entirely on the :class:`.Collection` to which
245+
objects belong.
246+
365247
Parameters
366248
----------
367249
user : :class:`django.contrib.auth.models.User`
@@ -373,18 +255,42 @@ def apply_filter(user, auth, queryset):
373255
:class:`django.db.models.QuerySet`
374256
375257
"""
376-
# TODO: implement a more general way to correct these legacy auth names.
377-
if getattr(queryset, 'model', None) == Collection \
378-
and auth == 'view_collection':
379-
auth = 'view_resource'
380-
258+
# Everything depends on the Collection now.
259+
auth = auth_label(auth, Collection.objects.first())
381260
if user.is_superuser:
382261
return queryset
383262
if type(queryset) is list:
384263
return [obj for obj in queryset if check_authorization(auth, user, obj)]
385264
if auth == 'is_owner':
386265
return queryset.filter(created_by_id=user.id)
387-
return get_objects_for_user(user, auth, queryset)
266+
267+
ctype = ContentType.objects.get_for_model(Collection)
268+
rtype = ContentType.objects.get_for_model(Resource)
269+
perm = Permission.objects.get(codename=auth, content_type_id=ctype)
270+
perms = UserObjectPermission.objects.filter(user_id=user.id,
271+
permission_id=perm.id,
272+
content_type_id=ctype.id)
273+
274+
# For some reason Guardian stores related primary keys as strings; without
275+
# mapping back to int this will cause Postgres to choke.
276+
collection_pks = map(int, perms.values_list('object_pk', flat=True))
277+
if queryset.model is Collection:
278+
return queryset.filter(pk__in=collection_pks)
279+
elif queryset.model is Resource:
280+
q = Q(belongs_to__id__in=collection_pks)
281+
else: # Traverse back up to the Collection via its Resources.
282+
resources = Resource.objects.filter(belongs_to__id__in=collection_pks)\
283+
.values_list('id', flat=True)
284+
285+
if queryset.model is ConceptEntity:
286+
q = Q(relations_to__source_instance_id__in=resources, relations_to__source_type=rtype) \
287+
| Q(relations_from__target_instance_id__in=resources, relations_from__target_type=rtype)
288+
elif queryset.model is Relation:
289+
q = Q(source_instance_id__in=resources) \
290+
| Q(target_instance_id__in=resources)
291+
elif queryset.model is Value:
292+
q = Q(relations_to__source_instance_id__in=resources)
293+
return queryset.filter(q).distinct()
388294

389295

390296
def make_nonpublic(obj):

cookies/entities.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
_tokenize = lambda s: [part for part in s.split() if len(part) > 2]
1010

1111

12-
def suggest_similar(entity):
12+
def suggest_similar(entity, qs=None):
1313
"""
1414
Attempt to find :class:`.ConceptEntity` instances that are similar to
1515
``entity``.
@@ -24,14 +24,19 @@ def suggest_similar(entity):
2424
A list of :class:`.ConceptEntity` instances. Ordered by descending
2525
similarity.
2626
"""
27+
28+
rep_ids = Identity.objects.filter(entities=entity.id).values_list('representative', flat=True)
29+
identities = Identity.objects.filter(representative__id__in=rep_ids)
30+
if not qs:
31+
qs = ConceptEntity.objects.all()
2732
name = _normalize(entity.name)
2833
suggestions = []
2934
# suggestions += [o.id ConceptEntity.objects.filter(Q(name__icontains=name) & ~Q(id=entity.id))
3035

3136
name_parts = _tokenize(name)
3237
_id = lambda o: o.id
33-
_name = lambda o: o.name
34-
_find = lambda part: ConceptEntity.objects.filter(Q(name__icontains=part) & ~Q(id=entity.id))
38+
_name = lambda o: o.name #similar_entities = similar_entities.filter()
39+
_find = lambda part: qs.filter(Q(name__icontains=part) & ~Q(id=entity.id) & ~Q(identities__id__in=identities))
3540
for name, group in groupby(sorted(chain(*[_find(part) for part in name_parts]), key=_name), key=_name):
3641
for pk, subgroup in groupby(sorted(group, key=_id), key=_id):
3742
subgroup = [o for o in subgroup]

0 commit comments

Comments
 (0)