diff --git a/portier/common/handlers.py b/portier/common/handlers.py
new file mode 100644
index 0000000..39451de
--- /dev/null
+++ b/portier/common/handlers.py
@@ -0,0 +1,10 @@
+from django.contrib.contenttypes.models import ContentType
+from django.db.models import Q
+from guardian.models import UserObjectPermission
+from guardian.models import GroupObjectPermission
+
+
+def remove_obj_perms_connected_with_user(sender, instance, **kwargs):
+ filters = Q(content_type=ContentType.objects.get_for_model(instance), object_pk=instance.pk)
+ UserObjectPermission.objects.filter(filters).delete()
+ GroupObjectPermission.objects.filter(filters).delete()
diff --git a/portier/settings.py b/portier/settings.py
index 03a9577..2d47842 100644
--- a/portier/settings.py
+++ b/portier/settings.py
@@ -36,6 +36,7 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
+ 'guardian',
'django_registration',
'bootstrap4',
'fa',
@@ -55,6 +56,11 @@ MIDDLEWARE = [
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
+AUTHENTICATION_BACKENDS = (
+ 'django.contrib.auth.backends.ModelBackend',
+ 'guardian.backends.ObjectPermissionBackend',
+)
+
ROOT_URLCONF = 'portier.urls'
TEMPLATES = [
diff --git a/requirements.txt b/requirements.txt
index 7643576..5960f3e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,7 @@
django>=3.0
django-registration>=3.1
django-bootstrap4
+django-guardian
django-fa
celery>=4.4
gunicorn>=20
diff --git a/restream/admin.py b/restream/admin.py
index 1fe1551..ef945a4 100644
--- a/restream/admin.py
+++ b/restream/admin.py
@@ -1,8 +1,9 @@
from django.contrib import admin
+from guardian.admin import GuardedModelAdmin
from .models import RestreamConfig
-class RestreamConfigAdmin(admin.ModelAdmin):
+class RestreamConfigAdmin(GuardedModelAdmin):
fields = ['name', 'active', 'stream', 'target']
diff --git a/restream/forms.py b/restream/forms.py
new file mode 100644
index 0000000..7497e5d
--- /dev/null
+++ b/restream/forms.py
@@ -0,0 +1,16 @@
+from django.forms import ModelForm
+from guardian.shortcuts import get_objects_for_user
+from . import models
+
+
+class RestreamConfigFilteredStreamForm(ModelForm):
+ class Meta:
+ model = models.RestreamConfig
+ fields = ['name', 'stream', 'target', 'active']
+
+ def __init__(self, *args, **kwargs):
+ user = kwargs.pop('user', None)
+ super().__init__(*args, **kwargs)
+
+ # limit the stream selection to user-accessible streams
+ self.fields['stream'].queryset = get_objects_for_user(user, 'rtmp.view_stream')
diff --git a/restream/models.py b/restream/models.py
index 99473a9..0f7682f 100644
--- a/restream/models.py
+++ b/restream/models.py
@@ -1,6 +1,8 @@
from django.db import models
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
+from django.db.models.signals import pre_delete
+from portier.common import handlers
from rtmp.models import Stream
@@ -23,3 +25,6 @@ class RestreamConfig(models.Model):
def __str__(self):
return '{} to {}'.format(self.stream, self.name)
+
+
+pre_delete.connect(handlers.remove_obj_perms_connected_with_user, sender=RestreamConfig)
diff --git a/restream/templates/restream/restreamconfig_list.html b/restream/templates/restream/restreamconfig_list.html
index 3c7fa4a..ae4fe8b 100644
--- a/restream/templates/restream/restreamconfig_list.html
+++ b/restream/templates/restream/restreamconfig_list.html
@@ -2,6 +2,8 @@
{% load i18n %}
{% load bootstrap4 %}
{% load font_awesome %}
+{% load guardian_tags %}
+
{% block 'content' %}
{% trans "restreamconfig_configuration_header" %}
@@ -19,15 +21,20 @@
{% for object in object_list %}
+ {% get_obj_perms user for object as "obj_perms" %}
+ {% if "view_restreamconfig" in obj_perms %}
{{ object.name }} |
|
+ {% endif %}
{% endfor %}
diff --git a/restream/views.py b/restream/views.py
index 4e7635b..1026dc4 100644
--- a/restream/views.py
+++ b/restream/views.py
@@ -2,27 +2,54 @@ from django.urls import reverse_lazy
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic import ListView, DetailView, CreateView, DeleteView
+from guardian.decorators import permission_required_or_403
+from guardian.shortcuts import assign_perm
from . import models
+from . import forms
@method_decorator(login_required, name='dispatch')
+@method_decorator(permission_required_or_403('restream.add_restreamconfig'),
+ name='dispatch')
class RestreamConfigList(ListView):
model = models.RestreamConfig
@method_decorator(login_required, name='dispatch')
+@method_decorator(permission_required_or_403('restream.view_restreamconfig',
+ (models.RestreamConfig, 'pk', 'pk')),
+ name='dispatch')
class RestreamConfigDetail(DetailView):
model = models.RestreamConfig
@method_decorator(login_required, name='dispatch')
+@method_decorator(permission_required_or_403('restream.add_restreamconfig'),
+ name='dispatch')
class RestreamConfigCreate(CreateView):
model = models.RestreamConfig
- fields = ["name", "stream", "target", "active"]
+ form_class = forms.RestreamConfigFilteredStreamForm
+
+ def get_form_kwargs(self):
+ kwargs = super().get_form_kwargs()
+ kwargs['user'] = self.request.user
+ return kwargs
+
+ def form_valid(self, form):
+ valid = super().form_valid(form)
+ if valid:
+ user = self.request.user
+ assign_perm('view_restreamconfig', user, self.object)
+ assign_perm('change_restreamconfig', user, self.object)
+ assign_perm('delete_restreamconfig', user, self.object)
+ return valid
@method_decorator(login_required, name='dispatch')
+@method_decorator(permission_required_or_403('restream.delete_restreamconfig',
+ (models.RestreamConfig, 'pk', 'pk')),
+ name='dispatch')
class RestreamConfigDelete(DeleteView):
model = models.RestreamConfig
success_url = reverse_lazy('restream:restreamconfig_list')
diff --git a/rtmp/admin.py b/rtmp/admin.py
index 025978a..cbbe9da 100644
--- a/rtmp/admin.py
+++ b/rtmp/admin.py
@@ -1,12 +1,13 @@
from django.contrib import admin
+from guardian.admin import GuardedModelAdmin
from .models import Application, Stream
-class ApplicationAdmin(admin.ModelAdmin):
+class ApplicationAdmin(GuardedModelAdmin):
fields = ['name']
-class StreamAdmin(admin.ModelAdmin):
+class StreamAdmin(GuardedModelAdmin):
fields = ['application', 'stream', 'name', 'publish_counter']
diff --git a/rtmp/models.py b/rtmp/models.py
index 7db5b5b..f4b9e67 100644
--- a/rtmp/models.py
+++ b/rtmp/models.py
@@ -1,7 +1,10 @@
+import uuid
+
from django.db import models
from django.urls import reverse
from django.utils.translation import gettext as _
-import uuid
+from django.db.models.signals import pre_delete
+from portier.common import handlers
from . import signals
@@ -66,3 +69,7 @@ class Stream(models.Model):
def __str__(self):
return self.name
+
+
+pre_delete.connect(handlers.remove_obj_perms_connected_with_user, sender=Application)
+pre_delete.connect(handlers.remove_obj_perms_connected_with_user, sender=Stream)
diff --git a/rtmp/templates/rtmp/stream_list.html b/rtmp/templates/rtmp/stream_list.html
index 445c74f..b314a62 100644
--- a/rtmp/templates/rtmp/stream_list.html
+++ b/rtmp/templates/rtmp/stream_list.html
@@ -2,6 +2,8 @@
{% load i18n %}
{% load bootstrap4 %}
{% load font_awesome %}
+{% load guardian_tags %}
+
{% block 'content' %}
{% trans "stream_configuration_header" %}
@@ -19,15 +21,20 @@
{% for object in object_list %}
+ {% get_obj_perms user for object as "obj_perms" %}
+ {% if "view_stream" in obj_perms %}
{{ object.name }} |
|
+ {% endif %}
{% endfor %}
diff --git a/rtmp/views.py b/rtmp/views.py
index 1f26e7f..ae589a8 100644
--- a/rtmp/views.py
+++ b/rtmp/views.py
@@ -9,6 +9,8 @@ from django.contrib.admin.utils import NestedObjects
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import ListView, DetailView, CreateView, DeleteView
+from guardian.decorators import permission_required_or_403
+from guardian.shortcuts import assign_perm
from . import models
@@ -48,22 +50,41 @@ def callback_srs(request):
@method_decorator(login_required, name='dispatch')
+@method_decorator(permission_required_or_403('rtmp.add_stream'),
+ name='dispatch')
class StreamList(ListView):
model = models.Stream
@method_decorator(login_required, name='dispatch')
+@method_decorator(permission_required_or_403('rtmp.view_stream',
+ (models.Stream, 'pk', 'pk')),
+ name='dispatch')
class StreamDetail(DetailView):
model = models.Stream
@method_decorator(login_required, name='dispatch')
+@method_decorator(permission_required_or_403('rtmp.add_stream'),
+ name='dispatch')
class StreamCreate(CreateView):
model = models.Stream
fields = ["name", "application"]
+ def form_valid(self, form):
+ valid = super().form_valid(form)
+ if valid:
+ user = self.request.user
+ assign_perm('view_stream', user, self.object)
+ assign_perm('change_stream', user, self.object)
+ assign_perm('delete_stream', user, self.object)
+ return valid
+
@method_decorator(login_required, name='dispatch')
+@method_decorator(permission_required_or_403('rtmp.delete_stream',
+ (models.Stream, 'pk', 'pk')),
+ name='dispatch')
class StreamDelete(DeleteView):
model = models.Stream
success_url = reverse_lazy('rtmp:stream_list')