From 75baf561a80e022d44370e2e3c4d1c9ae6b7ee0b Mon Sep 17 00:00:00 2001
From: Christophe <contact2@c-henry.fr>
Date: Fri, 13 Dec 2019 17:20:54 +0100
Subject: [PATCH] feat: Add permissions to subscriptions to only exposed user's

---
 .gitlab-ci.yml                              | 46 +++++++++++++++++++-
 djangoldp_notification/models.py            | 16 +++----
 djangoldp_notification/permissions.py       | 26 +++++++++++-
 djangoldp_notification/tests/conftest.py    | 15 +++++++
 djangoldp_notification/tests/test_models.py | 47 +++++++++++++++++++++
 setup.cfg                                   |  5 ++-
 6 files changed, 144 insertions(+), 11 deletions(-)
 create mode 100644 djangoldp_notification/tests/conftest.py
 create mode 100644 djangoldp_notification/tests/test_models.py

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 137fb38..7eadb2c 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -5,10 +5,54 @@ stages:
   - test
   - release
 
+variables:
+  SIB_PROJECT_NAME: "sib_test"
+  SIB_PROJECT_DIR: "/builds/$SIB_PROJECT_NAME"
+  DJANGO_SETTINGS_MODULE: "$SIB_PROJECT_NAME.settings"
+  SIB_PACKAGE_CONTENT: >
+    {
+      "ldppackages": {
+        "djangoldp_project": "djangoldp_project",
+        "djangoldp_circle": "djangoldp_circle",
+        "djangoldp_notification": "djangoldp_notification",
+        "djangoldp_account": "djangoldp_account",
+        "djangoldp_skill": "djangoldp_skill",
+        "djangoldp_joboffer": "djangoldp_joboffer",
+        "djangoldp_conversation": "djangoldp_conversation",
+        "djangoldp_profile": "djangoldp_profile",
+        "oidc_provider": "git+https://github.com/jblemee/django-oidc-provider.git@develop"
+      },
+      "server": {
+        "site_url": "http://localhost:8000",
+        "allowed_hosts": [
+
+        ],
+        "db_host": "localhost",
+        "db_name": "database",
+        "db_user": "me",
+        "db_pass": "changeit",
+        "smtp_host": "locahost",
+        "smtp_user": "user",
+        "smtp_pass": "changeit",
+        "admin_email": "admin@example.org",
+        "admin_name": "admin",
+        "admin_pass": "admin"
+      }
+    }
+
 test:
   stage: test
+  before_script:
+    - export PATH="$PATH:/root/.local/bin" PYTHONPATH="$SIB_PROJECT_DIR"
+    - pip install git+https://git.happy-dev.fr/startinblox/devops/sib.git
+    - cd /builds && DJANGO_SETTINGS_MODULE="" sib startproject $SIB_PROJECT_NAME
+    - echo $SIB_PACKAGE_CONTENT > $SIB_PROJECT_DIR/packages.yml
+    - cd $SIB_PROJECT_DIR
+    - sib install $SIB_PROJECT_NAME
+    - cd $CI_PROJECT_DIR
+    - pip install -e .[dev]
   script:
-    - echo 'Make your tests here !'
+    - pytest
   except:
     - master
   tags:
diff --git a/djangoldp_notification/models.py b/djangoldp_notification/models.py
index a58e44a..ddc9ff8 100644
--- a/djangoldp_notification/models.py
+++ b/djangoldp_notification/models.py
@@ -1,25 +1,20 @@
 import logging
-from threading import Thread
 
 import requests
 from django.conf import settings
-from django.contrib.admin.models import LogEntry
-from django.contrib.sessions.models import Session
 from django.core.mail import send_mail
 from django.db import models
 from django.db.models.signals import post_save
 from django.dispatch import receiver
-from oidc_provider.models import Token
+from django.template import loader
 from django.urls import NoReverseMatch
 from django.utils.translation import ugettext_lazy as _
-
 from djangoldp.fields import LDPUrlField
 from djangoldp.models import Model
-
-from django.template import loader
+from threading import Thread
 
 from djangoldp_notification.middlewares import MODEL_MODIFICATION_USER_FIELD
-from .permissions import InboxPermissions
+from djangoldp_notification.permissions import InboxPermissions, SubscriptionsPermissions
 
 
 class Notification(Model):
@@ -50,6 +45,11 @@ class Subscription(Model):
     def __str__(self):
         return '{}'.format(self.object)
 
+    class Meta(Model.Meta):
+        anonymous_perms = []
+        authenticated_perms = ["add", "view", "delete"]
+        permission_classes = [SubscriptionsPermissions]
+
 # --- SUBSCRIPTION SYSTEM ---
 @receiver(post_save, dispatch_uid="callback_notif")
 def send_notification(sender, instance, created, **kwargs):
diff --git a/djangoldp_notification/permissions.py b/djangoldp_notification/permissions.py
index f013752..52d68a9 100644
--- a/djangoldp_notification/permissions.py
+++ b/djangoldp_notification/permissions.py
@@ -1,4 +1,6 @@
+from django.contrib.auth import get_user_model
 from djangoldp.permissions import LDPPermissions
+from rest_framework.reverse import reverse
 
 
 class InboxPermissions(LDPPermissions):
@@ -35,4 +37,26 @@ class InboxPermissions(LDPPermissions):
             if not perm.split('.')[1].split('_')[0] in self.user_permissions(request.user, model, obj):
                 return False
 
-        return True
\ No newline at end of file
+        return True
+
+
+class SubscriptionsPermissions(LDPPermissions):
+    def has_permission(self, request, view):
+        if request.user.is_anonymous and not request.method == "OPTIONS":
+            return False
+
+        if request.method in ["GET", "PATCH", "DELETE", "PUT"]:
+            return True
+
+        return super().has_permission(request, view)
+
+    def has_object_permission(self, request, view, obj):
+        if request.user.is_anonymous and not request.method == "OPTIONS":
+            return False
+
+        reverse_path_key = "{}-notification-list".format(get_user_model()._meta.object_name.lower())
+        user_inbox = reverse(reverse_path_key, kwargs={"slug": request.user.slug}, request=request)
+        if obj.inbox == user_inbox:
+            return True
+
+        return False
diff --git a/djangoldp_notification/tests/conftest.py b/djangoldp_notification/tests/conftest.py
new file mode 100644
index 0000000..b1beb8b
--- /dev/null
+++ b/djangoldp_notification/tests/conftest.py
@@ -0,0 +1,15 @@
+from importlib import import_module
+from os import environ
+
+from pytest import fail
+
+settings_module = environ.get("DJANGO_SETTINGS_MODULE")
+if settings_module is None or len(settings_module) == 0:
+    fail("DJANGO_SETTINGS_MODULE needs to be defined and point to your SIB app installation settings")
+
+try:
+    import_module(settings_module)
+except ImportError:
+    initial_module = [token for token in settings_module.split(".") if len(token) > 0][0]
+    fail("Unable to import {}. Try to configure PYTHONPATH to point the "
+         "directory containing the {} module".format(settings_module, initial_module))
diff --git a/djangoldp_notification/tests/test_models.py b/djangoldp_notification/tests/test_models.py
new file mode 100644
index 0000000..3ae6f09
--- /dev/null
+++ b/djangoldp_notification/tests/test_models.py
@@ -0,0 +1,47 @@
+from collections import OrderedDict
+
+from djangoldp.factories import UserFactory
+from test_plus import APITestCase
+
+from djangoldp_notification.models import Subscription
+
+
+class TestSubscription(APITestCase):
+    user1 = None
+    user2 = None
+    circle_user1_url = "http://localhost:8000/circles/1"
+    circle_user2_url = "http://localhost:8000/circles/2"
+
+    def setUp(self):
+        self.user1 = UserFactory(username="karl_marx", password="password")
+        Subscription.objects.create(object=self.circle_user1_url, inbox="http://testserver/users/karl_marx/inbox/")
+
+        self.user2 = UserFactory(username="piotr_kropotkine", password="password")
+        Subscription.objects.create(object=self.circle_user2_url,
+                                    inbox="http://testserver/users/piotr_kropotkine/inbox/")
+
+    def test_not_logged_fails(self):
+        response = self.get("/subscriptions/")
+        self.assert_http_403_forbidden(response)
+        self.assertEqual(response.data.get("detail"), "Authentication credentials were not provided.")
+
+    def test_logged_in_succeeds(self):
+        with self.login(self.user1):
+            result = self.get("/subscriptions/").data.get("ldp:contains")
+            expected = [OrderedDict({
+                "@id": "http://localhost:8000/subscriptions/1/",
+                "object": self.circle_user1_url,
+                "inbox": "http://testserver/users/karl_marx/inbox/",
+                "permissions": [{'mode': {'@type': 'view'}}, {'mode': {'@type': 'delete'}}]
+            })]
+            self.assertSequenceEqual(result, expected)
+
+        with self.login(self.user2):
+            result = self.get("/subscriptions/").data.get("ldp:contains")
+            expected = [OrderedDict({
+                "@id": "http://localhost:8000/subscriptions/2/",
+                "object": self.circle_user2_url,
+                "inbox": "http://testserver/users/piotr_kropotkine/inbox/",
+                "permissions": [{'mode': {'@type': 'view'}}, {'mode': {'@type': 'delete'}}]
+            })]
+            self.assertSequenceEqual(result, expected)
diff --git a/setup.cfg b/setup.cfg
index ee79f41..38922fc 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -15,7 +15,10 @@ install_requires =
 [options.extras_require]
 include_package_data = True
 dev =
-    factory_boy>=2.11.0
+    factory_boy>=2.12.0
+    pytest==5.1.1
+    pytest-django==3.7.0
+    django-test-plus==1.4.0
 
 [semantic_release]
 version_source = tag