Skip to content

Commit 59ba0c7

Browse files
committed
Remove data-leaking endpoints
- /api/v1/groups/members is unused? - (API-)User-search is only used in a location we can remove.
1 parent ee8b179 commit 59ba0c7

File tree

12 files changed

+5
-402
lines changed

12 files changed

+5
-402
lines changed

apps/authentication/api/tests/group_tests.py

-167
Original file line numberDiff line numberDiff line change
@@ -181,170 +181,3 @@ def test_deputy_group_leader_can_delete_group(self):
181181
response = self.client.delete(self.id_url(online_group.id))
182182

183183
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
184-
185-
186-
class GroupMemberTestCase(APITestCase):
187-
@staticmethod
188-
def create_group_roles():
189-
for role_type in RoleType.values:
190-
GroupRole.objects.create(role_type=role_type)
191-
192-
@staticmethod
193-
def get_group_role(role_type: str) -> GroupRole:
194-
return GroupRole.objects.get(role_type=role_type)
195-
196-
def get_role(self, role: RoleType):
197-
return GroupRole.get_for_type(role)
198-
199-
def _create_group(self, **kwargs) -> OnlineGroup:
200-
leader_role = self.get_group_role(RoleType.LEADER)
201-
deputy_leader_role = self.get_group_role(RoleType.DEPUTY_LEADER)
202-
group: OnlineGroup = G(OnlineGroup, **kwargs)
203-
group.admin_roles.add(leader_role)
204-
group.admin_roles.add(deputy_leader_role)
205-
return group
206-
207-
def setUp(self):
208-
self.user: User = generate_user(username="test_user")
209-
self.user.is_superuser = True
210-
self.user.save()
211-
self.user.refresh_from_db()
212-
self.client.force_authenticate(user=self.user)
213-
self.other_user: User = generate_user(username="other_user")
214-
215-
self.url = reverse("group_members-list")
216-
self.id_url = lambda _id: self.url + str(_id) + "/"
217-
self.create_group_roles()
218-
219-
self.group_name = "Noenkom"
220-
self.group_name_long = "Noenkomiteen"
221-
self.group = G(Group, name=self.group_name)
222-
self.group_data = {
223-
"group": self.group.id,
224-
"name_short": self.group_name,
225-
"name_long": self.group_name_long,
226-
}
227-
self.create_group = lambda: self._create_group(
228-
group=self.group, name_short=self.group_name, name_long=self.group_name_long
229-
)
230-
self.online_group = self.create_group()
231-
232-
self.membership_data = {"group": self.online_group.id, "user": self.user.id}
233-
self.create_membership = lambda: GroupMember.objects.create(
234-
user=self.user, group=self.online_group
235-
)
236-
237-
def test_group_members_returns_200(self):
238-
response = self.client.get(self.url)
239-
240-
self.assertEqual(response.status_code, status.HTTP_200_OK)
241-
242-
def test_un_authenticated_user_gets_200(self):
243-
self.client.force_authenticate(user=None)
244-
response = self.client.get(self.url)
245-
246-
self.assertEqual(response.status_code, status.HTTP_200_OK)
247-
248-
def test_superuser_can_create_memberships(self):
249-
self.user.save()
250-
response = self.client.post(self.url, self.membership_data)
251-
252-
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
253-
254-
def test_user_cannot_have_multiple_memberships_in_one_group(self):
255-
self.create_membership()
256-
response = self.client.post(self.url, self.membership_data)
257-
258-
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
259-
self.assertEqual(
260-
response.json().get("non_field_errors"),
261-
["Feltene user, group må gjøre et unikt sett."],
262-
)
263-
264-
def test_regular_user_cannot_create_groups(self):
265-
self.user.is_superuser = False
266-
self.user.save()
267-
268-
response = self.client.post(self.url, self.membership_data)
269-
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
270-
271-
def test_users_with_permission_can_create_memberships(self):
272-
self.user.is_superuser = False
273-
self.user.save()
274-
permission = Permission.objects.get(codename="add_groupmember")
275-
self.user.user_permissions.add(permission)
276-
277-
response = self.client.post(self.url, self.membership_data)
278-
279-
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
280-
281-
def test_superuser_can_delete_memberships(self):
282-
membership = self.create_membership()
283-
284-
response = self.client.delete(self.id_url(membership.id))
285-
286-
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
287-
288-
def test_regular_user_cannot_delete_memberships(self):
289-
self.user.is_superuser = False
290-
self.user.save()
291-
membership = self.create_membership()
292-
293-
response = self.client.delete(self.id_url(membership.id))
294-
295-
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
296-
297-
def test_group_leader_can_delete_memberships(self):
298-
membership = self.create_membership()
299-
membership.roles.add(self.get_group_role(RoleType.LEADER))
300-
self.user.is_superuser = False
301-
self.user.save()
302-
303-
response = self.client.delete(self.id_url(membership.id))
304-
305-
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
306-
307-
def test_deputy_group_leader_can_delete_memberships(self):
308-
membership = self.create_membership()
309-
membership.roles.add(self.get_group_role(RoleType.LEADER))
310-
self.user.is_superuser = False
311-
self.user.save()
312-
313-
response = self.client.delete(self.id_url(membership.id))
314-
315-
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
316-
317-
def test_super_user_can_assign_roles_to_member(self):
318-
self.user.is_superuser = True
319-
self.user.save()
320-
321-
role_ids = [self.get_group_role(RoleType.TREASURER).id]
322-
membership = self.create_membership()
323-
324-
response = self.client.patch(self.id_url(membership.id), {"roles": role_ids})
325-
326-
self.assertEqual(response.status_code, status.HTTP_200_OK)
327-
self.assertEqual(response.json().get("roles"), role_ids)
328-
329-
def test_group_leader_can_assign_roles_to_member(self):
330-
self.user.is_superuser = False
331-
self.user.save()
332-
333-
membership = self.create_membership()
334-
membership.roles.add(self.get_group_role(RoleType.LEADER).id)
335-
role_id = self.get_group_role(RoleType.TREASURER).id
336-
337-
response = self.client.patch(self.id_url(membership.id), {"roles": [role_id]})
338-
339-
self.assertEqual(response.status_code, status.HTTP_200_OK)
340-
self.assertIn(role_id, response.json().get("roles"))
341-
342-
def test_regular_user_cannot_assign_roles_to_member(self):
343-
self.user.is_superuser = False
344-
self.user.save()
345-
346-
membership = self.create_membership()
347-
348-
response = self.client.patch(self.id_url(membership.id))
349-
350-
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

apps/authentication/api/views.py

+1-13
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,12 @@
77
from rest_framework.permissions import AllowAny, IsAuthenticated
88
from rest_framework.response import Response
99

10-
from apps.authentication.models import GroupMember, GroupRole, OnlineGroup
10+
from apps.authentication.models import GroupRole, OnlineGroup
1111
from apps.authentication.models import OnlineUser as User
1212
from apps.authentication.models import Position, SpecialPosition
1313
from apps.authentication.serializers import (
1414
AnonymizeUserSerializer,
15-
GroupMemberCreateSerializer,
1615
GroupMemberReadOnlySerializer,
17-
GroupMemberUpdateSerializer,
1816
GroupReadOnlySerializer,
1917
GroupRoleReadOnlySerializer,
2018
OnlineGroupCreateOrUpdateSerializer,
@@ -172,16 +170,6 @@ def group_users(self, request, pk: int = None):
172170
return Response(data=serializer.data, status=status.HTTP_200_OK)
173171

174172

175-
class GroupMemberViewSet(MultiSerializerMixin, viewsets.ModelViewSet):
176-
permission_classes = (DjangoObjectPermissionOrAnonReadOnly,)
177-
queryset = GroupMember.objects.all()
178-
serializer_classes = {
179-
"create": GroupMemberCreateSerializer,
180-
"update": GroupMemberUpdateSerializer,
181-
"read": GroupMemberReadOnlySerializer,
182-
}
183-
184-
185173
class GroupRoleViewSet(viewsets.ReadOnlyModelViewSet):
186174
permission_classes = (AllowAny,)
187175
serializer_class = GroupRoleReadOnlySerializer

apps/authentication/serializers.py

-17
Original file line numberDiff line numberDiff line change
@@ -225,23 +225,6 @@ class Meta:
225225
read_only = True
226226

227227

228-
class GroupMemberCreateSerializer(serializers.ModelSerializer):
229-
user = serializers.PrimaryKeyRelatedField(
230-
required=True, queryset=User.objects.all()
231-
)
232-
group = serializers.PrimaryKeyRelatedField(
233-
required=True, queryset=OnlineGroup.objects.all()
234-
)
235-
roles = serializers.PrimaryKeyRelatedField(
236-
required=False, queryset=GroupRole.objects.all(), many=True
237-
)
238-
239-
class Meta:
240-
model = GroupMember
241-
fields = ("id", "user", "group", "added", "roles", "is_on_leave", "is_retired")
242-
read_only_fields = ("added",)
243-
244-
245228
class GroupMemberUpdateSerializer(serializers.ModelSerializer):
246229
roles = serializers.PrimaryKeyRelatedField(
247230
required=False, queryset=GroupRole.objects.all(), many=True

apps/authentication/urls.py

-1
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,4 @@
2020
router.register(
2121
"group/online-groups", api_views.OnlineGroupViewSet, basename="online_groups"
2222
)
23-
router.register("group/members", api_views.GroupMemberViewSet, basename="group_members")
2423
router.register("group/roles", api_views.GroupRoleViewSet, basename="group_roles")

apps/profiles/tests.py

-13
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,12 @@
11
from django.test import TestCase
22
from django.urls import reverse
33
from django_dynamic_fixture import G
4-
from rest_framework import status
54

65
from apps.authentication.models import OnlineUser as User
76
from apps.profiles.forms import ZIP_CODE_VALIDATION_ERROR, ProfileForm
87
from apps.profiles.models import Privacy
98

109

11-
class ProfilesURLTestCase(TestCase):
12-
def test_user_search(self):
13-
user = G(User)
14-
url = reverse("profiles_user_search")
15-
16-
self.client.force_login(user)
17-
18-
response = self.client.get(url)
19-
20-
self.assertEqual(response.status_code, status.HTTP_200_OK)
21-
22-
2310
class ProfileInitTestCase(TestCase):
2411
def test_privacy_profile_save(self):
2512
user = G(User)

apps/profiles/urls.py

+1-6
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,12 @@
4545
views.update_mark_rules,
4646
name="profile_update_mark_rules",
4747
),
48-
# Endpoint that exposes a json lump of all users but only id and name.
48+
# Endpoint that exposes a json lump of all users but only id and name. Only used for autocomplete in the dashboard
4949
re_path(
5050
r"^api_plain_user_search/$",
5151
views.api_plain_user_search,
5252
name="profiles_api_plain_user_search",
5353
),
54-
# Endpoint that exposes a json lump of all users which have set their profile to public.
55-
re_path(
56-
"^api_user_search/$", views.api_user_search, name="profiles_api_user_search"
57-
),
58-
re_path(r"^user_search/$", views.user_search, name="profiles_user_search"),
5954
# Profile index with active tab.
6055
re_path(r"^(?P<active_tab>\w+)/$", views.index, name="profiles_active"),
6156
]

apps/profiles/views.py

+3-38
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from django.contrib import messages
66
from django.contrib.auth.decorators import login_required
77
from django.contrib.auth.models import Group
8-
from django.db.models import Q
98
from django.http import Http404, HttpResponse, JsonResponse
109
from django.shortcuts import get_object_or_404, redirect, render
1110
from django.utils.translation import gettext as _
@@ -17,8 +16,6 @@
1716

1817
from apps.approval.forms import FieldOfStudyApplicationForm
1918
from apps.approval.models import MembershipApproval
20-
from apps.authentication.constants import GroupType
21-
from apps.authentication.models import OnlineGroup
2219
from apps.authentication.models import OnlineUser as User
2320
from apps.authentication.models import Position
2421
from apps.dashboard.tools import has_access
@@ -265,44 +262,12 @@ def toggle_jobmail(request):
265262
raise Http404
266263

267264

268-
@login_required
269-
def user_search(request):
270-
committee_groups = OnlineGroup.objects.filter(
271-
Q(group_type=GroupType.COMMITTEE) | Q(group_type=GroupType.NODE_COMMITTEE)
272-
)
273-
groups_to_include = [online_group.group.pk for online_group in committee_groups]
274-
groups = Group.objects.filter(pk__in=groups_to_include).order_by("name")
275-
users_to_display = User.objects.filter(privacy__visible_for_other_users=True)
276-
277-
context = {"users": users_to_display, "groups": groups}
278-
return render(request, "profiles/users.html", context)
279-
280-
281-
@login_required
282-
def api_user_search(request):
283-
if request.GET.get("query"):
284-
users = search_for_users(request.GET.get("query"))
285-
return render_json(users)
286-
return render_json(error="Mangler søkestreng")
287-
288-
289-
def search_for_users(query, limit=10):
290-
if not query:
291-
return []
292-
293-
results = []
294-
295-
for result in watson.search(
296-
query, models=(User.objects.filter(privacy__visible_for_other_users=True),)
297-
):
298-
results.append(result.object)
299-
300-
return results[:limit]
301-
302-
303265
@login_required
304266
def api_plain_user_search(request):
305267
"""The difference between plain_user_search and the other is exposing only id and name."""
268+
if not request.user.is_staff:
269+
raise PermissionError()
270+
306271
if request.GET.get("query"):
307272
users = search_for_plain_users(request.GET.get("query"))
308273
return JsonResponse(users, safe=False)

assets/common/typeahead/index.js

-16
Original file line numberDiff line numberDiff line change
@@ -50,20 +50,4 @@ export const plainUserTypeahead = (element, select) => {
5050
});
5151
};
5252

53-
export const userTypeahead = (element, select) => {
54-
const userSearchTemplate = template(`
55-
<div>
56-
<img width="100%" src="<%= image %>" alt="" />
57-
<span data-id="<%= id %>" class="user-meta"><h4><%= name %></h4>
58-
</div>
59-
`);
60-
61-
typeahead(element, {
62-
template: userSearchTemplate,
63-
url: `${Urls.profiles_api_user_search()}?query=%QUERY`,
64-
select,
65-
name: 'users',
66-
});
67-
};
68-
6953
export { typeahead as default };

assets/profiles/index.js

-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { loadCityFromZipCode } from 'common/utils/';
22
import DependentDocumentation from './dependent_fields';
3-
import enableUserSearch from './userSearch';
43
import './profiles';
54
import './less/profiles.less';
65

@@ -10,8 +9,4 @@ if (zipCodeElement) {
109
loadCityFromZipCode(zipCodeElement, cityElement);
1110
}
1211

13-
const userSearchElement = document.getElementById('user-search');
14-
if (userSearchElement) {
15-
enableUserSearch();
16-
}
1712
DependentDocumentation();

0 commit comments

Comments
 (0)