def test_missing_mandatory_attribute(self): validator = XMLValidator(translator=FakeTranslator()) errors = validator.validate_request( sample_requests.missing_issue_instant_attr) self.assertEqual(len(errors), 1) self.assertIn("The attribute 'IssueInstant' is required but missing.", errors[0].message)
def test_invalid_attribute_format(self): # See: https://github.com/italia/spid-testenv2/issues/63 validator = XMLValidator(translator=FakeTranslator()) errors = validator.validate_request(sample_requests.invalid_id_attr) self.assertEqual(len(errors), 1) self.assertIn("is not a valid value of the atomic type 'xs:ID'", errors[0].message)
def test_multiple_errors(self): validator = XMLValidator(translator=FakeTranslator()) errors = validator.validate_request(sample_requests.multiple_errors) self.assertEqual(len(errors), 2) self.assertIn("is not a valid value of the atomic type 'xs:ID'", errors[0].message) self.assertIn("The attribute 'Version' is required but missing.", errors[1].message)
def test_unexpected_element(self): # See: https://github.com/italia/spid-testenv2/issues/79 validator = XMLValidator(translator=FakeTranslator()) errors = validator.validate_request(sample_requests.unexpected_element) self.assertEqual(len(errors), 1) self.assertIn( "Element '{urn:oasis:names:tc:SAML:2.0:assertion}AuthnContextClassRef': " "This element is not expected. Expected is one of ( {urn:oasis:names:tc:SAML:2.0:assertion}" "Conditions, {urn:oasis:names:tc:SAML:2.0:protocol}RequestedAuthnContext, {urn:oasis:names:tc:SAML:2.0:protocol}Scoping ).", errors[0].message)
def test_invalid_comparison_attribute(self): # https://github.com/italia/spid-testenv2/issues/97 validator = XMLValidator(translator=FakeTranslator()) errors = validator.validate_request( sample_requests.invalid_comparison_attr) self.assertEqual(len(errors), 2) self.assertIn( "The value 'invalid' is not an element of the set " "{'exact', 'minimum', 'maximum', 'better'}", errors[0].message) self.assertIn("'invalid' is not a valid value of the atomic type", errors[1].message)
def __init__(self, *args, **kwargs): from testenv.parser import XMLValidator self.xml_validator = XMLValidator() self.schema = None
class SpidParser(object): """ Parser for spid messages """ def __init__(self, *args, **kwargs): from testenv.parser import XMLValidator self.xml_validator = XMLValidator() self.schema = None def get_schema(self, action, binding, **kwargs): """ :param binding: """ _schema = None receivers = kwargs.get('receivers') issuer = kwargs.get('issuer') if action == 'login': required_signature = False if binding == BINDING_HTTP_POST: required_signature = True elif binding == BINDING_HTTP_REDIRECT: required_signature = False attribute_consuming_service_indexes = kwargs.get( 'attribute_consuming_service_indexes') assertion_consumer_service_indexes = kwargs.get( 'assertion_consumer_service_indexes') _schema = Elem( name='auth_request', tag='samlp:AuthnRequest', attributes=[ Attr('id'), Attr('version', default='2.0'), TimestampAttr('issue_instant', func=check_utc_date, val_converter=str_to_time), Attr('destination', default=receivers), Attr('force_authn', required=False), Attr('attribute_consuming_service_index', default=attribute_consuming_service_indexes, required=False), Or( Attr('assertion_consumer_service_index', default=assertion_consumer_service_indexes, required=False), And( Attr('assertion_consumer_service_url', required=False), Attr('protocol_binding', default=BINDING_HTTP_POST, required=False))) ], children=[ Elem('subject', tag='saml:Subject', required=False, attributes=[ Attr('format', default=NAMEID_FORMAT_ENTITY), Attr('name_qualifier') ]), Elem( 'issuer', tag='saml:Issuer', attributes=[ Attr('format', default=NAMEID_FORMAT_ENTITY), Attr('name_qualifier', default=issuer), Attr('text', default=issuer) ], ), Elem('name_id_policy', tag='samlp:NameIDPolicy', attributes=[ Attr('allow_create', absent=True, required=False), Attr('format', default=NAMEID_FORMAT_TRANSIENT) ]), Elem('conditions', tag='saml:Conditions', required=False, attributes=[ Attr('not_before', func=check_utc_date), Attr('not_on_or_after', func=check_utc_date) ]), Elem('requested_authn_context', tag='saml:RequestedAuthnContext', attributes=[ Attr('comparison', default=COMPARISONS), ], children=[ Elem('authn_context_class_ref', tag='saml:AuthnContextClassRef', attributes=[ Attr('text', default=SPID_LEVELS) ]) ]), Elem( 'signature', tag='ds:Signature', required=required_signature, ), Elem('scoping', tag='saml2p:Scoping', required=False, attributes=[Attr('proxy_count', default=[0])]), ]) elif action == 'logout': _schema = Elem(name='logout_request', tag='samlp:LogoutRequest', attributes=[ Attr('id'), Attr('version', default='2.0'), Attr('issue_instant', func=check_utc_date), Attr('destination', default=receivers), ], children=[ Elem( 'issuer', tag='saml:Issuer', attributes=[ Attr('format', default=NAMEID_FORMAT_ENTITY), Attr('name_qualifier', default=issuer), Attr('text', default=issuer) ], ), Elem('name_id', tag='saml:NameID', attributes=[ Attr('name_qualifier'), Attr('format', default=NAMEID_FORMAT_TRANSIENT) ]), Elem( 'session_index', tag='samlp:SessionIndex', ), ]) return _schema def parse(self, obj, action, binding, schema=None, **kwargs): """ :param obj: pysaml2 object :param binding: :param schema: custom schema (None by default) """ errors = {} # Validate xml against its XSD schema validation_errors = self.xml_validator.validate_request(obj.xmlstr) if validation_errors: errors['validation_errors'] = validation_errors # Validate xml against SPID rules _schema = self.get_schema(action, binding, **kwargs)\ if schema is None else schema self.observer = Observer() self.observer.attach(_schema) validated = _schema.validate(obj.message) spid_errors = self.observer.evaluate() if spid_errors: errors['spid_errors'] = spid_errors return validated, errors
def test_duplicate_attribute(self): validator = XMLValidator(translator=FakeTranslator()) errors = validator.validate_request( sample_requests.duplicate_version_attr) self.assertEqual(len(errors), 1) self.assertIn('Attribute Version redefined', errors[0].message)
def test_invalid_xml(self): validator = XMLValidator(translator=FakeTranslator()) errors = validator.validate_request('<a></b>') self.assertEqual(len(errors), 1) self.assertIn('Opening and ending tag mismatch: a line 1 and b', errors[0].message)
def test_not_xml(self): validator = XMLValidator(translator=FakeTranslator()) errors = validator.validate_request('{"this": "is JSON"}') self.assertEqual(len(errors), 1) self.assertIn("Start tag expected, '<' not found", errors[0].message)
def test_empty_request(self): validator = XMLValidator(translator=FakeTranslator()) errors = validator.validate_request('') self.assertEqual(len(errors), 1) self.assertIn('Document is empty', errors[0].message)
def test_valid_requests(self): validator = XMLValidator(translator=FakeTranslator()) for request in sample_requests.valid: errors = validator.validate_request(request) self.assertEqual(errors, [])