From 31f33da625167b60ff2d197f0531d32d32a7ee96 Mon Sep 17 00:00:00 2001 From: Dillon Dumesnil Date: Fri, 18 Feb 2022 13:20:20 -0500 Subject: [PATCH] fix: Update get user timezone logic Adds in sanitization of the timezone to ensure the timezone is formatted correctly and error handling to reasonably fall back to UTC --- lms/djangoapps/courseware/context_processor.py | 15 +++++++++++++-- .../courseware/tests/test_context_processor.py | 9 +++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/lms/djangoapps/courseware/context_processor.py b/lms/djangoapps/courseware/context_processor.py index d1d66d6fff..2832c577ca 100644 --- a/lms/djangoapps/courseware/context_processor.py +++ b/lms/djangoapps/courseware/context_processor.py @@ -5,8 +5,11 @@ This is meant to simplify the process of sending user preferences (espec. time_z to the templates without having to append every view file. """ +import string + from django.utils.translation import get_language from pytz import timezone +from pytz.exceptions import UnknownTimeZoneError from edx_django_utils.cache import TieredCache from lms.djangoapps.courseware.models import LastSeenCoursewareTimezone @@ -81,11 +84,19 @@ def get_user_timezone_or_last_seen_timezone_or_utc(user): Helper method for returning a reasonable timezone for a user. This method returns the timezone in the user's account if that is set. If that is not set, it returns a recent timezone that we have recorded from a user's visit to the courseware. - If that is not set, it returns UTC. + If that is not set or the timezone is unknown, it returns UTC. """ user_timezone = ( get_user_preference(user, 'time_zone') or get_last_seen_courseware_timezone(user) or 'UTC' ) - return timezone(user_timezone) + # We have seen non-printable characters (i.e. \x00) showing up in the + # user_timezone (I believe via the get_last_seen_courseware_timezone method). + # This sanitizes the user_timezone before passing it in. + user_timezone = filter(lambda l: l in string.printable, user_timezone) + user_timezone = ''.join(user_timezone) + try: + return timezone(user_timezone) + except UnknownTimeZoneError as err: + return timezone('UTC') diff --git a/lms/djangoapps/courseware/tests/test_context_processor.py b/lms/djangoapps/courseware/tests/test_context_processor.py index 8da91c2693..e1a65d822f 100644 --- a/lms/djangoapps/courseware/tests/test_context_processor.py +++ b/lms/djangoapps/courseware/tests/test_context_processor.py @@ -62,9 +62,9 @@ class UserPrefContextProcessorUnitTest(ModuleStoreTestCase): time_zone = get_user_timezone_or_last_seen_timezone_or_utc(self.user) assert time_zone == timezone('UTC') - # We record the timezone when a user hits the courseware api + # We record the timezone when a user hits the courseware api. Also sanitize input test self.client.login(username=self.user.username, password='foo') - self.client.get(f'/api/courseware/course/{course.id}?browser_timezone=America/New_York') + self.client.get(f'/api/courseware/course/{course.id}?browser_timezone=America/New_York\x00') time_zone = get_user_timezone_or_last_seen_timezone_or_utc(self.user) assert time_zone == timezone('America/New_York') @@ -72,3 +72,8 @@ class UserPrefContextProcessorUnitTest(ModuleStoreTestCase): set_user_preference(self.user, 'time_zone', 'Asia/Tokyo') time_zone = get_user_timezone_or_last_seen_timezone_or_utc(self.user) assert time_zone == timezone('Asia/Tokyo') + + # If we do not recognize the user's timezone, we default to UTC + with patch('lms.djangoapps.courseware.context_processor.get_user_preference', return_value='Unknown/Timezone'): + time_zone = get_user_timezone_or_last_seen_timezone_or_utc(self.user) + assert time_zone == timezone('UTC')