Skip to content
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

Make impersonate_user local to a course #670

Open
wants to merge 5 commits into
base: main
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
56 changes: 45 additions & 11 deletions course/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,9 @@
user_status,
participation_status,
participation_permission as pperm,
COURSE_ID_REGEX,
)
from course.models import Participation, ParticipationRole, AuthenticationToken # noqa
from course.models import Participation, ParticipationRole, AuthenticationToken, Course # noqa
from accounts.models import User
from course.utils import render_course_page, course_view

Expand All @@ -82,14 +83,13 @@ def get_pre_impersonation_user(request):
return None


def get_impersonable_user_qset(impersonator):
def get_impersonable_user_qset(impersonator, course_identifier):
# type: (User) -> query.QuerySet
if impersonator.is_superuser:
return User.objects.exclude(pk=impersonator.pk)

my_participations = Participation.objects.filter(
user=impersonator,
status=participation_status.active)
status=participation_status.active,
course__identifier=course_identifier)

impersonable_user_qset = User.objects.none()
for part in my_participations:
Expand Down Expand Up @@ -120,13 +120,22 @@ def get_impersonable_user_qset(impersonator):
return impersonable_user_qset


def _get_current_course_from_request(request):
course_match = re.match("^/course/"+COURSE_ID_REGEX+"/", request.get_full_path())
if course_match is None:
return None
else:
return course_match.group("course_identifier")


class ImpersonateMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response

def __call__(self, request):
if 'impersonate_id' in request.session:
imp_id = request.session['impersonate_id']
cur_course_identifier = _get_current_course_from_request(request)
impersonee = None

try:
Expand All @@ -136,17 +145,34 @@ def __call__(self, request):
pass

may_impersonate = False
permission_error = False
if impersonee is not None:
if request.user.is_superuser:
may_impersonate = True
else:
qset = get_impersonable_user_qset(cast(User, request.user))
if qset.filter(pk=cast(User, impersonee).pk).count():
may_impersonate = True
imp_course_identifier = request.session['impersonate_course_identifier']
if cur_course_identifier is not None:
if cur_course_identifier == imp_course_identifier:
qset = get_impersonable_user_qset(cast(User, request.user),
course_identifier=imp_course_identifier)
if qset.filter(pk=cast(User, impersonee).pk).count():
may_impersonate = True
else:
permission_error = True
else:
permission_error = True

if may_impersonate:
request.relate_impersonate_original_user = request.user
request.user = impersonee
elif request.get_full_path() == "/user/stop_impersonating/":
pass
elif request.get_full_path().startswith("/logout/"):
pass
elif permission_error:
messages.add_message(request, messages.WARNING,
_("Permission denied to impersonate this page. Falling back to " + \
cast(User, request.user).username))
else:
messages.add_message(request, messages.ERROR,
_("Error while impersonating."))
Expand Down Expand Up @@ -209,12 +235,17 @@ def __init__(self, *args, **kwargs):
self.helper.add_input(Submit("submit", _("Impersonate")))


def impersonate(request):
def impersonate(request, course_identifier):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should have a non-course entry point (URL) for the (global) impersonation. (Not for me, but I'm thinking of other folks serving as admins of Relate instances.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would they be superusers? Or users with a relate permission level?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would they be superusers?

Yep.

# type: (http.HttpRequest) -> http.HttpResponse
if not request.user.is_authenticated:
raise PermissionDenied()

impersonable_user_qset = get_impersonable_user_qset(cast(User, request.user))
impersonator = cast(User, request.user)
if impersonator.is_superuser:
impersonable_user_qset = User.objects.exclude(pk=impersonator.pk)
else:
impersonable_user_qset = get_impersonable_user_qset(impersonator,
course_identifier=course_identifier)
if not impersonable_user_qset.count():
raise PermissionDenied()

Expand All @@ -236,6 +267,9 @@ def impersonate(request):
request.session['impersonate_id'] = impersonee.id
request.session['relate_impersonation_header'] = form.cleaned_data[
"add_impersonation_header"]
if course_identifier is not None:
request.session['impersonate_course_identifier'] = course_identifier
return redirect("/course/{}".format(course_identifier))

# Because we'll likely no longer have access to this page.
return redirect("relate-home")
Expand All @@ -260,7 +294,7 @@ def stop_impersonating(request):
if "stop_impersonating" not in request.POST:
raise SuspiciousOperation(_("odd POST parameters"))

if not hasattr(request, "relate_impersonate_original_user"):
if not 'impersonate_id' in request.session:
# prevent user without pperm to stop_impersonating
my_participations = Participation.objects.filter(
user=request.user,
Expand Down
2 changes: 1 addition & 1 deletion relate/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@
{% if currently_impersonating %}
<li><a onclick=stop_impersonate()>{% trans "Stop impersonating" %}</a></li>
{% else %}
<li><a href="{% url 'relate-impersonate' %}" target="_blank">{% trans "Impersonate user" %}</a></li>
<li><a href="{% url 'relate-impersonate' course.identifier %}" target="_blank">{% trans "Impersonate user" %}</a></li>
{% endif %}
{% if pperm.set_fake_time or request.relate_impersonate_original_user|may_set_fake_time %}
<li><a href="{% url 'relate-set_fake_time' %}" target="_blank">{% trans "Set fake time" %}</a></li>
Expand Down
7 changes: 4 additions & 3 deletions relate/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,12 @@
name="relate-monitor_task"),

# {{{ troubleshooting

url(r'^user/impersonate/$',
url(r"^course"
"/" + COURSE_ID_REGEX
+ "/impersonate/$",
course.auth.impersonate,
name="relate-impersonate"),
url(r'^user/stop_impersonating/$',
url(r"^user/stop_impersonating/$",
course.auth.stop_impersonating,
name="relate-stop_impersonating"),

Expand Down