View only user (#539)

* Remove repo docs in favor of hosted docs (#537)

* updated base, channel, video htmls to hide elements based on if user is staff or in the group 'admin'

* added the load auth_extras

* updated auth_extras

* updated views.py to block api calls from deleting files from unprivileged users; The Templates needed to be updated to support the various group checks related to removing buttons an unprivileged user should not see

* bumped the channel templates to remove conflict

* fix linting issues

* more linting

---------

Co-authored-by: Merlin <4706504+MerlinScheurer@users.noreply.github.com>
This commit is contained in:
Steve Ovens 2023-10-15 01:58:06 -05:00 committed by GitHub
parent 446d5b7949
commit e1fce06f97
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 54 additions and 2 deletions

View File

@ -2,6 +2,8 @@
from api.src.aggs import BiggestChannel, DownloadHist, Primary, WatchProgress
from api.src.search_processor import SearchProcess
from django.contrib.auth.decorators import user_passes_test
from django.utils.decorators import method_decorator
from home.src.download.queue import PendingInteract
from home.src.download.subscriptions import (
ChannelSubscription,
@ -39,6 +41,11 @@ from rest_framework.response import Response
from rest_framework.views import APIView
def check_admin(user):
"""if user has access to restricted views"""
return user.is_staff or user.groups.filter(name="admin").exists()
class ApiBaseView(APIView):
"""base view to inherit from"""
@ -109,6 +116,7 @@ class VideoApiView(ApiBaseView):
self.get_document(video_id)
return Response(self.response, status=self.status_code)
@method_decorator(user_passes_test(check_admin), name="dispatch")
def delete(self, request, video_id):
# pylint: disable=unused-argument
"""delete single video"""
@ -165,7 +173,6 @@ class VideoProgressView(ApiBaseView):
message = {"position": position, "youtube_id": video_id}
RedisArchivist().set_message(key, message)
self.response = request.data
return Response(self.response)
def delete(self, request, video_id):
@ -283,6 +290,7 @@ class ChannelApiView(ApiBaseView):
self.get_document(channel_id)
return Response(self.response, status=self.status_code)
@method_decorator(user_passes_test(check_admin), name="dispatch")
def delete(self, request, channel_id):
# pylint: disable=unused-argument
"""delete channel"""
@ -328,6 +336,7 @@ class ChannelApiListView(ApiBaseView):
return Response(self.response)
@method_decorator(user_passes_test(check_admin), name="dispatch")
def post(self, request):
"""subscribe/unsubscribe to list of channels"""
data = request.data
@ -520,6 +529,7 @@ class DownloadApiView(ApiBaseView):
self.get_document(video_id)
return Response(self.response, status=self.status_code)
@method_decorator(user_passes_test(check_admin), name="dispatch")
def post(self, request, video_id):
"""post to video to change status"""
item_status = request.data.get("status")
@ -540,6 +550,7 @@ class DownloadApiView(ApiBaseView):
return Response(request.data)
@method_decorator(user_passes_test(check_admin), name="dispatch")
@staticmethod
def delete(request, video_id):
# pylint: disable=unused-argument
@ -585,6 +596,7 @@ class DownloadApiListView(ApiBaseView):
self.get_document_list(request)
return Response(self.response)
@method_decorator(user_passes_test(check_admin), name="dispatch")
@staticmethod
def post(request):
"""add list of videos to download queue"""
@ -610,6 +622,7 @@ class DownloadApiListView(ApiBaseView):
return Response(data)
@method_decorator(user_passes_test(check_admin), name="dispatch")
def delete(self, request):
"""delete download queue"""
query_filter = request.GET.get("filter", False)
@ -661,6 +674,7 @@ class LoginApiView(ObtainAuthToken):
return Response({"token": token.key, "user_id": user.pk})
@method_decorator(user_passes_test(check_admin), name="dispatch")
class SnapshotApiListView(ApiBaseView):
"""resolves to /api/snapshot/
GET: returns snapshot config plus list of existing snapshots
@ -684,6 +698,7 @@ class SnapshotApiListView(ApiBaseView):
return Response(response)
@method_decorator(user_passes_test(check_admin), name="dispatch")
class SnapshotApiView(ApiBaseView):
"""resolves to /api/snapshot/<snapshot-id>/
GET: return a single snapshot
@ -738,6 +753,7 @@ class TaskListView(ApiBaseView):
return Response(all_results)
@method_decorator(user_passes_test(check_admin), name="dispatch")
class TaskNameListView(ApiBaseView):
"""resolves to /api/task-name/<task-name>/
GET: return a list of stored results of task
@ -776,6 +792,7 @@ class TaskNameListView(ApiBaseView):
return Response({"message": message})
@method_decorator(user_passes_test(check_admin), name="dispatch")
class TaskIDView(ApiBaseView):
"""resolves to /api/task-id/<task-id>/
GET: return details of task id
@ -827,6 +844,7 @@ class TaskIDView(ApiBaseView):
return f"message:{task_conf.get('group')}:{task_id.split('-')[0]}"
@method_decorator(user_passes_test(check_admin), name="dispatch")
class RefreshView(ApiBaseView):
"""resolves to /api/refresh/
GET: get refresh progress
@ -948,6 +966,7 @@ class SearchView(ApiBaseView):
return Response(search_results)
@method_decorator(user_passes_test(check_admin), name="dispatch")
class TokenView(ApiBaseView):
"""resolves to /api/token/
DELETE: revoke the token

View File

@ -1,4 +1,5 @@
{% load static %}
{% load auth_extras %}
<!DOCTYPE html>
<html lang="en">
<head>
@ -57,9 +58,11 @@
<a href="{% url 'playlist' %}">
<div class="nav-item">playlists</div>
</a>
{% if request.user|has_group:"admin" or request.user.is_staff %}
<a href="{% url 'downloads' %}">
<div class="nav-item">downloads</div>
</a>
{% endif %}
</div>
<div class="nav-icons">
<a href="{% url 'search' %}">

View File

@ -2,11 +2,13 @@
{% load static %}
{% load humanize %}
{% block content %}
{% load auth_extras %}
<div class="boxed-content">
<div class="title-split">
<div class="title-bar">
<h1>Channels</h1>
</div>
{% if request.user|has_group:"admin" or request.user.is_staff %}
<div class="title-split-form">
<img id="animate-icon" onclick="showForm()" src="{% static 'img/icon-add.svg' %}" alt="add-icon" title="Subscribe to Channels">
<div class="show-form">
@ -17,6 +19,7 @@
</form>
</div>
</div>
{% endif %}
</div>
<div id="notifications" data="subscription"></div>
<div class="view-controls">

View File

@ -2,6 +2,8 @@
{% block content %}
{% load static %}
{% load humanize %}
{% load auth_extras %}
<div class="boxed-content">
<div class="channel-banner">
<a href="/channel/{{ channel_info.channel_id }}/"><img src="/cache/channels/{{ channel_info.channel_id }}_banner.jpg" alt="channel_banner"></a>
@ -38,7 +40,9 @@
<p>Subscribers: {{ channel_info.channel_subs|intcomma }}</p>
{% endif %}
{% if channel_info.channel_subscribed %}
{% if request.user|has_group:"admin" or request.user.is_staff %}
<button class="unsubscribe" type="button" data-type="channel" data-subscribe="" data-id="{{ channel_info.channel_id }}" onclick="subscribeStatus(this)" title="Unsubscribe from {{ channel_info.channel_name }}">Unsubscribe</button>
{% endif %}
{% else %}
<button type="button" data-type="channel" data-subscribe="true" data-id="{{ channel_info.channel_id }}" onclick="subscribeStatus(this)" title="Subscribe to {{ channel_info.channel_name }}">Subscribe</button>
{% endif %}

View File

@ -53,7 +53,7 @@
<a href="{% url 'channel_id' playlist.playlist_channel_id %}"><h3>{{ playlist.playlist_channel }}</h3></a>
<a href="{% url 'playlist_id' playlist.playlist_id %}"><h2>{{ playlist.playlist_name }}</h2></a>
<p>Last refreshed: {{ playlist.playlist_last_refresh }}</p>
{% if playlist.playlist_subscribed %}
{% if playlist.playlist_subscribed and request.user|has_group:"admin" or request.user.is_staff %}
<button class="unsubscribe" type="button" data-type="playlist" data-subscribe="" data-id="{{ playlist.playlist_id }}" onclick="subscribeStatus(this)" title="Unsubscribe from {{ playlist.playlist_name }}">Unsubscribe</button>
{% else %}
<button type="button" data-type="playlist" data-subscribe="true" data-id="{{ playlist.playlist_id }}" onclick="subscribeStatus(this)" title="Subscribe to {{ playlist.playlist_name }}">Subscribe</button>

View File

@ -1,11 +1,15 @@
{% extends "home/base.html" %}
{% load static %}
{% block content %}
{% load auth_extras %}
<div class="boxed-content">
<div class="title-split">
<div class="title-bar">
<h1>Playlists</h1>
</div>
{% if request.user|has_group:"admin" or request.user.is_staff %}
<div class="title-split-form">
<img id="animate-icon" onclick="showForm()" src="{% static 'img/icon-add.svg' %}" alt="add-icon" title="Subscribe to Playlists">
<div class="show-form">
@ -16,6 +20,8 @@
</form>
</div>
</div>
{% endif %}
</div>
<div id="notifications" data="subscription"></div>
<div class="view-controls">

View File

@ -2,6 +2,8 @@
{% load static %}
{% load humanize %}
{% block content %}
{% load auth_extras %}
<div class="boxed-content">
<div class="title-bar">
<h1>{{ playlist_info.playlist_name }}</h1>
@ -27,7 +29,9 @@
<p>Last refreshed: {{ playlist_info.playlist_last_refresh }}</p>
<p>Playlist:
{% if playlist_info.playlist_subscribed %}
{% if request.user|has_group:"admin" or request.user.is_staff %}
<button class="unsubscribe" type="button" data-type="playlist" data-subscribe="" data-id="{{ playlist_info.playlist_id }}" onclick="subscribeStatus(this)" title="Unsubscribe from {{ playlist_info.playlist_name }}">Unsubscribe</button>
{% endif %}
{% else %}
<button type="button" data-type="playlist" data-subscribe="true" data-id="{{ playlist_info.playlist_id }}" onclick="subscribeStatus(this)" title="Subscribe to {{ playlist_info.playlist_name }}">Subscribe</button>
{% endif %}

View File

@ -2,6 +2,7 @@
{% block content %}
{% load static %}
{% load humanize %}
{% load auth_extras %}
<div id="player" class="player-wrapper">
<div class="video-main">
<div class="video-modal"><span class="video-modal-text"></span></div>
@ -81,15 +82,19 @@
{% if reindex %}
<p>Reindex scheduled</p>
{% else %}
{% if request.user|has_group:"admin" or request.user.is_staff %}
<div id="reindex-button" class="button-box">
<button data-id="{{ video.youtube_id }}" data-type="video" onclick="reindex(this)" title="Reindex {{ video.title }}">Reindex</button>
</div>
{% endif %}
{% endif %}
{% if request.user|has_group:"admin" or request.user.is_staff %}
<a download="" href="{{ video.media_url }}"><button id="download-item">Download File</button></a>
<button onclick="deleteConfirm()" id="delete-item">Delete Video</button>
<div class="delete-confirm" id="delete-button">
<span>Are you sure? </span><button class="danger-button" onclick="deleteVideo(this)" data-id="{{ video.youtube_id }}" data-redirect = "{{ video.channel.channel_id }}">Delete</button> <button onclick="cancelDelete()">Cancel</button>
</div>
{% endif %}
</div>
</div>
<div class="info-box-item">

View File

@ -0,0 +1,8 @@
from django import template
register = template.Library()
@register.filter(name="has_group")
def has_group(user, group_name):
return user.groups.filter(name=group_name).exists()