Skip to content

Commit

Permalink
feat!: Remove legacy-ip code and Waffle switch (#33735)
Browse files Browse the repository at this point in the history
This switch has been kept disabled in edx.org for well over a year with no
trouble, and the migration to `CLOSEST_CLIENT_IP_FROM_HEADERS`
was introduced in Nutmeg.

DEPR issue: #33733
  • Loading branch information
timmc-edx committed Dec 1, 2023
1 parent 29a0edd commit 9444186
Show file tree
Hide file tree
Showing 9 changed files with 13 additions and 238 deletions.
6 changes: 1 addition & 5 deletions openedx/core/djangoapps/embargo/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
from common.djangoapps.student.auth import has_course_author_access
from openedx.core import types
from openedx.core.djangoapps.geoinfo.api import country_code_from_ip
from openedx.core.djangoapps.util import legacy_ip

from .models import CountryAccessRule, RestrictedCourse

Expand Down Expand Up @@ -49,10 +48,7 @@ def redirect_if_blocked(
If blocked, a URL path to a page explaining why the user was blocked. Else None.
"""
if settings.FEATURES.get('EMBARGO'):
if legacy_ip.USE_LEGACY_IP.is_enabled():
client_ips = [legacy_ip.get_legacy_ip(request)]
else:
client_ips = ip.get_all_client_ips(request)
client_ips = ip.get_all_client_ips(request)
user = user or request.user
is_blocked = not check_course_access(course_key, user=user, ip_addresses=client_ips, url=request.path)
if is_blocked:
Expand Down
9 changes: 2 additions & 7 deletions openedx/core/djangoapps/embargo/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
from rest_framework.request import Request
from rest_framework.response import Response

from openedx.core.djangoapps.util import legacy_ip
from openedx.core.lib.request_utils import course_id_from_url

from . import api as embargo_api
Expand Down Expand Up @@ -87,12 +86,8 @@ def process_request(self, request: Request) -> Optional[Response]:
if pattern.match(request.path) is not None:
return None

if legacy_ip.USE_LEGACY_IP.is_enabled():
safest_ip_address = legacy_ip.get_legacy_ip(request)
all_ip_addresses = [safest_ip_address]
else:
safest_ip_address = ip.get_safest_client_ip(request)
all_ip_addresses = ip.get_all_client_ips(request)
safest_ip_address = ip.get_safest_client_ip(request)
all_ip_addresses = ip.get_all_client_ips(request)

ip_filter = IPFilter.current()

Expand Down
15 changes: 5 additions & 10 deletions openedx/core/djangoapps/embargo/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
from django.db import connection
from django.test.client import RequestFactory
from django.test.utils import override_settings
from edx_toggles.toggles.testutils import override_waffle_switch
from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, mixed_store_config

Expand All @@ -28,7 +27,6 @@
OrgStaffRole, OrgInstructorRole
)
from common.djangoapps.util.testing import UrlResetMixin
from openedx.core.djangoapps.util.legacy_ip import USE_LEGACY_IP

from ..models import (
RestrictedCourse, Country, CountryAccessRule,
Expand Down Expand Up @@ -234,15 +232,13 @@ def test_staff_access_country_block(self, staff_role_cls):

@ddt.data(
# (Note that any '0.x.x.x' IP _should_ be blocked in this test.)
# ips, legacy, allow access
(['0.0.0.0', '1.1.1.1'], True, False), # legacy looks at first IP and blocks
(['1.1.1.1', '0.0.0.0'], True, True), # legacy fails to look at later IPs and allows
(['1.1.1.1', '2.2.2.2'], False, True), # normal chain of access
(['1.1.1.1', '0.0.0.0', '2.2.2.2'], False, False), # tried to sneak a blocked IP in, but we caught it
# ips, allow access
(['1.1.1.1', '2.2.2.2'], True), # normal chain of access
(['1.1.1.1', '0.0.0.0', '2.2.2.2'], False), # tried to sneak a blocked IP in, but we caught it
)
@ddt.unpack
@mock.patch('openedx.core.djangoapps.embargo.api.country_code_from_ip')
def test_redirect_if_blocked_ips(self, ips, use_legacy, allow_access, mock_country):
def test_redirect_if_blocked_ips(self, ips, allow_access, mock_country):
# Block the US
CountryAccessRule.objects.create(
rule_type=CountryAccessRule.BLACKLIST_RULE,
Expand All @@ -256,8 +252,7 @@ def test_redirect_if_blocked_ips(self, ips, use_legacy, allow_access, mock_count
request = RequestFactory().get('', HTTP_X_FORWARDED_FOR=','.join(ips))
request.user = self.user

with override_waffle_switch(USE_LEGACY_IP, use_legacy):
assert (embargo_api.redirect_if_blocked(request, self.course.id) is None) == allow_access
assert (embargo_api.redirect_if_blocked(request, self.course.id) is None) == allow_access

@ddt.data(
# access point, check disabled, allow access
Expand Down
82 changes: 0 additions & 82 deletions openedx/core/djangoapps/embargo/tests/test_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,11 @@
from django.conf import settings
from django.core.cache import cache as django_cache
from django.urls import reverse
from edx_toggles.toggles.testutils import override_waffle_switch
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory

from common.djangoapps.student.tests.factories import UserFactory
from common.djangoapps.util.testing import UrlResetMixin
from openedx.core.djangoapps.util.legacy_ip import USE_LEGACY_IP
from openedx.core.djangolib.testing.utils import skip_unless_lms

from ..models import IPFilter, RestrictedCourse
Expand Down Expand Up @@ -161,86 +159,6 @@ def test_ip_whitelist_rules(self, request_ips, blacklist, whitelist, is_enabled,
)
self.assertRedirects(response, redirect_url)

@patch.dict(settings.FEATURES, {'EMBARGO': True})
@override_waffle_switch(USE_LEGACY_IP, True)
@ddt.data(
# request ip chain, blacklist, whitelist, allow_access
(['192.178.2.3'], [], [], False), # confirm that test setup & no config blocks users by default
(['173.194.123.35', '192.178.2.3'], [], ['192.178.2.3'], False), # whitelist ignores last (safest) ip
(['173.194.123.35', '192.178.2.3'], [], ['173.194.0.0/16'], True), # whitelist does look at first ip though
)
@ddt.unpack
def test_ip_legacy_whitelist_rules(self, request_ips, blacklist, whitelist, allow_access):
# Ensure that IP blocking works for anonymous users
self.client.logout()

# Set up the IP rules
IPFilter.objects.create(
blacklist=", ".join(blacklist),
whitelist=", ".join(whitelist),
enabled=True,
)

# Check that access is enforced (restrict course by default, so that allow-list logic is actually tested)
with restrict_course(self.course.id):
response = self.client.get(
self.courseware_url,
HTTP_X_FORWARDED_FOR=','.join(request_ips),
REMOTE_ADDR=request_ips[-1],
)

if allow_access:
assert response.status_code == 200
else:
redirect_url = reverse(
'embargo:blocked_message',
kwargs={
'access_point': 'courseware',
'message_key': 'default',
}
)
self.assertRedirects(response, redirect_url)

@patch.dict(settings.FEATURES, {'EMBARGO': True})
@override_waffle_switch(USE_LEGACY_IP, True)
@ddt.data(
# request ip chain, blacklist, whitelist, allow_access
(['192.178.2.3'], [], [], True), # confirm that test setup & no config allows users by default
(['173.194.123.35', '192.178.2.3'], ['192.178.2.3'], [], True), # blacklist ignores last (safest) ip
(['173.194.123.35', '192.178.2.3'], ['173.194.123.35'], [], False), # blacklist looks at first though
(['192.178.2.3'], ['192.178.2.3'], ['192.178.2.3'], False), # blacklist overrides whitelist
)
@ddt.unpack
def test_ip_legacy_blacklist_rules(self, request_ips, blacklist, whitelist, allow_access):
# Ensure that IP blocking works for anonymous users
self.client.logout()

# Set up the IP rules
IPFilter.objects.create(
blacklist=", ".join(blacklist),
whitelist=", ".join(whitelist),
enabled=True,
)

# Check that access is enforced
response = self.client.get(
self.courseware_url,
HTTP_X_FORWARDED_FOR=','.join(request_ips),
REMOTE_ADDR=request_ips[-1],
)

if allow_access:
assert response.status_code == 200
else:
redirect_url = reverse(
'embargo:blocked_message',
kwargs={
'access_point': 'courseware',
'message_key': 'embargo',
}
)
self.assertRedirects(response, redirect_url)

@patch.dict(settings.FEATURES, {'EMBARGO': True})
@ddt.data(
('courseware', 'default'),
Expand Down
48 changes: 0 additions & 48 deletions openedx/core/djangoapps/util/legacy_ip.py

This file was deleted.

7 changes: 1 addition & 6 deletions openedx/core/djangoapps/util/ratelimit.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@

from edx_django_utils import ip

from openedx.core.djangoapps.util import legacy_ip


def real_ip(group, request): # pylint: disable=unused-argument
"""
Expand All @@ -17,10 +15,7 @@ def real_ip(group, request): # pylint: disable=unused-argument
(Intended to be called by ``django-ratelimit``, hence the unused argument.)
"""
if legacy_ip.USE_LEGACY_IP.is_enabled():
return legacy_ip.get_legacy_ip(request)
else:
return ip.get_safest_client_ip(request)
return ip.get_safest_client_ip(request)


def request_post_email(group, request) -> str: # pylint: disable=unused-argument
Expand Down
50 changes: 0 additions & 50 deletions openedx/core/djangoapps/util/tests/test_legacy_ip.py

This file was deleted.

14 changes: 0 additions & 14 deletions openedx/core/djangoapps/util/tests/test_ratelimit.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@
import ddt
from django.test import TestCase
from django.test.client import RequestFactory
from edx_toggles.toggles.testutils import override_waffle_switch

import openedx.core.djangoapps.util.ratelimit as ratelimit
from openedx.core.djangoapps.util.legacy_ip import USE_LEGACY_IP
from openedx.core.lib.x_forwarded_for.middleware import XForwardedForMiddleware


Expand Down Expand Up @@ -36,18 +34,6 @@ def test_real_ip_after_xff_middleware(self):
XForwardedForMiddleware(get_response=lambda request: None).process_request(self.request)
assert ratelimit.real_ip(None, self.request) == '1.2.3.4'

@override_waffle_switch(USE_LEGACY_IP, True)
def test_legacy_switch(self):
assert ratelimit.real_ip(None, self.request) == '7.8.9.0'

@override_waffle_switch(USE_LEGACY_IP, True)
def test_legacy_switch_after_xff_middleware(self):
"""
Again, but with XFF Middleware running first.
"""
XForwardedForMiddleware(get_response=lambda request: None).process_request(self.request)
assert ratelimit.real_ip(None, self.request) == '7.8.9.0'

def test_request_post_email(self):
"""
Tests post email param.
Expand Down
20 changes: 4 additions & 16 deletions openedx/core/lib/x_forwarded_for/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
from edx_django_utils import ip
from edx_django_utils.monitoring import set_custom_attribute

from openedx.core.djangoapps.util import legacy_ip


def _ip_type(ip_str):
"""
Expand Down Expand Up @@ -45,9 +43,6 @@ def process_request(self, request):
# This function will cache its results in the request.
ip.init_client_ips(request)

# Only used to support ip.legacy switch.
request.META['ORIGINAL_REMOTE_ADDR'] = request.META['REMOTE_ADDR']

safest_client_ip = ip.get_safest_client_ip(request)

try:
Expand All @@ -63,8 +58,6 @@ def process_request(self, request):
set_custom_attribute('ip_chain.count', len(ip_chain))
set_custom_attribute('ip_chain.types', '-'.join(_ip_type(s) for s in ip_chain))

set_custom_attribute('ip_chain.use_legacy', legacy_ip.USE_LEGACY_IP.is_enabled())

external_chain = ip.get_all_client_ips(request)
set_custom_attribute('ip_chain.external.count', len(external_chain))
set_custom_attribute('ip_chain.external.types', '-'.join(_ip_type(s) for s in external_chain))
Expand Down Expand Up @@ -102,13 +95,8 @@ def process_request(self, request):
# makes it possible to handle multi-valued headers correctly.
# After that, this override can probably be safely removed.
#
# It is very important that init_client_ips is called before this
# It is important that init_client_ips is called before this
# point, allowing it to cache its results in request.META, since
# after this point it will be more difficult for it to operate
# without knowing about ORIGINAL_REMOTE_ADDR. (The less code that
# is aware of that, the better, and the ip code should be lifted
# out into a library anyhow.)
if legacy_ip.USE_LEGACY_IP.is_enabled():
request.META['REMOTE_ADDR'] = legacy_ip.get_legacy_ip(request)
else:
request.META['REMOTE_ADDR'] = safest_client_ip
# after this point it will be unable to reconstruct the original
# IP chain.
request.META['REMOTE_ADDR'] = safest_client_ip

0 comments on commit 9444186

Please sign in to comment.