Skip to content

Added committee-based permissions #595

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 45 additions & 1 deletion hknweb/settings/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

from hknweb.utils import DATETIME_12_HOUR_FORMAT

from enum import Enum

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

Expand Down Expand Up @@ -179,7 +181,49 @@
CAND_GROUP = "candidate"
OFFICER_GROUP = "officer"
EXEC_GROUP = "exec"

MEMBER_GROUP = "member"

# committee groups
ACT_GROUP = "act"
BRIDGE_GROUP = "bridge"
COMPSERV_GROUP = "compserv"
DECAL_GROUP = "decal"
INDREL_GROUP = "indrel"
PRODEV_GROUP = "prodev"
SERV_GROUP = "serv"
STUDREL_GROUP = "studrel"
TUTORING_GROUP = "tutoring"

COMMITTEE_GROUPS = (
ACT_GROUP,
BRIDGE_GROUP,
COMPSERV_GROUP,
DECAL_GROUP,
INDREL_GROUP,
PRODEV_GROUP,
SERV_GROUP,
STUDREL_GROUP,
TUTORING_GROUP,
)

# exec groups
CSEC_GROUP = "csec"
PRES_GROUP = "pres"
RSEC_GROUP = "rsec"
TRES_GROUP = "tres"
IVP_GROUP = "ivp"
EVP_GROUP = "evp"
DEPREL_GROUP = "deprel"

EXEC_GROUPS = (
CSEC_GROUP,
PRES_GROUP,
RSEC_GROUP,
TRES_GROUP,
IVP_GROUP,
EVP_GROUP,
DEPREL_GROUP,
)

# Note: both candidate and officer group should have permission to add officer challenges

Expand Down
6 changes: 3 additions & 3 deletions hknweb/studentservices/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
from hknweb.events.views.event_transactions.show_event import show_details_helper
from hknweb.utils import (
allow_public_access,
login_and_access_level,
GROUP_TO_ACCESSLEVEL,
login_and_committee,
)
from hknweb.studentservices.models import (
CourseGuideNode,
Expand Down Expand Up @@ -192,7 +192,7 @@ def course_description(request, slug):
return render(request, "studentservices/course_description.html", context=context)


@login_and_access_level(GROUP_TO_ACCESSLEVEL["officer"])
@login_and_committee(settings.TUTORING_GROUP)
def edit_description(request, slug):
course = get_object_or_404(CourseDescription, slug=slug)
if request.method == "GET":
Expand All @@ -211,7 +211,7 @@ def edit_description(request, slug):
return render(request, "studentservices/course_edit.html", context=context)


@login_and_access_level(GROUP_TO_ACCESSLEVEL["officer"])
@login_and_committee(settings.TUTORING_GROUP)
def delete_description(request, slug):
course = get_object_or_404(CourseDescription, slug=slug)

Expand Down
30 changes: 16 additions & 14 deletions hknweb/templates/about/people.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,24 @@
}
</style>
<script>
function toggle_edit(button) {
button.disabled = true;
{% if viewer_in_bridge %}
function toggle_edit(button) {
button.disabled = true;

const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has("edit")) {
urlParams.delete("edit");
} else {
urlParams.set("edit", "true");
}
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has("edit")) {
urlParams.delete("edit");
} else {
urlParams.set("edit", "true");
}

if (history.pushState) {
var newurl = window.location.origin + window.location.pathname + "?" + urlParams.toString();
window.history.replaceState({path: newurl}, "", newurl);
window.location = window.location;
if (history.pushState) {
var newurl = window.location.origin + window.location.pathname + "?" + urlParams.toString();
window.history.replaceState({path: newurl}, "", newurl);
window.location = window.location;
}
}
}
{% endif %}
</script>

<script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script>
Expand All @@ -53,7 +55,7 @@
{% block content %}
<div style="text-align: center; overflow: auto;">
<!-- Edit button -->
{% if is_officer %}
{% if viewer_in_bridge %}
<button
onclick="toggle_edit(this)"
style="border-radius: 0.3em; border-width: 0.02em;"
Expand Down
13 changes: 7 additions & 6 deletions hknweb/templates/committees.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@


{% block "portal-content" %}

<a href="{% url 'tutoring:tutoring_portal' %}">
<div class="portal-content">
<h2>Tutoring</h2>
</div>
</a>
{% if viewer_in_tutoring %}
<a href="{% url 'tutoring:tutoring_portal' %}">
<div class="portal-content">
<h2>Tutoring</h2>
</div>
</a>
{% endif %}

<a href="{% url 'login' %}?next={{ request.path|urlencode }}">
<div class="portal-content">
Expand Down
2 changes: 1 addition & 1 deletion hknweb/templates/studentservices/course_description.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<!-- Title -->
<div class="course-title-section">
<h2 class="course-title">{{ course.title|default:"Course Title" }}</h2>
{% if viewer_is_an_officer %}
{% if viewer_in_tutoring %}
<p> Last Updated: {{ course.updated_at }} </p>
<a href="{{ request.path }}edit"> Edit Page </a>
{% endif %}
Expand Down
5 changes: 3 additions & 2 deletions hknweb/tutoring/views/courses.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from django.shortcuts import render
from hknweb.utils import login_and_access_level, GROUP_TO_ACCESSLEVEL
from hknweb.utils import login_and_committee
from hknweb.studentservices.models import CourseDescription
from hknweb.tutoring.forms import AddCourseForm
from django.conf import settings


@login_and_access_level(GROUP_TO_ACCESSLEVEL["officer"])
@login_and_committee(settings.TUTORING_GROUP)
def courses(request):
if request.method == "POST":
new_course = AddCourseForm(request.POST)
Expand Down
5 changes: 3 additions & 2 deletions hknweb/tutoring/views/tutoringportal.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from django.shortcuts import render
from hknweb.utils import login_and_access_level, GROUP_TO_ACCESSLEVEL
from hknweb.utils import login_and_committee, GROUP_TO_ACCESSLEVEL
from django.conf import settings


@login_and_access_level(GROUP_TO_ACCESSLEVEL["officer"])
@login_and_committee(settings.TUTORING_GROUP)
def tutoringportal(request):
return render(request, "tutoring/portal.html")
45 changes: 43 additions & 2 deletions hknweb/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
from django.utils.safestring import mark_safe
from pytz import timezone

###


# constants

Expand Down Expand Up @@ -99,6 +97,49 @@ def login_and_access_level(access_level):
)


# Committee Permission Checks
def committee_required(committee):
if committee not in settings.COMMITTEE_GROUPS:
return ValueError("Invalid Committee Group")

def test_user(user):
if (
not user.groups.filter(name=committee).exists()
and not user.groups.filter(name=settings.EXEC_GROUP).exists()
):
raise PermissionDenied
return True

return user_passes_test(test_user)


def login_and_committee(committee):
return _wrap_with_access_check(
f"Must be in {committee}",
committee_required(committee),
)


# Exec Permission Checks
def exec_required(position):
if position not in settings.EXEC_GROUPS:
return ValueError("Invalid Exec Position")

def test_user(user):
if not user.groups.filter(name=position).exists():
raise PermissionDenied
return True

return user_passes_test(test_user)


def login_and_exec(position):
return _wrap_with_access_check(
f"Must be {position}",
exec_required(position),
)


def method_login_and_permission(permission_name):
return method_decorator(login_and_permission(permission_name), name="dispatch")

Expand Down
14 changes: 10 additions & 4 deletions hknweb/views/people.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from django.db.models import QuerySet
from django.contrib.auth.models import User
from django.http import Http404
from django.conf import settings
from django.core.exceptions import PermissionDenied

from hknweb.utils import allow_public_access, get_access_level, GROUP_TO_ACCESSLEVEL

Expand All @@ -16,6 +18,13 @@ def people(request):
if "semester" in request.GET and not request.GET["semester"].isdigit():
raise Http404

is_bridge = request.user.groups.filter(name=settings.BRIDGE_GROUP).exists()

# Prevents unauthorized users from just typing the url to edit the page
if request.GET.get("edit") == "true":
if not is_bridge:
raise PermissionDenied

semester: Semester = Semester.objects.filter(
pk=request.GET.get("semester") or None
).first()
Expand All @@ -40,10 +49,8 @@ def people(request):
committee__is_exec=False
).order_by("committee__name")

is_officer = get_access_level(request.user) <= GROUP_TO_ACCESSLEVEL["officer"]

form = ProfilePictureForm(request.POST)
if is_officer and request.method == "POST":
if is_bridge and request.method == "POST":
user = User.objects.get(pk=request.POST["user_id"])
form.instance = user.profile
if form.is_valid():
Expand All @@ -52,7 +59,6 @@ def people(request):
context = {
"execs": execs,
"committeeships": committeeships,
"is_officer": is_officer,
"form": form,
"semester_select_form": SemesterSelectForm({"semester": semester}),
}
Expand Down
25 changes: 16 additions & 9 deletions hknweb/views/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,26 @@

# context processor for base to know whether a user is in the officer group
def add_officer_context(request):
return {
"viewer_is_an_officer": request.user.groups.filter(
name=settings.OFFICER_GROUP
).exists()
usergroups = request.user.groups
context = {
"viewer_is_an_officer": usergroups.filter(name=settings.OFFICER_GROUP).exists()
}
for committee in settings.COMMITTEE_GROUPS:
context[f"viewer_in_{committee}"] = (
usergroups.filter(name=committee).exists()
or usergroups.filter(name=settings.EXEC_GROUP).exists()
)
return context


def add_exec_context(request):
return {
"viewer_is_an_exec": request.user.groups.filter(
name=settings.EXEC_GROUP
).exists()
usergroups = request.user.groups
context = {
"viewer_is_an_exec": usergroups.filter(name=settings.EXEC_GROUP).exists()
}
for exec in settings.EXEC_GROUPS:
context[f"viewer_in_{exec}"] = usergroups.filter(name=exec).exists()
return context


def get_current_cand_semester(): # pragma: no cover
Expand Down Expand Up @@ -77,7 +84,7 @@ def account_create(request):
candidate_password.lower()
== form.cleaned_data["candidate_password"].lower()
):
group = Group.objects.get(name=settings.CAND_GROUP)
group = Group.objects.get(name=settings.UserGroup.CAND_GROUP)
group.user_set.add(user)
group.save()

Expand Down
Loading