def test_editors_see_only_self_anon_and_owner_assignments(self): self.client.login(username='******', password='******') permission_list_response = self.client.get( self.get_asset_perm_assignment_list_url(self.asset), format='json') self.assertEqual(permission_list_response.status_code, status.HTTP_200_OK) results = permission_list_response.data assignable_perms = Asset.get_assignable_permissions() expected_perms = [] for user in [ self.admin, self.someuser, # Permissions assigned to self.anotheruser must not appear get_anonymous_user(), ]: user_perms = self.asset.get_perms(user) expected_perms.extend( (user.username, perm) for perm in set(user_perms).intersection(assignable_perms)) expected_perms = sorted(expected_perms, key=lambda element: (element[0], element[1])) obj_perms = [] for assignment in results: object_permission = self.url_to_obj(assignment.get('url')) obj_perms.append(( object_permission.user.username, object_permission.permission.codename, )) obj_perms = sorted(obj_perms, key=lambda element: (element[0], element[1])) self.assertEqual(expected_perms, obj_perms)
def test_anonymous_get_only_owner_s_assignments(self): self.client.logout() self.collection.assign_perm(get_anonymous_user(), PERM_VIEW_COLLECTION) permission_list_response = self.client.get( self.collection_permissions_list_url, format='json') self.assertEqual(permission_list_response.status_code, status.HTTP_200_OK) admin_perms = self.collection.get_perms(self.admin) results = permission_list_response.data # As an editor of the collection. `someuser` should see all. expected_perms = [] for admin_perm in admin_perms: if admin_perm in Collection.get_assignable_permissions(): expected_perms.append((self.admin.username, admin_perm)) expected_perms = sorted(expected_perms, key=lambda element: (element[0], element[1])) obj_perms = [] for assignment in results: object_permission = self.url_to_obj(assignment.get('url')) obj_perms.append((object_permission.user.username, object_permission.permission.codename)) obj_perms = sorted(obj_perms, key=lambda element: (element[0], element[1])) self.assertEqual(expected_perms, obj_perms)
def test_list_submissions_anonymous_asset_publicly_shared(self): self.client.logout() anonymous_user = get_anonymous_user() self.asset.assign_perm(anonymous_user, 'view_submissions') response = self.client.get(self.submission_url, {"format": "json"}) self.assertEqual(response.status_code, status.HTTP_200_OK) self.asset.remove_perm(anonymous_user, 'view_submissions')
def validate_parent(self, parent: Asset) -> Asset: request = self.context['request'] user = request.user if user.is_anonymous: user = get_anonymous_user() # Validate first if user can update the current parent if self.instance and self.instance.parent is not None: if not self.instance.parent.has_perm(user, PERM_CHANGE_ASSET): raise serializers.ValidationError( _('User cannot update current parent collection')) # Target collection is `None`, no need to check permissions if parent is None: return parent # `user` must have write access to target parent before being able to # move the asset. parent_perms = parent.get_perms(user) if PERM_VIEW_ASSET not in parent_perms: raise serializers.ValidationError(_('Target collection not found')) if PERM_CHANGE_ASSET not in parent_perms: raise serializers.ValidationError( _('User cannot update target parent collection')) return parent
def test_anonymous_get_only_owner_and_anonymous_assignments(self): self.client.logout() permission_list_response = self.client.get( self.get_asset_perm_assignment_list_url(self.asset), format='json' ) self.assertEqual(permission_list_response.status_code, status.HTTP_200_OK) admin = self.admin admin_perms = self.asset.get_perms(admin) anon = get_anonymous_user() anon_perms = self.asset.get_perms(anon) assignable_perms = self.asset.get_assignable_permissions() results = permission_list_response.data # Get admin permissions. expected_perms = [] for user, perms in [(anon, anon_perms), (admin, admin_perms)]: for perm in perms: if perm in assignable_perms: expected_perms.append((user.username, perm)) expected_perms = sorted(expected_perms, key=lambda element: (element[0], element[1])) obj_perms = [] for assignment in results: object_permission = self.url_to_obj(assignment.get('url')) obj_perms.append((object_permission.user.username, object_permission.permission.codename)) obj_perms = sorted(obj_perms, key=lambda element: (element[0], element[1])) self.assertEqual(expected_perms, obj_perms)
def get_queryset(self): queryset = Asset.objects.filter(asset_type=ASSET_TYPE_SURVEY) if self.action == 'retrieve': # `get_object()` will do the checking; no need to manipulate the # queryset further return queryset.defer('content') # `ReportsListSerializer` needs only the UID; don't bother retrieving # anything else from the database queryset = queryset.only('uid') # Reduce the number of asset versions we have to consider by filtering # for accessible assets first owned_and_explicitly_shared = get_objects_for_user( self.request.user, self.required_permissions, queryset, all_perms_required=False, ) subscribed_and_public = get_objects_for_user( get_anonymous_user(), self.required_permissions, queryset.filter( parent__userassetsubscription__user=self.request.user ), all_perms_required=False, ) # Find which of these are deployed, using a custom manager method deployed_assets = ( owned_and_explicitly_shared | subscribed_and_public ) & Asset.objects.deployed().distinct() return deployed_assets
def get_queryset(self, *args, **kwargs): user = self.request.user # Check if the user is anonymous. The # django.contrib.auth.models.AnonymousUser object doesn't work for # queries. if user.is_anonymous: user = get_anonymous_user() def _get_tags_on_items(content_type_name, avail_items): """ return all ids of tags which are tagged to items of the given content_type """ same_content_type = Q( taggit_taggeditem_items__content_type__model=content_type_name) same_id = Q(taggit_taggeditem_items__object_id__in=avail_items. values_list('id')) return Tag.objects.filter(same_content_type & same_id).distinct().\ values_list('id', flat=True) accessible_collections = get_objects_for_user(user, PERM_VIEW_COLLECTION, Collection).only('pk') accessible_assets = get_objects_for_user(user, PERM_VIEW_ASSET, Asset).only('pk') all_tag_ids = list( chain( _get_tags_on_items('collection', accessible_collections), _get_tags_on_items('asset', accessible_assets), )) return Tag.objects.filter(id__in=all_tag_ids).distinct()
def perform_create(self, serializer): # Check if the user is anonymous. The # django.contrib.auth.models.AnonymousUser object doesn't work for # queries. user = self.request.user if user.is_anonymous: user = get_anonymous_user() serializer.save(owner=user)
def test_anotheruser_can_export_when_submissions_publicly_shared(self): """ Running through behaviour described in issue kpi/#2870 where an asset that has been publicly shared and then explicity shared with a user, the user has lower permissions than an anonymous user and is therefore unable to export submission data. """ # resetting permissions of `anotheruser` to have no permissions self.asset.remove_perm(self.anotheruser, PERM_PARTIAL_SUBMISSIONS) self.asset.remove_perm(self.anotheruser, PERM_VIEW_ASSET) anonymous_user = get_anonymous_user() assert self.asset.has_perm(self.anotheruser, PERM_VIEW_ASSET) == False assert PERM_VIEW_ASSET not in self.asset.get_perms(self.anotheruser) assert self.asset.has_perm(self.anotheruser, PERM_CHANGE_ASSET) == False assert PERM_CHANGE_ASSET not in self.asset.get_perms(self.anotheruser) # required to export self.asset.assign_perm(self.anotheruser, PERM_CHANGE_ASSET) assert self.asset.has_perm(self.anotheruser, PERM_VIEW_ASSET) == True assert PERM_VIEW_ASSET in self.asset.get_perms(self.anotheruser) assert self.asset.has_perm(self.anotheruser, PERM_CHANGE_ASSET) == True assert PERM_CHANGE_ASSET in self.asset.get_perms(self.anotheruser) assert ( self.asset.has_perm(self.anotheruser, PERM_VIEW_SUBMISSIONS) == False ) assert PERM_VIEW_SUBMISSIONS not in self.asset.get_perms( self.anotheruser ) self.asset.assign_perm(anonymous_user, PERM_VIEW_SUBMISSIONS) assert ( self.asset.has_perm(self.anotheruser, PERM_VIEW_SUBMISSIONS) == True ) assert PERM_VIEW_SUBMISSIONS in self.asset.get_perms(self.anotheruser) # testing anotheruser can export data self.run_csv_export_test(user=self.anotheruser) # resetting permissions of asset partial_perms = { PERM_VIEW_SUBMISSIONS: [ {'_submitted_by': self.anotheruser.username} ] } self.asset.assign_perm( self.anotheruser, PERM_PARTIAL_SUBMISSIONS, partial_perms=partial_perms, ) self.asset.remove_perm(self.anotheruser, PERM_CHANGE_ASSET) self.asset.remove_perm(anonymous_user, PERM_VIEW_ASSET) self.asset.remove_perm(anonymous_user, PERM_VIEW_SUBMISSIONS)
def get_queryset(self): # Retrieve all deployed assets first. deployed_assets = Asset.objects.filter(asset_versions__deployed=True).distinct() # Then retrieve all assets user is allowed to view (user must have 'view_submissions' on Asset objects) user_assets = get_objects_for_user(self.request.user, 'view_submissions', deployed_assets) publicly_shared_assets = get_objects_for_user(get_anonymous_user(), 'view_submissions', deployed_assets) return user_assets | publicly_shared_assets
def _get_assets(self, obj): request = self.context.get('request', None) user = request.user # Check if the user is anonymous. The # django.contrib.auth.models.AnonymousUser object doesn't work for # queries. if user.is_anonymous: user = get_anonymous_user() return [reverse('asset-detail', args=(sa.uid,), request=request) for sa in Asset.objects.filter(tags=obj, owner=user).all()]
def get_queryset(self): user = self.request.user # Check if the user is anonymous. The # django.contrib.auth.models.AnonymousUser object doesn't work for # queries. if user.is_anonymous: user = get_anonymous_user() criteria = {'user': user} if 'collection__uid' in self.request.query_params: criteria['collection__uid'] = self.request.query_params[ 'collection__uid'] return UserCollectionSubscription.objects.filter(**criteria)
def setUp(self): super().setUp() self.anon = get_anonymous_user() self.super = User.objects.get(username='******') self.super_password = '******' self.someuser = User.objects.get(username='******') self.someuser_password = '******' self.anotheruser = User.objects.get(username='******') self.anotheruser_password = '******' self.collection = Asset.objects.create( asset_type=ASSET_TYPE_COLLECTION, owner=self.someuser) self.asset = Asset.objects.create(owner=self.someuser)
def user_matches(self, user, ignore_invalid_queries=True): if user.is_anonymous(): user = get_anonymous_user() manager = user._meta.model.objects queryset = manager.none() for user_query in self.user_queries: try: queryset |= manager.filter(**user_query) except (FieldError, TypeError): if ignore_invalid_queries: return False else: raise return queryset.filter(pk=user.pk).exists()
def has_object_permission(self, request, view, obj): # Checks if the user has the require permissions # To access the submission data in reports user = request.user if user.is_superuser: return True if user.is_anonymous: user = get_anonymous_user() permissions = list(obj.get_perms(user)) required_permissions = [ PERM_VIEW_SUBMISSIONS, PERM_PARTIAL_SUBMISSIONS, ] return any(perm in permissions for perm in required_permissions)
def get_queryset(self, *args, **kwargs): user = self.request.user # Check if the user is anonymous. The # django.contrib.auth.models.AnonymousUser object doesn't work for # queries. if user.is_anonymous: user = get_anonymous_user() accessible_asset_pks = get_objects_for_user(user, PERM_VIEW_ASSET, Asset).only('pk') content_type = ContentType.objects.get_for_model(Asset) return Tag.objects.filter( taggit_taggeditem_items__content_type=content_type, taggit_taggeditem_items__object_id__in=[accessible_asset_pks], )
def setUp(self): KpiTestCase.setUp(self) self.anon = get_anonymous_user() self.admin = User.objects.get(username='******') self.admin_password = '******' self.someuser = User.objects.get(username='******') self.someuser_password = '******' self.login(self.admin.username, self.admin_password) self.admins_public_asset = self.create_asset('admins_public_asset') self.add_perm(self.admins_public_asset, self.anon, 'view') self.login(self.someuser.username, self.someuser_password) self.someusers_public_asset = self.create_asset('someusers_public_asset') self.add_perm(self.someusers_public_asset, self.anon, 'view')
def test_list_submissions_asset_publicly_shared_and_shared_with_user(self): """ Running through behaviour described in issue kpi/#2870 where an asset that has been publicly shared and then explicity shared with a user, the user has lower permissions than an anonymous user and is therefore unable to view submission data. """ self._log_in_as_another_user() anonymous_user = get_anonymous_user() assert self.asset.has_perm(self.anotheruser, PERM_VIEW_ASSET) == False assert PERM_VIEW_ASSET not in self.asset.get_perms(self.anotheruser) assert self.asset.has_perm(self.anotheruser, PERM_CHANGE_ASSET) == False assert PERM_CHANGE_ASSET not in self.asset.get_perms(self.anotheruser) self.asset.assign_perm(self.anotheruser, PERM_CHANGE_ASSET) assert self.asset.has_perm(self.anotheruser, PERM_VIEW_ASSET) == True assert PERM_VIEW_ASSET in self.asset.get_perms(self.anotheruser) assert self.asset.has_perm(self.anotheruser, PERM_CHANGE_ASSET) == True assert PERM_CHANGE_ASSET in self.asset.get_perms(self.anotheruser) assert ( self.asset.has_perm(self.anotheruser, PERM_VIEW_SUBMISSIONS) == False ) assert PERM_VIEW_SUBMISSIONS not in self.asset.get_perms( self.anotheruser ) self.asset.assign_perm(anonymous_user, PERM_VIEW_SUBMISSIONS) assert self.asset.has_perm(self.anotheruser, PERM_VIEW_ASSET) == True assert PERM_VIEW_ASSET in self.asset.get_perms(self.anotheruser) assert ( self.asset.has_perm(self.anotheruser, PERM_VIEW_SUBMISSIONS) == True ) assert PERM_VIEW_SUBMISSIONS in self.asset.get_perms(self.anotheruser) # resetting permssions of asset self.asset.remove_perm(self.anotheruser, PERM_VIEW_ASSET) self.asset.remove_perm(self.anotheruser, PERM_CHANGE_ASSET) self.asset.remove_perm(anonymous_user, PERM_VIEW_ASSET) self.asset.remove_perm(anonymous_user, PERM_VIEW_SUBMISSIONS)
def test_list_submissions_authenticated_asset_publicly_shared(self): """ https://github.com/kobotoolbox/kpi/issues/2698 """ anonymous_user = get_anonymous_user() self._log_in_as_another_user() # Give the user who will access the public data--without any explicit # permission assignment--their own asset. This is needed to expose a # flaw in `ObjectPermissionMixin.__get_object_permissions()` Asset.objects.create(name='i own it', owner=self.anotheruser) # `self.asset` is owned by `someuser`; `anotheruser` has no # explicitly-granted access to it self.asset.assign_perm(anonymous_user, PERM_VIEW_SUBMISSIONS) response = self.client.get(self.submission_url, {"format": "json"}) self.assertEqual(response.status_code, status.HTTP_200_OK) self.asset.remove_perm(anonymous_user, PERM_VIEW_SUBMISSIONS)
def has_permission(self, request, view): if not request.user: return False elif request.user.is_superuser: return True parent_object = self._get_parent_object(view) user = request.user if user.is_anonymous: user = get_anonymous_user() user_permissions = self._get_user_permissions(parent_object, user) view_permissions = self.get_required_permissions('GET') can_view = set(view_permissions).issubset(user_permissions) try: required_permissions = self.get_required_permissions( request.method) except exceptions.MethodNotAllowed as e: # Only reveal the HTTP 405 if the user has view access if can_view: raise e else: raise Http404 if user == parent_object.owner: # The owner can always manage permission assignments has_perm = True else: has_perm = set(required_permissions).issubset(user_permissions) if has_perm: # Access granted! return True if not has_perm and can_view: # If users are allowed to view, we want to show them HTTP 403 return False # Don't reveal the existence of this object to users who do not have # permission to view it raise Http404
def setUp(self): self.anon = get_anonymous_user() self.someuser = User.objects.get(username='******') self.someuser_password = '******' # This was written when we allowed anons to create assets, but I'll # leave it here just to make sure it has no effect permission = Permission.objects.get(codename='add_asset') self.anon.user_permissions.add(permission) # Log in and create an asset that anon can access self.client.login(username=self.someuser.username, password=self.someuser_password) self.anon_accessible = self.create_asset('Anonymous can access this!') self.add_perm(self.anon_accessible, self.anon, 'view_') # Log out and become anonymous again self.client.logout() response = self.client.get(reverse('currentuser-detail')) self.assertFalse('username' in response.data)
def setUp(self): super().setUp() self.anon = get_anonymous_user() self.super = User.objects.get(username='******') self.super_password = '******' self.someuser = User.objects.get(username='******') self.someuser_password = '******' self.anotheruser = User.objects.get(username='******') self.anotheruser_password = '******' def create_object_with_specific_pk(model, pk, **kwargs): obj = model() obj.pk = pk for k, v in kwargs.items(): setattr(obj, k, v) obj.save() return obj self.collection = Asset.objects.create( asset_type=ASSET_TYPE_COLLECTION, owner=self.someuser) self.asset = Asset.objects.create(owner=self.someuser)
def get_queryset(self): # Retrieve all deployed assets first. deployed_assets = Asset.objects.filter( asset_versions__deployed=True).distinct() # Then retrieve all assets user is allowed to view # (user must have 'view_submissions' on Asset objects) required_permissions = [ PERM_VIEW_SUBMISSIONS, PERM_PARTIAL_SUBMISSIONS, ] user_assets = get_objects_for_user(self.request.user, required_permissions, deployed_assets, all_perms_required=False) publicly_shared_assets = get_objects_for_user(get_anonymous_user(), required_permissions, deployed_assets, all_perms_required=False) return user_assets | publicly_shared_assets
def get_fields(self, *args, **kwargs): fields = super().get_fields(*args, **kwargs) user = self.context['request'].user # Check if the user is anonymous. The # django.contrib.auth.models.AnonymousUser object doesn't work for # queries. if user.is_anonymous: user = get_anonymous_user() if 'parent' in fields: # TODO: remove this restriction? fields['parent'].queryset = fields['parent'].queryset.filter( owner=user) # Honor requests to exclude fields # TODO: Actually exclude fields from tha database query! DRF grabs # all columns, even ones that are never named in `fields` excludes = self.context['request'].GET.get('exclude', '') for exclude in excludes.split(','): exclude = exclude.strip() if exclude in fields: fields.pop(exclude) return fields
def setUp(self): super().setUp() self.anon = get_anonymous_user() self.super = User.objects.get(username='******') self.super_password = '******' self.someuser = User.objects.get(username='******') self.someuser_password = '******' self.anotheruser = User.objects.get(username='******') self.anotheruser_password = '******' # Find an unused, common PK for both Asset and Collection--useful for # catching bugs related to content types like # https://github.com/kobotoolbox/kpi/issues/2270 last_asset = Asset.objects.order_by('pk').last() last_collection = Collection.objects.order_by('pk').last() available_pk = 1 + max(last_asset.pk if last_asset else 1, last_collection.pk if last_collection else 1) def create_object_with_specific_pk(model, pk, **kwargs): obj = model() obj.pk = pk for k, v in kwargs.items(): setattr(obj, k, v) obj.save() return obj self.collection = create_object_with_specific_pk( Collection, available_pk, owner=self.someuser, ) self.asset = create_object_with_specific_pk( Asset, available_pk, owner=self.someuser, # perenially evil `auto_now_add` leaves the field NULL if a pk is # specified, leading to `IntegrityError` unless we set it manually date_created=timezone.now(), )
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['collection'].queryset = get_objects_for_user( get_anonymous_user(), PERM_VIEW_COLLECTION, Collection.objects.filter(discoverable_when_public=True))
def setUp(self): super().setUp() self.asset.assign_perm(self.someuser, PERM_CHANGE_ASSET) self.asset.assign_perm(self.anotheruser, PERM_VIEW_ASSET) self.asset.assign_perm(get_anonymous_user(), PERM_VIEW_ASSET)
from kpi.deployment_backends.kobocat_backend import KobocatDeploymentBackend from kpi.models import Asset, ObjectPermission from kpi.models.object_permission import get_anonymous_user from kpi.model_utils import _set_auto_field_update TIMESTAMP_DIFFERENCE_TOLERANCE = datetime.timedelta(seconds=30) # Swap keys and values so that keys are KC's codenames and values are KPI's PERMISSIONS_MAP = {kc: kpi for kpi, kc in Asset.KC_PERMISSIONS_MAP.items()} # Optimization ASSET_CT = ContentType.objects.get_for_model(Asset) FROM_KC_ONLY_PERMISSION = Permission.objects.get( content_type=ASSET_CT, codename=PERM_FROM_KC_ONLY) XFORM_CT = ShadowModel.get_content_type_for_model(ReadOnlyKobocatXForm) ANONYMOUS_USER = get_anonymous_user() # Replace codenames with Permission PKs, remembering the codenames permission_map_copy = dict(PERMISSIONS_MAP) KPI_PKS_TO_CODENAMES = {} for kc_codename, kpi_codename in permission_map_copy.items(): kc_perm_pk = KobocatPermission.objects.get( content_type=XFORM_CT, codename=kc_codename).pk kpi_perm_pk = Permission.objects.get( content_type=ASSET_CT, codename=kpi_codename).pk del PERMISSIONS_MAP[kc_codename] PERMISSIONS_MAP[kc_perm_pk] = kpi_perm_pk KPI_PKS_TO_CODENAMES[kpi_perm_pk] = kpi_codename