def test_course_id_from_url(self): """ Test course_id_from_url(). """ self.assertIsNone(course_id_from_url('/login')) self.assertIsNone(course_id_from_url('/course/edX/maths/2020')) self.assertIsNone(course_id_from_url('/courses/edX/maths/')) course_id = course_id_from_url('/courses/edX/maths/2020') self.assertEqual(course_id.org, 'edX') self.assertEqual(course_id.course, 'maths') self.assertEqual(course_id.run, '2020')
def country_access_rules(self, user, ip_address, url_path): """ Check the country access rules for a given course. Applies only to courseware URLs. Args: user (User): The user making the current request. ip_address (str): The IP address from which the request originated. url_path (str): The request path. Returns: HttpResponse or None """ course_id = course_id_from_url(url_path) if course_id: redirect_url = embargo_api.redirect_if_blocked( course_id, user=user, ip_address=ip_address, url=url_path, access_point='courseware' ) if redirect_url: return redirect(redirect_url)
def process_request(self, request): """ Processes embargo requests. """ url = request.path course_id = course_id_from_url(url) course_is_embargoed = EmbargoedCourse.is_embargoed(course_id) # If they're trying to access a course that cares about embargoes if self.site_enabled or course_is_embargoed: # 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) ] ] # 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
def country_access_rules(self, user, ip_address, url_path): """ Check the country access rules for a given course. Applies only to courseware URLs. Args: user (User): The user making the current request. ip_address (str): The IP address from which the request originated. url_path (str): The request path. Returns: HttpResponse or None """ course_id = course_id_from_url(url_path) if course_id: redirect_url = embargo_api.redirect_if_blocked( course_id, user=user, ip_address=ip_address, url=url_path, access_point='courseware') if redirect_url: return redirect(redirect_url)
def process_request(self, request): """ Processes embargo requests """ url = request.path course_id = course_id_from_url(url) # If they're trying to access a course that cares about embargoes if EmbargoedCourse.is_embargoed(course_id): # If we're having performance issues, add caching here ip_addr = get_ip(request) # if blacklisted, immediately fail if ip_addr in IPFilter.current().blacklist_ips: log.info("Embargo: Restricting IP address %s to course %s because IP is blacklisted.", ip_addr, course_id) return redirect('embargo') country_code_from_ip = pygeoip.GeoIP(settings.GEOIP_PATH).country_code_by_addr(ip_addr) 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: log.info( "Embargo: Restricting IP address %s to course %s because IP is from country %s.", ip_addr, course_id, country_code_from_ip ) return redirect('embargo')
def process_request(self, request): """ Processes embargo requests """ url = request.path course_id = course_id_from_url(url) # If they're trying to access a course that cares about embargoes if EmbargoedCourse.is_embargoed(course_id): # If we're having performance issues, add caching here ip_addr = get_ip(request) # if blacklisted, immediately fail if ip_addr in IPFilter.current().blacklist_ips: log.info( "Embargo: Restricting IP address %s to course %s because IP is blacklisted.", ip_addr, course_id) return redirect('embargo') country_code_from_ip = pygeoip.GeoIP( settings.GEOIP_PATH).country_code_by_addr(ip_addr) 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: log.info( "Embargo: Restricting IP address %s to course %s because IP is from country %s.", ip_addr, course_id, country_code_from_ip) return redirect('embargo')
def process_view(self, request, view_func, view_args, view_kwargs): # pylint: disable=unused-argument """ This function handles authentication logic for wiki urls and redirects from the "root wiki" to the "course wiki" if the user accesses the wiki from a course url """ # we care only about requests to wiki urls if not view_func.__module__.startswith('wiki.'): return # wiki pages are login required if not request.user.is_authenticated(): return redirect(reverse('signin_user'), next=request.path) course_id = course_id_from_url(request.path) wiki_path = request.path.partition('/wiki/')[2] if course_id: # This is a /courses/org/name/run/wiki request course_path = "/courses/{}".format(text_type(course_id)) # HACK: django-wiki monkeypatches the reverse function to enable # urls to be rewritten reverse._transform_url = lambda url: course_path + url # pylint: disable=protected-access # Authorization Check # Let's see if user is enrolled or the course allows for public access try: course = get_course_with_access(request.user, 'load', course_id) except Http404: # course does not exist. redirect to root wiki. # clearing the referrer will cause process_response not to redirect # back to a non-existent course request.META['HTTP_REFERER'] = '' return redirect('/wiki/{}'.format(wiki_path)) if not course.allow_public_wiki_access: is_enrolled = CourseEnrollment.is_enrolled( request.user, course.id) is_staff = has_access(request.user, 'staff', course) if not (is_enrolled or is_staff): # if a user is logged in, but not authorized to see a page, # we'll redirect them to the course about page return redirect('about_course', text_type(course_id)) # If we need enterprise data sharing consent for this course, then redirect to the form. consent_url = get_enterprise_consent_url( request, text_type(course_id)) if consent_url: return redirect(consent_url) # set the course onto here so that the wiki template can show the course navigation request.course = course else: # this is a request for /wiki/... # Check to see if we don't allow top-level access to the wiki via the /wiki/xxxx/yyy/zzz URLs # this will help prevent people from writing pell-mell to the Wiki in an unstructured way if not settings.FEATURES.get('ALLOW_WIKI_ROOT_ACCESS', False): raise PermissionDenied() return self._redirect_from_referrer(request, wiki_path)
def process_view(self, request, view_func, view_args, view_kwargs): # pylint: disable=unused-argument """ This function handles authentication logic for wiki urls and redirects from the "root wiki" to the "course wiki" if the user accesses the wiki from a course url """ # we care only about requests to wiki urls if not view_func.__module__.startswith('wiki.'): return # wiki pages are login required if not request.user.is_authenticated(): return redirect(reverse('signin_user'), next=request.path) course_id = course_id_from_url(request.path) wiki_path = request.path.partition('/wiki/')[2] if course_id: # This is a /courses/org/name/run/wiki request course_path = "/courses/{}".format(course_id.to_deprecated_string()) # HACK: django-wiki monkeypatches the reverse function to enable # urls to be rewritten reverse._transform_url = lambda url: course_path + url # pylint: disable=protected-access # Authorization Check # Let's see if user is enrolled or the course allows for public access try: course = get_course_with_access(request.user, 'load', course_id) except Http404: # course does not exist. redirect to root wiki. # clearing the referrer will cause process_response not to redirect # back to a non-existent course request.META['HTTP_REFERER'] = '' return redirect('/wiki/{}'.format(wiki_path)) if not course.allow_public_wiki_access: is_enrolled = CourseEnrollment.is_enrolled(request.user, course.id) is_staff = has_access(request.user, 'staff', course) if not (is_enrolled or is_staff): # if a user is logged in, but not authorized to see a page, # we'll redirect them to the course about page return redirect('about_course', course_id.to_deprecated_string()) # If we need enterprise data sharing consent for this course, then redirect to the form. consent_url = get_enterprise_consent_url(request, unicode(course_id)) if consent_url: return redirect(consent_url) # set the course onto here so that the wiki template can show the course navigation request.course = course else: # this is a request for /wiki/... # Check to see if we don't allow top-level access to the wiki via the /wiki/xxxx/yyy/zzz URLs # this will help prevent people from writing pell-mell to the Wiki in an unstructured way if not settings.FEATURES.get('ALLOW_WIKI_ROOT_ACCESS', False): raise PermissionDenied() return self._redirect_from_referrer(request, wiki_path)
def test_course_id_from_url(self): """ Test course_id_from_url(). """ self.assertIsNone(course_id_from_url('/login')) self.assertIsNone(course_id_from_url('/course/edX/maths/2020')) self.assertIsNone(course_id_from_url('/courses/edX/maths/')) self.assertIsNone( course_id_from_url('/api/courses/v1/blocks/edX/maths/2020')) self.assertIsNone( course_id_from_url( '/api/courses/v1/blocks/course-v1:incidental+courseid+formatting' )) self.assertIsNone( course_id_from_url( '/api/courses/v41/notcourses/course-v1:incidental+courseid+formatting' )) course_id = course_id_from_url('/courses/course-v1:edX+maths+2020') self.assertCourseIdFieldsMatch(course_id=course_id, org="edX", course='maths', run='2020') course_id = course_id_from_url('/courses/edX/maths/2020') self.assertCourseIdFieldsMatch(course_id=course_id, org='edX', course='maths', run='2020') course_id = course_id_from_url( '/api/courses/v1/courses/course-v1:edX+maths+2020') self.assertCourseIdFieldsMatch(course_id=course_id, org='edX', course='maths', run='2020') course_id = course_id_from_url( '/api/courses/v1/courses/edX/maths/2020') self.assertCourseIdFieldsMatch(course_id=course_id, org='edX', course='maths', run='2020')
def test_course_id_from_url(self): """ Test course_id_from_url(). """ self.assertIsNone(course_id_from_url('/login')) self.assertIsNone(course_id_from_url('/course/edX/maths/2020')) self.assertIsNone(course_id_from_url('/courses/edX/maths/')) self.assertIsNone(course_id_from_url('/api/courses/v1/blocks/edX/maths/2020')) self.assertIsNone(course_id_from_url('/api/courses/v1/blocks/course-v1:incidental+courseid+formatting')) self.assertIsNone(course_id_from_url('/api/courses/v41/notcourses/course-v1:incidental+courseid+formatting')) course_id = course_id_from_url('/courses/course-v1:edX+maths+2020') self.assertCourseIdFieldsMatch(course_id=course_id, org="edX", course='maths', run='2020') course_id = course_id_from_url('/courses/edX/maths/2020') self.assertCourseIdFieldsMatch(course_id=course_id, org='edX', course='maths', run='2020') course_id = course_id_from_url('/api/courses/v1/courses/course-v1:edX+maths+2020') self.assertCourseIdFieldsMatch(course_id=course_id, org='edX', course='maths', run='2020') course_id = course_id_from_url('/api/courses/v1/courses/edX/maths/2020') self.assertCourseIdFieldsMatch(course_id=course_id, org='edX', course='maths', run='2020')
def process_request(self, request): """ Processes embargo requests """ url = request.path course_id = course_id_from_url(url) course_is_embargoed = EmbargoedCourse.is_embargoed(course_id) # 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') # If we're having performance issues, add caching here ip_addr = get_ip(request) # 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 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) 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
def process_request(self, request): """ Processes embargo requests """ url = request.path course_id = course_id_from_url(url) course_is_embargoed = EmbargoedCourse.is_embargoed(course_id) # 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") ) # If we're having performance issues, add caching here ip_addr = get_ip(request) # 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 response country_code_from_ip = pygeoip.GeoIP(settings.GEOIP_PATH).country_code_by_addr(ip_addr) 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
def _redirect_from_referrer(self, request, wiki_path): """ redirect to course wiki url if the referrer is from a course page """ course_id = course_id_from_url(request.META.get('HTTP_REFERER')) if course_id: # See if we are able to view the course. If we are, redirect to it try: get_course_overview_with_access(request.user, 'load', course_id) return redirect("/courses/{course_id}/wiki/{path}".format(course_id=course_id.to_deprecated_string(), path=wiki_path)) except Http404: # Even though we came from the course, we can't see it. So don't worry about it. pass
def process_request(self, request): """ Only if the following are all true: 1. request is a GET 2. request is NOT to a URL in DISALLOW_SNEAKPEEK_URL_NAMES 3. request.user is AnonymousUser (This middleware must be added after the AuthenticationMiddleware) 4. request has a course context 5. request's course allows sneakpeek 6. request's course's enrollment period is open Does the following: 1. Registers an anonymous user 2. Login this user in 3. Enrolls this user in the course """ ### Start by testing the conditions, each of which can fail fast and return, ### causing the middleware to do nothing if request.method != "GET": return None try: match = resolve(request.path) if match.url_name in DISALLOW_SNEAKPEEK_URL_NAMES: return None except Http404: pass if request.user.is_authenticated(): return None course_id = course_id_from_url(request.path) if not course_id: return None if not CoursePreference.course_allows_nonregistered_access(course_id): return None if not modulestore().has_course(course_id): message = u"Sneakpeek attempt for non-existent course %s" LOG.warning(message, course_id) return None can_enroll, _ = _check_can_enroll_in_course( request.user, course_id, access_type='within_enrollment_period') if not can_enroll: return None ### OK. We should now do the 3 steps to get the users access to follow the deeplink _create_and_login_nonregistered_user(request) CourseEnrollment.enroll(request.user, course_id) return None
def process_request(self, request): """ Only if the following are all true: 1. request is a GET 2. request is NOT to a URL in DISALLOW_SNEAKPEEK_URL_NAMES 3. request.user is AnonymousUser (This middleware must be added after the AuthenticationMiddleware) 4. request has a course context 5. request's course allows sneakpeek 6. request's course's enrollment period is open Does the following: 1. Registers an anonymous user 2. Login this user in 3. Enrolls this user in the course """ ### Start by testing the conditions, each of which can fail fast and return, ### causing the middleware to do nothing if request.method != "GET": return None try: match = resolve(request.path) if match.url_name in DISALLOW_SNEAKPEEK_URL_NAMES: return None except Http404: pass if request.user.is_authenticated(): return None course_id = course_id_from_url(request.path) if not course_id: return None if not CoursePreference.course_allows_nonregistered_access(course_id): return None can_enroll, _ = _check_can_enroll_in_course(request.user, course_id, access_type="within_enrollment_period") if not can_enroll: return None ### OK. We should now do the 3 steps to get the users access to follow the deeplink _create_and_login_nonregistered_user(request) CourseEnrollment.enroll(request.user, course_id) return None
def process_request(self, request): """Block requests based on embargo rules. In the new ENABLE_COUNTRY_ACCESS implmentation, this will perform the following checks: 1) If the user's IP address is blacklisted, block. 2) If the user's IP address is whitelisted, allow. 3) If the user's country (inferred from their IP address) is blocked for a courseware page, block. 4) If the user's country (retrieved from the user's profile) is blocked for a courseware page, block. 5) Allow access. """ # If the feature flag is set, use the new "country access" implementation. # This is a more flexible implementation of the embargo feature that allows # per-course country access rules. if self.enable_country_access: # Never block certain patterns by IP address for pattern in self.ALLOW_URL_PATTERNS: if pattern.match(request.path) is not None: return None ip_address = get_ip(request) ip_filter = IPFilter.current() if ip_filter.enabled and ip_address in ip_filter.blacklist_ips: log.info( ( u"User %s was blocked from accessing %s " u"because IP address %s is blacklisted." ), request.user.id, request.path, ip_address ) # If the IP is blacklisted, reject. # This applies to any request, not just courseware URLs. ip_blacklist_url = reverse( 'embargo_blocked_message', kwargs={ 'access_point': 'courseware', 'message_key': 'embargo' } ) return redirect(ip_blacklist_url) elif ip_filter.enabled and ip_address in ip_filter.whitelist_ips: log.info( ( u"User %s was allowed access to %s because " u"IP address %s is whitelisted." ), request.user.id, request.path, ip_address ) # If the IP is whitelisted, then allow access, # skipping later checks. return None else: # Otherwise, perform the country access checks. # This applies only to courseware URLs. return self.country_access_rules(request.user, ip_address, request.path) url = request.path course_id = course_id_from_url(url) course_is_embargoed = EmbargoedCourse.is_embargoed(course_id) # If they're trying to access a course that cares about embargoes if self.site_enabled or course_is_embargoed: # 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) ] ] # 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
def process_request(self, request): """Block requests based on embargo rules. In the new ENABLE_COUNTRY_ACCESS implmentation, this will perform the following checks: 1) If the user's IP address is blacklisted, block. 2) If the user's IP address is whitelisted, allow. 3) If the user's country (inferred from their IP address) is blocked for a courseware page, block. 4) If the user's country (retrieved from the user's profile) is blocked for a courseware page, block. 5) Allow access. """ # If the feature flag is set, use the new "country access" implementation. # This is a more flexible implementation of the embargo feature that allows # per-course country access rules. if self.enable_country_access: # Never block certain patterns by IP address for pattern in self.ALLOW_URL_PATTERNS: if pattern.match(request.path) is not None: return None ip_address = get_ip(request) ip_filter = IPFilter.current() if ip_filter.enabled and ip_address in ip_filter.blacklist_ips: log.info((u"User %s was blocked from accessing %s " u"because IP address %s is blacklisted."), request.user.id, request.path, ip_address) # If the IP is blacklisted, reject. # This applies to any request, not just courseware URLs. ip_blacklist_url = reverse('embargo_blocked_message', kwargs={ 'access_point': 'courseware', 'message_key': 'embargo' }) return redirect(ip_blacklist_url) elif ip_filter.enabled and ip_address in ip_filter.whitelist_ips: log.info((u"User %s was allowed access to %s because " u"IP address %s is whitelisted."), request.user.id, request.path, ip_address) # If the IP is whitelisted, then allow access, # skipping later checks. return None else: # Otherwise, perform the country access checks. # This applies only to courseware URLs. return self.country_access_rules(request.user, ip_address, request.path) url = request.path course_id = course_id_from_url(url) course_is_embargoed = EmbargoedCourse.is_embargoed(course_id) # If they're trying to access a course that cares about embargoes if self.site_enabled or course_is_embargoed: # 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) ] ] # 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