Skip to content
Merged

Bugfix #1685

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
8 changes: 2 additions & 6 deletions cms/server/admin/handlers/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from cms import ServiceCoord, get_service_shards, get_service_address
from cms.db import Admin, Contest, Question
from cms.server.jinja2_toolbox import markdown_filter
from cms.server.util import normalize_login_next_page
from cmscommon.crypto import validate_password
from cmscommon.datetime import make_datetime, make_timestamp
from .base import BaseHandler, SimpleHandler, require_permission
Expand All @@ -48,12 +49,7 @@ def post(self):
next_page: str = self.get_argument("next", None)
Comment thread
pxsit marked this conversation as resolved.
if next_page is not None:
error_args["next"] = next_page
if next_page != "/":
next_page = self.url(*next_page.strip("/").split("/"))
else:
next_page = self.url()
else:
next_page = self.url()
next_page = normalize_login_next_page(next_page, self.url, self.url())
error_page = self.url("login", **error_args)

username: str = self.get_argument("username", "")
Expand Down
8 changes: 2 additions & 6 deletions cms/server/contest/handlers/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
from cms.grading.languagemanager import get_language
from cms.grading.steps import COMPILATION_MESSAGES, EVALUATION_MESSAGES
from cms.server import multi_contest
from cms.server.util import normalize_login_next_page
from cms.server.contest.authentication import validate_login
from cms.server.contest.communication import get_communications
from cmscommon.crypto import hash_password, validate_password
Expand Down Expand Up @@ -217,12 +218,7 @@ def post(self):
next_page: str | None = self.get_argument("next", None)
if next_page is not None:
error_args["next"] = next_page
if next_page != "/":
next_page = self.url(*next_page.strip("/").split("/"))
else:
next_page = self.url()
else:
next_page = self.contest_url()
next_page = normalize_login_next_page(next_page, self.url, self.contest_url())
error_page = self.contest_url(**error_args)

username: str = self.get_argument("username", "")
Expand Down
34 changes: 33 additions & 1 deletion cms/server/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

import logging
from functools import wraps
from urllib.parse import quote, urlencode
from urllib.parse import quote, urlencode, urlsplit

import collections
try:
Expand Down Expand Up @@ -168,6 +168,38 @@ def __getitem__(self, component: object) -> typing.Self:
return self.__class__(self.__call__(component))


def normalize_login_next_page(next_page: str | None, url: Url, default_url: str) -> str:
"""Normalize a login redirection target.

Accept only local absolute-path targets (plus optional query), and
rebase them through the provided URL builder. Query-only values are
treated as "/" and preserved.

next_page: raw value of the "next" parameter.
url: URL builder for local paths.
default_url: fallback when next_page is missing or invalid.

return: normalized redirect target.
"""
if next_page is None:
return default_url

split = urlsplit(next_page)
path = split.path or "/"
if split.scheme or split.netloc or not path.startswith("/"):
return default_url

if path != "/":
normalized = url(*path.strip("/").split("/"))
else:
normalized = url()

if split.query:
normalized += "?" + split.query

return normalized


class CommonRequestHandler(RequestHandler):
"""Encapsulates shared RequestHandler functionality.

Expand Down
Loading