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