Skip to content

Commit e39741d

Browse files
authored
Merge pull request #595 from ryantrinh05/permissions
Added committee-based permissions
2 parents 2848b8a + e0b2961 commit e39741d

File tree

11 files changed

+273
-44
lines changed

11 files changed

+273
-44
lines changed

hknweb/settings/common.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
from hknweb.utils import DATETIME_12_HOUR_FORMAT
1717

18+
from enum import Enum
19+
1820
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
1921
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
2022

@@ -179,7 +181,49 @@
179181
CAND_GROUP = "candidate"
180182
OFFICER_GROUP = "officer"
181183
EXEC_GROUP = "exec"
182-
184+
MEMBER_GROUP = "member"
185+
186+
# committee groups
187+
ACT_GROUP = "act"
188+
BRIDGE_GROUP = "bridge"
189+
COMPSERV_GROUP = "compserv"
190+
DECAL_GROUP = "decal"
191+
INDREL_GROUP = "indrel"
192+
PRODEV_GROUP = "prodev"
193+
SERV_GROUP = "serv"
194+
STUDREL_GROUP = "studrel"
195+
TUTORING_GROUP = "tutoring"
196+
197+
COMMITTEE_GROUPS = (
198+
ACT_GROUP,
199+
BRIDGE_GROUP,
200+
COMPSERV_GROUP,
201+
DECAL_GROUP,
202+
INDREL_GROUP,
203+
PRODEV_GROUP,
204+
SERV_GROUP,
205+
STUDREL_GROUP,
206+
TUTORING_GROUP,
207+
)
208+
209+
# exec groups
210+
CSEC_GROUP = "csec"
211+
PRES_GROUP = "pres"
212+
RSEC_GROUP = "rsec"
213+
TRES_GROUP = "tres"
214+
IVP_GROUP = "ivp"
215+
EVP_GROUP = "evp"
216+
DEPREL_GROUP = "deprel"
217+
218+
EXEC_GROUPS = (
219+
CSEC_GROUP,
220+
PRES_GROUP,
221+
RSEC_GROUP,
222+
TRES_GROUP,
223+
IVP_GROUP,
224+
EVP_GROUP,
225+
DEPREL_GROUP,
226+
)
183227

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

hknweb/studentservices/views.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
from hknweb.events.views.event_transactions.show_event import show_details_helper
1212
from hknweb.utils import (
1313
allow_public_access,
14-
login_and_access_level,
1514
GROUP_TO_ACCESSLEVEL,
15+
login_and_committee,
1616
)
1717
from hknweb.studentservices.models import (
1818
CourseGuideNode,
@@ -192,7 +192,7 @@ def course_description(request, slug):
192192
return render(request, "studentservices/course_description.html", context=context)
193193

194194

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

213213

214-
@login_and_access_level(GROUP_TO_ACCESSLEVEL["officer"])
214+
@login_and_committee(settings.TUTORING_GROUP)
215215
def delete_description(request, slug):
216216
course = get_object_or_404(CourseDescription, slug=slug)
217217

hknweb/templates/about/people.html

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,24 @@
1717
}
1818
</style>
1919
<script>
20-
function toggle_edit(button) {
21-
button.disabled = true;
20+
{% if viewer_in_bridge %}
21+
function toggle_edit(button) {
22+
button.disabled = true;
2223

23-
const urlParams = new URLSearchParams(window.location.search);
24-
if (urlParams.has("edit")) {
25-
urlParams.delete("edit");
26-
} else {
27-
urlParams.set("edit", "true");
28-
}
24+
const urlParams = new URLSearchParams(window.location.search);
25+
if (urlParams.has("edit")) {
26+
urlParams.delete("edit");
27+
} else {
28+
urlParams.set("edit", "true");
29+
}
2930

30-
if (history.pushState) {
31-
var newurl = window.location.origin + window.location.pathname + "?" + urlParams.toString();
32-
window.history.replaceState({path: newurl}, "", newurl);
33-
window.location = window.location;
31+
if (history.pushState) {
32+
var newurl = window.location.origin + window.location.pathname + "?" + urlParams.toString();
33+
window.history.replaceState({path: newurl}, "", newurl);
34+
window.location = window.location;
35+
}
3436
}
35-
}
37+
{% endif %}
3638
</script>
3739

3840
<script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script>
@@ -53,7 +55,7 @@
5355
{% block content %}
5456
<div style="text-align: center; overflow: auto;">
5557
<!-- Edit button -->
56-
{% if is_officer %}
58+
{% if viewer_in_bridge %}
5759
<button
5860
onclick="toggle_edit(this)"
5961
style="border-radius: 0.3em; border-width: 0.02em;"

hknweb/templates/committees.html

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@
77

88

99
{% block "portal-content" %}
10-
11-
<a href="{% url 'tutoring:tutoring_portal' %}">
12-
<div class="portal-content">
13-
<h2>Tutoring</h2>
14-
</div>
15-
</a>
10+
{% if viewer_in_tutoring %}
11+
<a href="{% url 'tutoring:tutoring_portal' %}">
12+
<div class="portal-content">
13+
<h2>Tutoring</h2>
14+
</div>
15+
</a>
16+
{% endif %}
1617

1718
<a href="{% url 'login' %}?next={{ request.path|urlencode }}">
1819
<div class="portal-content">

hknweb/templates/studentservices/course_description.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
<!-- Title -->
1717
<div class="course-title-section">
1818
<h2 class="course-title">{{ course.title|default:"Course Title" }}</h2>
19-
{% if viewer_is_an_officer %}
19+
{% if viewer_in_tutoring %}
2020
<p> Last Updated: {{ course.updated_at }} </p>
2121
<a href="{{ request.path }}edit"> Edit Page </a>
2222
{% endif %}

hknweb/tutoring/views/courses.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
from django.shortcuts import render
2-
from hknweb.utils import login_and_access_level, GROUP_TO_ACCESSLEVEL
2+
from hknweb.utils import login_and_committee
33
from hknweb.studentservices.models import CourseDescription
44
from hknweb.tutoring.forms import AddCourseForm
5+
from django.conf import settings
56

67

7-
@login_and_access_level(GROUP_TO_ACCESSLEVEL["officer"])
8+
@login_and_committee(settings.TUTORING_GROUP)
89
def courses(request):
910
if request.method == "POST":
1011
new_course = AddCourseForm(request.POST)
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from django.shortcuts import render
2-
from hknweb.utils import login_and_access_level, GROUP_TO_ACCESSLEVEL
2+
from hknweb.utils import login_and_committee, GROUP_TO_ACCESSLEVEL
3+
from django.conf import settings
34

45

5-
@login_and_access_level(GROUP_TO_ACCESSLEVEL["officer"])
6+
@login_and_committee(settings.TUTORING_GROUP)
67
def tutoringportal(request):
78
return render(request, "tutoring/portal.html")

hknweb/utils.py

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@
2121
from django.utils.safestring import mark_safe
2222
from pytz import timezone
2323

24-
###
25-
2624

2725
# constants
2826

@@ -99,6 +97,49 @@ def login_and_access_level(access_level):
9997
)
10098

10199

100+
# Committee Permission Checks
101+
def committee_required(committee):
102+
if committee not in settings.COMMITTEE_GROUPS:
103+
return ValueError("Invalid Committee Group")
104+
105+
def test_user(user):
106+
if (
107+
not user.groups.filter(name=committee).exists()
108+
and not user.groups.filter(name=settings.EXEC_GROUP).exists()
109+
):
110+
raise PermissionDenied
111+
return True
112+
113+
return user_passes_test(test_user)
114+
115+
116+
def login_and_committee(committee):
117+
return _wrap_with_access_check(
118+
f"Must be in {committee}",
119+
committee_required(committee),
120+
)
121+
122+
123+
# Exec Permission Checks
124+
def exec_required(position):
125+
if position not in settings.EXEC_GROUPS:
126+
return ValueError("Invalid Exec Position")
127+
128+
def test_user(user):
129+
if not user.groups.filter(name=position).exists():
130+
raise PermissionDenied
131+
return True
132+
133+
return user_passes_test(test_user)
134+
135+
136+
def login_and_exec(position):
137+
return _wrap_with_access_check(
138+
f"Must be {position}",
139+
exec_required(position),
140+
)
141+
142+
102143
def method_login_and_permission(permission_name):
103144
return method_decorator(login_and_permission(permission_name), name="dispatch")
104145

hknweb/views/people.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
from django.db.models import QuerySet
33
from django.contrib.auth.models import User
44
from django.http import Http404
5+
from django.conf import settings
6+
from django.core.exceptions import PermissionDenied
57

68
from hknweb.utils import allow_public_access, get_access_level, GROUP_TO_ACCESSLEVEL
79

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

21+
is_bridge = request.user.groups.filter(name=settings.BRIDGE_GROUP).exists()
22+
23+
# Prevents unauthorized users from just typing the url to edit the page
24+
if request.GET.get("edit") == "true":
25+
if not is_bridge:
26+
raise PermissionDenied
27+
1928
semester: Semester = Semester.objects.filter(
2029
pk=request.GET.get("semester") or None
2130
).first()
@@ -40,10 +49,8 @@ def people(request):
4049
committee__is_exec=False
4150
).order_by("committee__name")
4251

43-
is_officer = get_access_level(request.user) <= GROUP_TO_ACCESSLEVEL["officer"]
44-
4552
form = ProfilePictureForm(request.POST)
46-
if is_officer and request.method == "POST":
53+
if is_bridge and request.method == "POST":
4754
user = User.objects.get(pk=request.POST["user_id"])
4855
form.instance = user.profile
4956
if form.is_valid():
@@ -52,7 +59,6 @@ def people(request):
5259
context = {
5360
"execs": execs,
5461
"committeeships": committeeships,
55-
"is_officer": is_officer,
5662
"form": form,
5763
"semester_select_form": SemesterSelectForm({"semester": semester}),
5864
}

hknweb/views/users.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,26 @@
2121

2222
# context processor for base to know whether a user is in the officer group
2323
def add_officer_context(request):
24-
return {
25-
"viewer_is_an_officer": request.user.groups.filter(
26-
name=settings.OFFICER_GROUP
27-
).exists()
24+
usergroups = request.user.groups
25+
context = {
26+
"viewer_is_an_officer": usergroups.filter(name=settings.OFFICER_GROUP).exists()
2827
}
28+
for committee in settings.COMMITTEE_GROUPS:
29+
context[f"viewer_in_{committee}"] = (
30+
usergroups.filter(name=committee).exists()
31+
or usergroups.filter(name=settings.EXEC_GROUP).exists()
32+
)
33+
return context
2934

3035

3136
def add_exec_context(request):
32-
return {
33-
"viewer_is_an_exec": request.user.groups.filter(
34-
name=settings.EXEC_GROUP
35-
).exists()
37+
usergroups = request.user.groups
38+
context = {
39+
"viewer_is_an_exec": usergroups.filter(name=settings.EXEC_GROUP).exists()
3640
}
41+
for exec in settings.EXEC_GROUPS:
42+
context[f"viewer_in_{exec}"] = usergroups.filter(name=exec).exists()
43+
return context
3744

3845

3946
def get_current_cand_semester(): # pragma: no cover
@@ -77,7 +84,7 @@ def account_create(request):
7784
candidate_password.lower()
7885
== form.cleaned_data["candidate_password"].lower()
7986
):
80-
group = Group.objects.get(name=settings.CAND_GROUP)
87+
group = Group.objects.get(name=settings.UserGroup.CAND_GROUP)
8188
group.user_set.add(user)
8289
group.save()
8390

0 commit comments

Comments
 (0)