Revert "Merge pull request #4976 from edx/will/ecom-128"
This reverts commita03baee25c, reversing changes made to12e2d442e9.
This commit is contained in:
@@ -12,7 +12,7 @@ This middleware allows you to:
|
||||
* Embargoing site (access restriction of the main site)
|
||||
|
||||
Embargo can restrict by states and whitelist/blacklist (IP Addresses
|
||||
(ie. 10.0.0.0), Networks (ie. 10.0.0.0/24)), or the user profile country.
|
||||
(ie. 10.0.0.0) or Networks (ie. 10.0.0.0/24)).
|
||||
|
||||
Usage:
|
||||
|
||||
@@ -30,20 +30,16 @@ EMBARGO_SITE_REDIRECT_URL = 'https://www.edx.org/'
|
||||
# if EMBARGO_SITE_REDIRECT_URL is missing, a HttpResponseForbidden is returned.
|
||||
|
||||
"""
|
||||
from functools import partial
|
||||
import logging
|
||||
import pygeoip
|
||||
from lazy import lazy
|
||||
|
||||
from django.core.exceptions import MiddlewareNotUsed
|
||||
from django.core.cache import cache
|
||||
from django.conf import settings
|
||||
from django.shortcuts import redirect
|
||||
from django.http import HttpResponseRedirect, HttpResponseForbidden
|
||||
from ipware.ip import get_ip
|
||||
from util.request import course_id_from_url
|
||||
|
||||
from student.models import unique_id_for_user
|
||||
from embargo.models import EmbargoedCourse, EmbargoedState, IPFilter
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -56,27 +52,16 @@ class EmbargoMiddleware(object):
|
||||
This is configured by creating ``EmbargoedCourse``, ``EmbargoedState``, and
|
||||
optionally ``IPFilter`` rows in the database, using the django admin site.
|
||||
"""
|
||||
|
||||
# Reasons a user might be blocked.
|
||||
# These are used to generate info messages in the logs.
|
||||
REASONS = {
|
||||
"ip_blacklist": u"Restricting IP address {ip_addr} {from_course} because IP is blacklisted.",
|
||||
"ip_country": u"Restricting IP address {ip_addr} {from_course} because IP is from country {ip_country}.",
|
||||
"profile_country": (
|
||||
u"Restricting user {user_id} {from_course} because "
|
||||
u"the user set the profile country to {profile_country}."
|
||||
)
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.site_enabled = settings.FEATURES.get('SITE_EMBARGOED', False)
|
||||
# If embargoing is turned off, make this middleware do nothing
|
||||
if not settings.FEATURES.get('EMBARGO', False) and not self.site_enabled:
|
||||
if not settings.FEATURES.get('EMBARGO', False) and \
|
||||
not self.site_enabled:
|
||||
raise MiddlewareNotUsed()
|
||||
|
||||
def process_request(self, request):
|
||||
"""
|
||||
Processes embargo requests.
|
||||
Processes embargo requests
|
||||
"""
|
||||
url = request.path
|
||||
course_id = course_id_from_url(url)
|
||||
@@ -84,216 +69,41 @@ class EmbargoMiddleware(object):
|
||||
|
||||
# If they're trying to access a course that cares about embargoes
|
||||
if self.site_enabled or course_is_embargoed:
|
||||
response = redirect('embargo')
|
||||
# Set the proper response if site is enabled
|
||||
if self.site_enabled:
|
||||
redirect_url = getattr(settings, 'EMBARGO_SITE_REDIRECT_URL', None)
|
||||
response = HttpResponseRedirect(redirect_url) if redirect_url \
|
||||
else HttpResponseForbidden('Access Denied')
|
||||
|
||||
# Construct the list of functions that check whether the user is embargoed.
|
||||
# We wrap each of these functions in a decorator that logs the reason the user
|
||||
# was blocked.
|
||||
# Each function should return `True` iff the user is blocked by an embargo.
|
||||
check_functions = [
|
||||
self._log_embargo_reason(check_func, course_id, course_is_embargoed)
|
||||
for check_func in [
|
||||
partial(self._is_embargoed_by_ip, get_ip(request)),
|
||||
partial(self._is_embargoed_by_profile_country, request.user)
|
||||
]
|
||||
]
|
||||
# If we're having performance issues, add caching here
|
||||
ip_addr = get_ip(request)
|
||||
|
||||
# Perform each of the checks
|
||||
# If the user fails any of the checks, immediately redirect them
|
||||
# and skip later checks.
|
||||
for check_func in check_functions:
|
||||
if check_func():
|
||||
return self._embargo_redirect_response
|
||||
|
||||
# If all the check functions pass, implicitly return None
|
||||
# so that the middleware processor can continue processing
|
||||
# the response.
|
||||
|
||||
def _is_embargoed_by_ip(self, ip_addr, course_id=u"", course_is_embargoed=False):
|
||||
"""
|
||||
Check whether the user is embargoed based on the IP address.
|
||||
|
||||
Args:
|
||||
ip_addr (str): The IP address the request originated from.
|
||||
|
||||
Keyword Args:
|
||||
course_id (unicode): The course the user is trying to access.
|
||||
course_is_embargoed (boolean): Whether the course the user is accessing has been embargoed.
|
||||
|
||||
Returns:
|
||||
A unicode message if the user is embargoed, otherwise `None`
|
||||
|
||||
"""
|
||||
# If blacklisted, immediately fail
|
||||
if ip_addr in IPFilter.current().blacklist_ips:
|
||||
return self.REASONS['ip_blacklist'].format(
|
||||
ip_addr=ip_addr,
|
||||
from_course=self._from_course_msg(course_id, course_is_embargoed)
|
||||
)
|
||||
|
||||
# If we're white-listed, then allow access
|
||||
if ip_addr in IPFilter.current().whitelist_ips:
|
||||
return None
|
||||
|
||||
# Retrieve the country code from the IP address
|
||||
# and check it against the list of embargoed countries
|
||||
ip_country = self._country_code_from_ip(ip_addr)
|
||||
if ip_country in self._embargoed_countries:
|
||||
return self.REASONS['ip_country'].format(
|
||||
ip_addr=ip_addr,
|
||||
ip_country=ip_country,
|
||||
from_course=self._from_course_msg(course_id, course_is_embargoed)
|
||||
)
|
||||
|
||||
# If none of the other checks caught anything,
|
||||
# implicitly return None to indicate that the user can access the course
|
||||
|
||||
def _is_embargoed_by_profile_country(self, user, course_id="", course_is_embargoed=False):
|
||||
"""
|
||||
Check whether the user is embargoed based on the country code in the user's profile.
|
||||
|
||||
Args:
|
||||
user (User): The user attempting to access courseware.
|
||||
|
||||
Keyword Args:
|
||||
course_id (unicode): The course the user is trying to access.
|
||||
course_is_embargoed (boolean): Whether the course the user is accessing has been embargoed.
|
||||
|
||||
Returns:
|
||||
A unicode message if the user is embargoed, otherwise `None`
|
||||
|
||||
"""
|
||||
cache_key = u'user.{user_id}.profile.country'.format(user_id=user.id)
|
||||
profile_country = cache.get(cache_key)
|
||||
if profile_country is None:
|
||||
profile = getattr(user, 'profile', None)
|
||||
if profile is not None:
|
||||
profile_country = profile.country.code.upper()
|
||||
else:
|
||||
profile_country = ""
|
||||
cache.set(cache_key, profile_country)
|
||||
|
||||
if profile_country in self._embargoed_countries:
|
||||
return self.REASONS['profile_country'].format(
|
||||
user_id=unique_id_for_user(user),
|
||||
profile_country=profile_country,
|
||||
from_course=self._from_course_msg(course_id, course_is_embargoed)
|
||||
)
|
||||
else:
|
||||
return None
|
||||
|
||||
def _country_code_from_ip(self, ip_addr):
|
||||
"""
|
||||
Return the country code associated with an IP address.
|
||||
Handles both IPv4 and IPv6 addresses.
|
||||
|
||||
Args:
|
||||
ip_addr (str): The IP address to look up.
|
||||
|
||||
Returns:
|
||||
str: A 2-letter country code.
|
||||
|
||||
"""
|
||||
if ip_addr.find(':') >= 0:
|
||||
return pygeoip.GeoIP(settings.GEOIPV6_PATH).country_code_by_addr(ip_addr)
|
||||
else:
|
||||
return pygeoip.GeoIP(settings.GEOIP_PATH).country_code_by_addr(ip_addr)
|
||||
|
||||
@property
|
||||
def _embargo_redirect_response(self):
|
||||
"""
|
||||
The HTTP response to send when the user is blocked from a course.
|
||||
This will either be a redirect to a URL configured in Django settings
|
||||
or a forbidden response.
|
||||
|
||||
Returns:
|
||||
HTTPResponse
|
||||
|
||||
"""
|
||||
response = redirect('embargo')
|
||||
|
||||
# Set the proper response if site is enabled
|
||||
if self.site_enabled:
|
||||
redirect_url = getattr(settings, 'EMBARGO_SITE_REDIRECT_URL', None)
|
||||
response = (
|
||||
HttpResponseRedirect(redirect_url)
|
||||
if redirect_url
|
||||
else HttpResponseForbidden('Access Denied')
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
@lazy
|
||||
def _embargoed_countries(self):
|
||||
"""
|
||||
Return the list of 2-letter country codes for embargoed countries.
|
||||
The result is cached within the scope of the response.
|
||||
|
||||
Returns:
|
||||
list
|
||||
|
||||
"""
|
||||
return EmbargoedState.current().embargoed_countries_list
|
||||
|
||||
def _from_course_msg(self, course_id, course_is_embargoed):
|
||||
"""
|
||||
Format a message indicating whether the user was blocked from a specific course.
|
||||
This can be used in info messages, but should not be used in user-facing messages.
|
||||
|
||||
Args:
|
||||
course_id (unicode): The ID of the course being accessed.
|
||||
course_is_embarged (boolean): Whether the course being accessed is embargoed.
|
||||
|
||||
Returns:
|
||||
unicode
|
||||
|
||||
"""
|
||||
return (
|
||||
u"from course {course_id}".format(course_id=course_id)
|
||||
if course_is_embargoed
|
||||
else u""
|
||||
)
|
||||
|
||||
def _log_embargo_reason(self, check_func, course_id, course_is_embargoed):
|
||||
"""
|
||||
Decorator for embargo check functions that will:
|
||||
* execute the check function
|
||||
* check whether the user is blocked by an embargo, and if so, log the reason
|
||||
* return a boolean indicating whether the user was blocked.
|
||||
|
||||
Args:
|
||||
check_func (partial): A function that should return unicode reason if the user
|
||||
was blocked, otherwise should return None. This function will be passed
|
||||
`course_id` and `course_is_embarged` kwargs so it can format a detailed
|
||||
reason message.
|
||||
|
||||
course_id (unicode): The ID of the course the user is trying to access.
|
||||
|
||||
course_is_embargoed (boolean): Whether the course the user is trying
|
||||
to access is under an embargo.
|
||||
|
||||
Returns:
|
||||
boolean: True iff the user was blocked by an embargo
|
||||
|
||||
"""
|
||||
def _inner():
|
||||
# Perform the check and retrieve the reason string.
|
||||
# The reason will be `None` if the user passes the check and can access the course.
|
||||
# We pass in the course ID and whether the course is embargoed
|
||||
# so that the check function can fill in the "reason" message with more specific details.
|
||||
reason = check_func(
|
||||
course_id=course_id,
|
||||
course_is_embargoed=course_is_embargoed
|
||||
)
|
||||
|
||||
# If the reason was `None`, indicate that the user was not blocked.
|
||||
if reason is None:
|
||||
return False
|
||||
|
||||
# Otherwise, log the reason the user was blocked
|
||||
# and return True.
|
||||
else:
|
||||
msg = u"Embargo: {reason}".format(reason=reason)
|
||||
# if blacklisted, immediately fail
|
||||
if ip_addr in IPFilter.current().blacklist_ips:
|
||||
if course_is_embargoed:
|
||||
msg = "Embargo: Restricting IP address %s to course %s because IP is blacklisted." % \
|
||||
(ip_addr, course_id)
|
||||
else:
|
||||
msg = "Embargo: Restricting IP address %s because IP is blacklisted." % ip_addr
|
||||
log.info(msg)
|
||||
return True
|
||||
return response
|
||||
# ipv6 support
|
||||
if ip_addr.find(':') >= 0:
|
||||
country_code_from_ip = pygeoip.GeoIP(settings.GEOIPV6_PATH).country_code_by_addr(ip_addr)
|
||||
else:
|
||||
country_code_from_ip = pygeoip.GeoIP(settings.GEOIP_PATH).country_code_by_addr(ip_addr)
|
||||
|
||||
return _inner
|
||||
is_embargoed = country_code_from_ip in EmbargoedState.current().embargoed_countries_list
|
||||
# Fail if country is embargoed and the ip address isn't explicitly
|
||||
# whitelisted
|
||||
if is_embargoed and ip_addr not in IPFilter.current().whitelist_ips:
|
||||
if course_is_embargoed:
|
||||
msg = "Embargo: Restricting IP address %s to course %s because IP is from country %s." % \
|
||||
(ip_addr, course_id, country_code_from_ip)
|
||||
else:
|
||||
msg = "Embargo: Restricting IP address %s because IP is from country %s." % \
|
||||
(ip_addr, country_code_from_ip)
|
||||
|
||||
log.info(msg)
|
||||
return response
|
||||
|
||||
Reference in New Issue
Block a user