Skip to content

Commit 5527d91

Browse files
Fix "log as user" functionality - convert GET to POST requests (#139)
* Initial plan * Fix log as user functionality - convert GET to POST Co-authored-by: alexeygrigorev <875246+alexeygrigorev@users.noreply.github.com> * Add security check - prevent staff from impersonating other staff Co-authored-by: alexeygrigorev <875246+alexeygrigorev@users.noreply.github.com> * Improve code readability - convert lambda to named function Co-authored-by: alexeygrigorev <875246+alexeygrigorev@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: alexeygrigorev <875246+alexeygrigorev@users.noreply.github.com>
1 parent efc0086 commit 5527d91

4 files changed

Lines changed: 91 additions & 10 deletions

File tree

cadmin/templates/cadmin/homework_submissions.html

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,13 @@ <h2>{{ homework.title }} - Submissions</h2>
7070
</td>
7171
{% endfor %}
7272
<td>
73-
<a href="/admin/login/user/{{ data.submission.student.id }}/?next={% url 'cadmin_homework_submissions' course.slug homework.slug %}"
74-
class="btn btn-sm btn-info"
75-
title="Log in as {{ data.submission.student.email }}">
76-
<i class="fas fa-user-circle"></i> Log in as
77-
</a>
73+
<form method="post" action="{% url 'loginas-user-login' data.submission.student.id %}" style="display: inline;">
74+
{% csrf_token %}
75+
<input type="hidden" name="next" value="{% url 'cadmin_homework_submissions' course.slug homework.slug %}">
76+
<button type="submit" class="btn btn-sm btn-info" title="Log in as {{ data.submission.student.email }}">
77+
<i class="fas fa-user-circle"></i> Log in as
78+
</button>
79+
</form>
7880
</td>
7981
</tr>
8082
{% endfor %}

cadmin/templates/cadmin/project_submissions.html

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,13 @@ <h2>{{ project.title }} - Submissions</h2>
9595
<a href="{% url 'cadmin_project_submission_edit' course.slug project.slug submission.id %}" class="btn btn-sm btn-primary">
9696
<i class="fas fa-edit"></i> Edit
9797
</a>
98-
<a href="/admin/login/user/{{ submission.student.id }}/?next={% url 'cadmin_project_submissions' course.slug project.slug %}"
99-
class="btn btn-sm btn-info"
100-
title="Log in as {{ submission.student.email }}">
101-
<i class="fas fa-user-circle"></i> Log in as
102-
</a>
98+
<form method="post" action="{% url 'loginas-user-login' submission.student.id %}" style="display: inline;">
99+
{% csrf_token %}
100+
<input type="hidden" name="next" value="{% url 'cadmin_project_submissions' course.slug project.slug %}">
101+
<button type="submit" class="btn btn-sm btn-info" title="Log in as {{ submission.student.email }}">
102+
<i class="fas fa-user-circle"></i> Log in as
103+
</button>
104+
</form>
103105
</td>
104106
</tr>
105107
{% endfor %}

cadmin/tests/test_views.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,3 +418,59 @@ def test_project_assign_reviews_shows_message(self):
418418
# Check that a message was added
419419
messages = list(response.context['messages'])
420420
self.assertEqual(len(messages), 1)
421+
422+
def test_log_as_user_requires_post_request(self):
423+
"""Test that the log as user endpoint requires a POST request"""
424+
self.client.login(username="admin@test.com", password="admin123")
425+
426+
# Try to access the endpoint with GET - should fail
427+
url = f"/admin/login/user/{self.user.id}/"
428+
response = self.client.get(url)
429+
430+
# Should return 405 Method Not Allowed
431+
self.assertEqual(response.status_code, 405)
432+
433+
def test_log_as_user_with_post_request(self):
434+
"""Test that staff can log in as another user with POST request"""
435+
self.client.login(username="admin@test.com", password="admin123")
436+
437+
# Create enrollment for the user
438+
Enrollment.objects.create(
439+
student=self.user,
440+
course=self.course,
441+
)
442+
443+
# Verify we're logged in as admin
444+
self.assertEqual(self.client.session['_auth_user_id'], str(self.admin_user.id))
445+
446+
# Try to log in as the user with POST
447+
url = f"/admin/login/user/{self.user.id}/"
448+
response = self.client.post(url)
449+
450+
# Should redirect to a page (the login redirect)
451+
self.assertEqual(response.status_code, 302)
452+
453+
# After the POST, we should be logged in as the user
454+
# (The session should have changed to the target user)
455+
# Note: django-loginas stores the original user in a different session key
456+
# and switches the current user to the target user
457+
458+
def test_staff_cannot_impersonate_other_staff(self):
459+
"""Test that staff users cannot impersonate other staff users"""
460+
# Create another staff user
461+
other_staff = User.objects.create_user(
462+
username="staff2@test.com",
463+
email="staff2@test.com",
464+
password="staff123",
465+
is_staff=True,
466+
)
467+
468+
self.client.login(username="admin@test.com", password="admin123")
469+
470+
# Try to log in as another staff user with POST
471+
url = f"/admin/login/user/{other_staff.id}/"
472+
response = self.client.post(url, follow=True)
473+
474+
# Should be redirected back with an error message
475+
# Check that we're still logged in as the original admin user
476+
self.assertEqual(response.wsgi_request.user.username, "admin@test.com")

course_management/settings.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,27 @@
308308
}
309309
}
310310

311+
312+
# django-loginas settings
313+
def can_login_as(request, target_user):
314+
"""
315+
Determine if the current user can impersonate another user.
316+
317+
Staff users can impersonate regular users (students) but cannot
318+
impersonate other staff members or superusers for security reasons.
319+
320+
Args:
321+
request: The current HTTP request
322+
target_user: The user to be impersonated
323+
324+
Returns:
325+
bool: True if impersonation is allowed, False otherwise
326+
"""
327+
return request.user.is_staff and not target_user.is_staff
328+
329+
330+
CAN_LOGIN_AS = can_login_as
331+
311332
# Unfold Configurations
312333
UNFOLD = {
313334
"SITE_HEADER": _("Course Management"),

0 commit comments

Comments
 (0)