diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6a7343bd30c6f680fa04bae4f8769bd3bdbb14eb..587d1d7776ea36bd83e784476b4b522c5bf2f0a0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,9 +2,21 @@ image: python:3.6 stages: + - test - release include: - project: 'infra/platform' ref: master file: '/templates/python.ci.yml' + +test: + stage: test + script: + - pip install .[dev] + - python -m unittest djangoldp_contact.tests.runner + except: + - master + - tags + tags: + - test diff --git a/djangoldp_contact/migrations/0003_auto_20210218_1145.py b/djangoldp_contact/migrations/0003_auto_20210218_1145.py new file mode 100644 index 0000000000000000000000000000000000000000..9980538affd82c7bc9fa5b94323438c4879b0001 --- /dev/null +++ b/djangoldp_contact/migrations/0003_auto_20210218_1145.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.18 on 2021-02-18 10:45 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('djangoldp_contact', '0002_auto_20201208_1157'), + ] + + operations = [ + migrations.AlterModelOptions( + name='contact', + options={'default_permissions': ['add', 'change', 'delete', 'view', 'control'], 'verbose_name': 'contact', 'verbose_name_plural': 'contacts'}, + ), + ] diff --git a/djangoldp_contact/models.py b/djangoldp_contact/models.py index d090e4555a829d88da46128eb1fdf917aae7f267..9c56efe8782f1411c4ba4c44b20e23840c8fe0e9 100644 --- a/djangoldp_contact/models.py +++ b/djangoldp_contact/models.py @@ -5,6 +5,7 @@ from django.dispatch import receiver from django.utils.translation import ugettext_lazy as _ from djangoldp.fields import LDPUrlField from djangoldp.models import Model +from djangoldp_contact.views import ContactViewSet from djangoldp_notification.models import Notification @@ -21,9 +22,11 @@ class Contact(Model): auto_author = 'user' owner_field = 'user' anonymous_perms = [] - authenticated_perms = [] + authenticated_perms = ['add'] owner_perms = ['view', 'delete'] + superuser_perms = [] unique_together = [['user', 'contact']] + view_set = ContactViewSet rdf_type = "sib:contact" @receiver(post_save, sender=Notification) diff --git a/djangoldp_contact/tests/__init__.py b/djangoldp_contact/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/djangoldp_contact/tests/runner.py b/djangoldp_contact/tests/runner.py new file mode 100644 index 0000000000000000000000000000000000000000..02156e19f39ff834374b2701e632ced06da1e80c --- /dev/null +++ b/djangoldp_contact/tests/runner.py @@ -0,0 +1,45 @@ +import sys +import yaml + +import django +from django.conf import settings as django_settings +from djangoldp.conf.ldpsettings import LDPSettings +from djangoldp.tests.settings_default import yaml_config + +# override config loading +config = { + # add the packages to the reference list + 'ldppackages': ['djangoldp_account', 'djangoldp_notification', 'djangoldp_contact', 'djangoldp_contact.tests'], + + # required values for server + 'server': { + 'AUTH_USER_MODEL': 'djangoldp_account.LDPUser', + 'REST_FRAMEWORK': { + 'DEFAULT_PAGINATION_CLASS': 'djangoldp.pagination.LDPPagination', + 'PAGE_SIZE': 5 + }, + # map the config of the core settings (avoid asserts to fail) + 'SITE_URL': 'http://happy-dev.fr', + 'BASE_URL': 'http://happy-dev.fr', + 'SEND_BACKLINKS': False, + 'JABBER_DEFAULT_HOST': None, + 'PERMISSIONS_CACHE': False, + 'ANONYMOUS_USER_NAME': None, + 'SERIALIZER_CACHE': False + } +} +ldpsettings = LDPSettings(config) +ldpsettings.config = yaml.safe_load(yaml_config) + +django_settings.configure(ldpsettings) + +django.setup() +from django.test.runner import DiscoverRunner + +test_runner = DiscoverRunner(verbosity=1) + +failures = test_runner.run_tests([ + 'djangoldp_contact.tests.tests_permissions', +]) +if failures: + sys.exit(failures) diff --git a/djangoldp_contact/tests/tests_permissions.py b/djangoldp_contact/tests/tests_permissions.py new file mode 100644 index 0000000000000000000000000000000000000000..f633e6cc79730d0d99b92ea4479a3f385d4d7607 --- /dev/null +++ b/djangoldp_contact/tests/tests_permissions.py @@ -0,0 +1,108 @@ +import uuid +import json +from datetime import datetime, timedelta + +from djangoldp.serializers import LDListMixin, LDPSerializer +from rest_framework.test import APITestCase, APIClient + +from djangoldp_account.models import LDPUser + + +class PermissionsTestCase(APITestCase): + # Django runs setUp automatically before every test + def setUp(self): + # we set up a client, that allows us + self.client = APIClient() + LDListMixin.to_representation_cache.reset() + LDPSerializer.to_representation_cache.reset() + + # we have custom set up functions for things that we don't want to run before *every* test, e.g. often we want to + # set up an authenticated user, but sometimes we want to run a test with an anonymous user + def setUpLoggedInUser(self, is_superuser=False): + self.user = LDPUser(email='test@mactest.co.uk', first_name='Test', last_name='Mactest', username='test', + password='glass onion', is_superuser=is_superuser) + self.user.save() + # this means that our user is now logged in (as if they had typed username and password) + self.client.force_authenticate(user=self.user) + + # we write functions like this for convenience - we can reuse between tests + def _get_random_user(self): + return LDPUser.objects.create(email='{}@test.co.uk'.format(str(uuid.uuid4())), first_name='Test', + last_name='Test', username=str(uuid.uuid4())) + + def _get_context(self): + return { + '@vocab': "http://happy-dev.fr/owl/#", + 'rdf': "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + 'rdfs': "http://www.w3.org/2000/01/rdf-schema#", + 'ldp': "http://www.w3.org/ns/ldp#", + 'foaf': "http://xmlns.com/foaf/0.1/", + 'name': "rdfs:label", + 'acl': "http://www.w3.org/ns/auth/acl#", + 'permissions': "acl:accessControl", + 'mode': "acl:mode", + 'inbox': "http://happy-dev.fr/owl/#inbox", + 'object': "http://happy-dev.fr/owl/#object", + 'author': "http://happy-dev.fr/owl/#author", + 'account': "http://happy-dev.fr/owl/#account", + 'jabberID': "foaf:jabberID", + 'picture': "foaf:depiction", + 'firstName': "http://happy-dev.fr/owl/#first_name", + 'lastName': "http://happy-dev.fr/owl/#last_name", + 'isAdmin': "http://happy-dev.fr/owl/#is_admin" + } + + def test_post_contact_for_myself(self): + self.setUpLoggedInUser() + another_user = self._get_random_user() + + body = { + 'user': {'@id': self.user.urlid}, + 'contact': another_user.urlid, + '@context': self._get_context() + } + + response = self.client.post('/contacts/', json.dumps(body), content_type='application/ld+json') + self.assertEqual(response.status_code, 201) + + def test_post_contact_for_another(self): + self.setUpLoggedInUser() + another_user = self._get_random_user() + + body = { + 'user': {'@id': another_user.urlid}, + 'contact': self.user.urlid, + '@context': self._get_context() + } + + response = self.client.post('/contacts/', json.dumps(body), content_type='application/ld+json') + self.assertEqual(response.status_code, 403) + + # repeated tests using nested field + def test_post_contact_for_myself_nested(self): + self.setUpLoggedInUser() + another_user = self._get_random_user() + + body = { + 'user': {'@id': self.user.urlid}, + 'contact': another_user.urlid, + '@context': self._get_context() + } + + response = self.client.post('/users/{}/contacts/'.format(self.user.username), + json.dumps(body), content_type='application/ld+json') + self.assertEqual(response.status_code, 201) + + def test_post_contact_for_another_nested(self): + self.setUpLoggedInUser() + another_user = self._get_random_user() + + body = { + 'user': {'@id': another_user.urlid}, + 'contact': self.user.urlid, + '@context': self._get_context() + } + + response = self.client.post('/users/{}/contacts/'.format(another_user.username), + json.dumps(body), content_type='application/ld+json') + self.assertEqual(response.status_code, 403) diff --git a/djangoldp_contact/views.py b/djangoldp_contact/views.py new file mode 100644 index 0000000000000000000000000000000000000000..15543654602952fc6e8cc91f1243058916b8da7c --- /dev/null +++ b/djangoldp_contact/views.py @@ -0,0 +1,16 @@ +from djangoldp.views import LDPViewSet + + +class ContactViewSet(LDPViewSet): + + def is_safe_create(self, user, validated_data, *args, **kwargs): + try: + if validated_data['user']['urlid'] == user.urlid: + return True + except KeyError: + # may be in a nested field + if hasattr(self, 'get_parent'): + if self.get_parent() == user: + return True + + return False diff --git a/setup.cfg b/setup.cfg index db84d792a8e0abf10d86d88950568984f3558ce6..ad374694c34d97d6b6c79bc05ff0e9089f95cf9d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -10,9 +10,9 @@ license = MIT [options] packages = find: install_requires = - djangoldp~=2.0 - djangoldp_account~=2.0 - djangoldp_notification~=2.0 + djangoldp~=2.1 + djangoldp_account~=2.1 + djangoldp_notification~=2.1 [semantic_release] version_source = tag