diff --git a/peeringdb_server/inet.py b/peeringdb_server/inet.py index 400af389..241da04f 100644 --- a/peeringdb_server/inet.py +++ b/peeringdb_server/inet.py @@ -5,9 +5,38 @@ import rdap from rdap import RdapAsn from rdap.exceptions import RdapException, RdapHTTPError, RdapNotFoundError import requests +from django.utils.translation import ugettext_lazy as _ from peeringdb_server import settings +BOGON_ASN_RANGES = [ + # RFC 5398 - documentation 16-bit + (64496, 64511), + # RFC 5398 - documentation 32-bit + (65536, 65551), + # RFC 6996 - private 16-bit + (64512, 65534), + # RFC 6996 - private 32-bit + (4200000000, 4294967294), +] + +class BogonAsn(rdap.RdapAsn): + + """ + On tutorial mode environments we will return an instance + of this to provide an rdapasn result for asns in the + private and documentation ranges + """ + + def __init__(self, asn): + name = "AS{}".format(asn) + self._parsed = { + "name":name, + "org_name":name, + "org_address":None, + "emails":[] + } + class RdapLookup(rdap.RdapClient): """ @@ -23,6 +52,42 @@ class RdapLookup(rdap.RdapClient): super(RdapLookup, self).__init__(config) + def get_asn(self, asn): + """ + We handle asns that fall into the private/documentation ranges + manually - others are processed normally through rdap lookup + """ + + if asn_is_bogon(asn): + if settings.TUTORIAL_MODE: + return BogonAsn(asn) + else: + raise RdapException(_("ASNs for documentation/private purposes " \ + "are not allowed in this environment")) + return super(RdapLookup, self).get_asn(asn) + + + +def asn_is_bogon(asn): + """ + Test if an asn is bogon by being either in the documentation + or private asn ranges + + Arguments: + - asn + + Return: + - bool: True if in bogon range + """ + + asn = int(asn) + for as_range in BOGON_ASN_RANGES: + if asn >= as_range[0] and asn <= as_range[1]: + return True + return False + + + def network_is_bogon(network): """ Returns if the passed ipaddress network is a bogon diff --git a/peeringdb_server/management/commands/pdb_api_test.py b/peeringdb_server/management/commands/pdb_api_test.py index c474fdeb..db50c679 100644 --- a/peeringdb_server/management/commands/pdb_api_test.py +++ b/peeringdb_server/management/commands/pdb_api_test.py @@ -27,6 +27,7 @@ from peeringdb_server.models import ( IXLanPrefix, InternetExchangeFacility) from peeringdb_server.serializers import REFTAG_MAP as REFTAG_MAP_SLZ +from peeringdb_server import inet, settings as pdb_settings START_TIMESTAMP = time.time() @@ -1033,6 +1034,32 @@ class TestJSON(unittest.TestCase): ########################################################################## + def test_org_admin_002_POST_net_bogon_asn(self): + + # Test bogon asn failure + + data = self.make_data_net() + for bogon_asn in inet.BOGON_ASN_RANGES: + r_data = self.assert_create( + self.db_org_admin, "net", data, + test_failures={"invalid": { + "asn": bogon_asn[0] + }}, test_success=False) + + # server running in tutorial mode should be allowed + # to create networks with bogon asns, so we test that + # as well + + pdb_settings.TUTORIAL_MODE = True + + for bogon_asn in inet.BOGON_ASN_RANGES: + data = self.make_data_net(asn=bogon_asn[0]) + r_data = self.assert_create(self.db_org_admin, "net", data) + + pdb_settings.TUTORIAL_MODE = False + + ########################################################################## + def test_org_admin_002_PUT_net_write_only_fields(self): """ with this we check that certain fields that are allowed to be diff --git a/peeringdb_server/signals.py b/peeringdb_server/signals.py index 0fd1ed33..696c2ab6 100644 --- a/peeringdb_server/signals.py +++ b/peeringdb_server/signals.py @@ -199,9 +199,8 @@ def uoar_creation(sender, instance, created=False, **kwargs): rdap_lookup = rdap = RdapLookup().get_asn(instance.asn) ok = rdap_lookup.emails except RdapException, inst: - if not pdb_settings.AUTO_APPROVE_AFFILIATION: - instance.deny() - raise + instance.deny() + raise # create organization instance.org, org_created = Organization.create_from_rdap( diff --git a/tests/django_init.py b/tests/django_init.py index d36dd958..32247bc1 100644 --- a/tests/django_init.py +++ b/tests/django_init.py @@ -156,7 +156,7 @@ settings.configure( DATA_QUALITY_MAX_PREFIX_V6_LIMIT=500000, TUTORIAL_MODE=False, RATELIMITS={ - "view_affiliate_to_org_POST": "3/m", + "view_affiliate_to_org_POST": "100/m", "resend_confirmation_mail": "2/m", "view_request_ownership_GET": "3/m", "view_username_retrieve_initiate": "2/m", diff --git a/tests/test_api.py b/tests/test_api.py index 8a1aa871..0d379c65 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -41,6 +41,8 @@ def setup_module(module): def get_asn(self, asn): if asn in ASN_RANGE_OVERRIDE: return pdbinet.RdapAsn(self.override_result) + elif pdbinet.asn_is_bogon(asn): + return RdapLookup_get_asn(self, asn) else: raise pdbinet.RdapNotFoundError() diff --git a/tests/test_asn_automation.py b/tests/test_asn_automation.py index b76772ee..77f201b2 100644 --- a/tests/test_asn_automation.py +++ b/tests/test_asn_automation.py @@ -3,12 +3,16 @@ import json import pytest import peeringdb_server.models as models import peeringdb_server.views as pdbviews +from django.contrib.auth import get_user_model from django.contrib.auth.models import Group from django.test import TestCase, Client, RequestFactory import peeringdb_server.inet as pdbinet +from util import SettingsCase ERR_COULD_NOT_GET_RIR_ENTRY = "RDAP Lookup Error: Test Not Found" +ERR_BOGON_ASN = "RDAP Lookup Error: ASNs for documentation/private purposes " \ + "are not allowed in this environment" RdapLookup_get_asn = pdbinet.RdapLookup.get_asn @@ -39,6 +43,8 @@ def setup_module(module): r._parsed["name"] = "AS%d" % asn r._parsed["org_name"] = "ORG AS%d" % asn return r + elif pdbinet.asn_is_bogon(asn): + return RdapLookup_get_asn(self, asn) else: raise pdbinet.RdapNotFoundError("Test Not Found") @@ -204,6 +210,24 @@ class AsnAutomationTestCase(TestCase): self.assertEqual(net.status, "ok") self.assertEqual(net.org.status, "ok") + def test_affiliate_to_bogon_asn(self): + """ + tests affiliation with non-existant asn + """ + asns = [] + for a,b in pdbinet.BOGON_ASN_RANGES: + asns.extend([a,b]) + + for asn in asns: + request = self.factory.post("/affiliate-to-org", data={ + "asn": asn}) + + request.user = self.user_a + request._dont_enforce_csrf_checks = True + resp = json.loads(pdbviews.view_affiliate_to_org(request).content) + self.assertEqual(resp.get("asn"), ERR_BOGON_ASN) + + def test_claim_ownership(self): """ tests ownership to org via asn RiR validation @@ -243,3 +267,31 @@ class AsnAutomationTestCase(TestCase): self.assertEqual( self.user_b.groups.filter(name=org.admin_usergroup.name).exists(), False) + +class TestTutorialMode(SettingsCase): + settings = {"TUTORIAL_MODE":True} + + def setUp(self): + super(TestTutorialMode, self).setUp() + self.factory = RequestFactory() + + def test_affiliate_to_bogon_asn(self): + """ + tests affiliation with non-existant bogon asn + with tutorial mode enabled those should be allowed + """ + user = get_user_model().objects.create_user("user_a", "user_a@localhost", "user_a") + asns = [] + for a,b in pdbinet.BOGON_ASN_RANGES: + asns.extend([a,b]) + + for asn in asns: + request = self.factory.post("/affiliate-to-org", data={ + "asn": asn}) + + request.user = user + request._dont_enforce_csrf_checks = True + resp = json.loads(pdbviews.view_affiliate_to_org(request).content) + self.assertEqual(resp.get("status"), "ok") + + diff --git a/tests/test_settings.py b/tests/test_settings.py index 06fea92e..ed45f7ac 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -3,36 +3,11 @@ from django.contrib.auth import get_user_model from django.conf import settings -from util import ClientCase +from util import SettingsCase from peeringdb_server import signals, models, serializers from peeringdb_server import settings as pdb_settings -class SettingsCase(ClientCase): - - """ - Since we read settings from peeringdb_server.settings - we can't use the `settings` fixture from pytest-django - - This class instead does something similar for peeringdb_server.settings, - where it will override settings specified and then reset after test case - is finished - """ - - settings = {} - - @classmethod - def setUp(cls): - cls._restore = {} - for k,v in cls.settings.items(): - cls._restore[k] = getattr(pdb_settings, k) - setattr(pdb_settings, k, v) - - @classmethod - def tearDown(cls): - for k,v in cls._restore.items(): - setattr(pdb_settings, k, v) - class TestAutoVerifyUser(SettingsCase): settings = {"AUTO_VERIFY_USERS":True} diff --git a/tests/util.py b/tests/util.py index d694a125..b1d2c246 100644 --- a/tests/util.py +++ b/tests/util.py @@ -1,7 +1,9 @@ from django.test import TestCase from django.contrib.auth.models import Group, AnonymousUser +from django.conf import settings import peeringdb_server.models as models import django_namespace_perms as nsp +from peeringdb_server import settings as pdb_settings class ClientCase(TestCase): @@ -33,3 +35,31 @@ class ClientCase(TestCase): group=guest_group, namespace="peeringdb.organization.*.network.*.poc_set.public", permissions=0x01) + + +class SettingsCase(ClientCase): + + """ + Since we read settings from peeringdb_server.settings + we can't use the `settings` fixture from pytest-django + + This class instead does something similar for peeringdb_server.settings, + where it will override settings specified and then reset after test case + is finished + """ + + settings = {} + + @classmethod + def setUp(cls): + cls._restore = {} + for k,v in cls.settings.items(): + cls._restore[k] = getattr(pdb_settings, k) + setattr(pdb_settings, k, v) + + @classmethod + def tearDown(cls): + for k,v in cls._restore.items(): + setattr(pdb_settings, k, v) + +