Files
edx-platform/lms/djangoapps/mobile_api/middleware.py
Kyle McCormick d1a775d3cd Use full names for lms.djangoapps imports (#25401)
* Use full LMS imports paths in LMS settings and urls modules
* Use full LMS import paths in Studio settings and urls modules
* Import from lms.djangoapps.badges instead of badges
* Import from lms.djangoapps.branding instead of branding
* Import from lms.djangoapps.bulk_email instead of bulk_email
* Import from lms.djangoapps.bulk_enroll instead of bulk_enroll
* Import from lms.djangoapps.ccx instead of ccx
* Import from lms.djangoapps.course_api instead of course_api
* Import from lms.djangoapps.course_blocks instead of course_blocks
* Import from lms.djangoapps.course_wiki instead of course_wiki
* Import from lms.djangoapps.courseware instead of courseware
* Import from lms.djangoapps.dashboard instead of dashboard
* Import from lms.djangoapps.discussion import discussion
* Import from lms.djangoapps.email_marketing instead of email_marketing
* Import from lms.djangoapps.experiments instead of experiments
* Import from lms.djangoapps.gating instead of gating
* Import from lms.djangoapps.grades instead of grades
* Import from lms.djangoapps.instructor_analytics instead of instructor_analytics
* Import form lms.djangoapps.lms_xblock instead of lms_xblock
* Import from lms.djangoapps.lti_provider instead of lti_provider
* Import from lms.djangoapps.mobile_api instead of mobile_api
* Import from lms.djangoapps.rss_proxy instead of rss_proxy
* Import from lms.djangoapps.static_template_view instead of static_template_view
* Import from lms.djangoapps.survey instead of survey
* Import from lms.djangoapps.verify_student instead of verify_student
* Stop suppressing EdxPlatformDeprecatedImportWarnings
2020-11-04 08:48:33 -05:00

140 lines
6.4 KiB
Python

"""
Middleware for Mobile APIs
"""
from datetime import datetime
from django.conf import settings
from django.core.cache import cache
from django.http import HttpResponse
from django.utils.deprecation import MiddlewareMixin
from pytz import UTC
import six
from lms.djangoapps.mobile_api.mobile_platform import MobilePlatform
from lms.djangoapps.mobile_api.models import AppVersionConfig
from lms.djangoapps.mobile_api.utils import parsed_version
from openedx.core.lib.cache_utils import get_cache
from openedx.core.lib.mobile_utils import is_request_from_mobile_app
class AppVersionUpgrade(MiddlewareMixin):
"""
Middleware class to keep track of mobile application version being used.
"""
LATEST_VERSION_HEADER = 'EDX-APP-LATEST-VERSION'
LAST_SUPPORTED_DATE_HEADER = 'EDX-APP-VERSION-LAST-SUPPORTED-DATE'
NO_LAST_SUPPORTED_DATE = 'NO_LAST_SUPPORTED_DATE'
NO_LATEST_VERSION = 'NO_LATEST_VERSION'
USER_APP_VERSION = 'USER_APP_VERSION'
REQUEST_CACHE_NAME = 'app-version-info'
CACHE_TIMEOUT = settings.APP_UPGRADE_CACHE_TIMEOUT
def process_request(self, request):
"""
Processes request to validate app version that is making request.
Returns:
Http response with status code 426 (i.e. Update Required) if request is from
mobile native app and app version is no longer supported else returns None
"""
version_data = self._get_version_info(request)
if version_data:
last_supported_date = version_data[self.LAST_SUPPORTED_DATE_HEADER]
if last_supported_date != self.NO_LAST_SUPPORTED_DATE:
if datetime.now().replace(tzinfo=UTC) > last_supported_date:
return HttpResponse(status=426) # Http status 426; Update Required
def process_response(self, __, response):
"""
If request is from mobile native app, then add version related info to response headers.
Returns:
Http response: with additional headers;
1. EDX-APP-LATEST-VERSION; if user app version < latest available version
2. EDX-APP-VERSION-LAST-SUPPORTED-DATE; if user app version < min supported version and
timestamp < expiry of that version
"""
request_cache_dict = get_cache(self.REQUEST_CACHE_NAME)
if request_cache_dict:
last_supported_date = request_cache_dict[self.LAST_SUPPORTED_DATE_HEADER]
if last_supported_date != self.NO_LAST_SUPPORTED_DATE:
response[self.LAST_SUPPORTED_DATE_HEADER] = last_supported_date.isoformat()
latest_version = request_cache_dict[self.LATEST_VERSION_HEADER]
user_app_version = request_cache_dict[self.USER_APP_VERSION]
if (latest_version != self.NO_LATEST_VERSION and
parsed_version(user_app_version) < parsed_version(latest_version)):
response[self.LATEST_VERSION_HEADER] = latest_version
return response
def _get_cache_key_name(self, field, key):
"""
Get key name to use to cache any property against field name and identification key.
Arguments:
field (str): The property name that needs to get cached.
key (str): Unique identification for cache key (e.g. platform_name).
Returns:
string: Cache key to be used.
"""
return "mobile_api.app_version_upgrade.{}.{}".format(field, key)
def _get_version_info(self, request):
"""
Gets and Sets version related info in mem cache and request cache; and returns a dict of it.
It sets request cache data for last_supported_date and latest_version with memcached values if exists against
user app properties else computes the values for specific platform and sets it in both memcache (for next
server interaction from same app version/platform) and request cache
Returns:
dict: Containing app version info
"""
user_agent = request.META.get('HTTP_USER_AGENT')
if user_agent:
platform = self._get_platform(request, user_agent)
if platform:
request_cache_dict = get_cache(self.REQUEST_CACHE_NAME)
request_cache_dict[self.USER_APP_VERSION] = platform.version
last_supported_date_cache_key = self._get_cache_key_name(
self.LAST_SUPPORTED_DATE_HEADER,
platform.version
)
latest_version_cache_key = self._get_cache_key_name(self.LATEST_VERSION_HEADER, platform.NAME)
cached_data = cache.get_many([last_supported_date_cache_key, latest_version_cache_key])
last_supported_date = cached_data.get(last_supported_date_cache_key)
if last_supported_date != self.NO_LAST_SUPPORTED_DATE and not isinstance(last_supported_date, datetime):
last_supported_date = self._get_last_supported_date(platform.NAME, platform.version)
cache.set(last_supported_date_cache_key, last_supported_date, self.CACHE_TIMEOUT)
request_cache_dict[self.LAST_SUPPORTED_DATE_HEADER] = last_supported_date
latest_version = cached_data.get(latest_version_cache_key)
if not (latest_version and isinstance(latest_version, six.text_type)):
latest_version = self._get_latest_version(platform.NAME)
cache.set(latest_version_cache_key, latest_version, self.CACHE_TIMEOUT)
request_cache_dict[self.LATEST_VERSION_HEADER] = latest_version
return request_cache_dict
def _get_platform(self, request, user_agent):
"""
Determines the platform type for mobile app making the request against user_agent.
Returns:
None if request app does not belong to one of the supported mobile platforms
else returns an instance of corresponding mobile platform.
"""
if is_request_from_mobile_app(request):
return MobilePlatform.get_instance(user_agent)
def _get_last_supported_date(self, platform_name, platform_version):
""" Get expiry date of app version for a platform. """
return AppVersionConfig.last_supported_date(platform_name, platform_version) or self.NO_LAST_SUPPORTED_DATE
def _get_latest_version(self, platform_name):
""" Get latest app version available for platform. """
return AppVersionConfig.latest_version(platform_name) or self.NO_LATEST_VERSION