Skip to content

Commit 55be003

Browse files
committed
Reformat and rearrange code
Signed-off-by: Ivan Kanakarakis <[email protected]>
1 parent 7fd1719 commit 55be003

File tree

3 files changed

+112
-67
lines changed

3 files changed

+112
-67
lines changed

docs/howto/config.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,8 @@ a period for which the metadata fetched from the the MDQ server are considered
227227
fresh. After that period has passed the metadata are not valid anymore and must
228228
be fetched again. The period must be in the format defined in `iso 8601 <https://www.iso.org/iso-8601-date-and-time-format.html>`_.
229229

230-
By default, if `freshness_period` is not defined, the metadata are fetched
231-
only the first time they are requested and never refreshed.
230+
By default, if `freshness_period` is not defined, the metadata are refreshed
231+
every 12 hours (`P0Y0M0DT12H0M0S`).
232232

233233
organization
234234
^^^^^^^^^^^^

src/saml2/mdstore.py

Lines changed: 64 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,11 @@
3232
from saml2.s_utils import UnknownSystemEntity
3333
from saml2.sigver import split_len
3434
from saml2.validate import valid_instance
35-
from saml2.time_util import valid, instant, add_duration, before, str_to_time
35+
from saml2.time_util import valid
36+
from saml2.time_util import instant
37+
from saml2.time_util import add_duration
38+
from saml2.time_util import before
39+
from saml2.time_util import str_to_time
3640
from saml2.validate import NotValid
3741
from saml2.sigver import security_context
3842
from saml2.extension.mdattr import NAMESPACE as NS_MDATTR
@@ -72,6 +76,11 @@
7276
ENTITY_CATEGORY_SUPPORT = "http://macedir.org/entity-category-support"
7377
ASSURANCE_CERTIFICATION = "urn:oasis:names:tc:SAML:attribute:assurance-certification"
7478

79+
SAML_METADATA_CONTENT_TYPE = "application/samlmetadata+xml"
80+
DEFAULT_FRESHNESS_PERIOD = "P0Y0M0DT12H0M0S"
81+
82+
83+
7584
REQ2SRV = {
7685
# IDP
7786
"authn_request": "single_sign_on_service",
@@ -664,22 +673,21 @@ def signed(self):
664673
def parse_and_check_signature(self, txt):
665674
self.parse(txt)
666675

667-
if self.cert:
668-
if not self.signed():
669-
return True
670-
671-
node_name = self.node_name \
672-
or "%s:%s" % (md.EntitiesDescriptor.c_namespace,
673-
md.EntitiesDescriptor.c_tag)
676+
if not self.cert:
677+
return True
674678

675-
if self.security.verify_signature(
676-
txt, node_name=node_name, cert_file=self.cert):
677-
return True
678-
else:
679-
return False
680-
else:
679+
if not self.signed():
681680
return True
682681

682+
fallback_name = "{ns}:{tag}".format(
683+
ns=md.EntitiesDescriptor.c_namespace, tag=md.EntitiesDescriptor.c_tag
684+
)
685+
node_name = self.node_name or fallback_name
686+
687+
return self.security.verify_signature(
688+
txt, node_name=node_name, cert_file=self.cert
689+
)
690+
683691

684692
class MetaDataFile(InMemoryMetaData):
685693
"""
@@ -805,9 +813,6 @@ def load(self, *args, **kwargs):
805813
self.entity[key] = item
806814

807815

808-
SAML_METADATA_CONTENT_TYPE = 'application/samlmetadata+xml'
809-
810-
811816
class MetaDataMDX(InMemoryMetaData):
812817
"""
813818
Uses the MDQ protocol to fetch entity information.
@@ -817,8 +822,9 @@ class MetaDataMDX(InMemoryMetaData):
817822

818823
@staticmethod
819824
def sha1_entity_transform(entity_id):
820-
return "{{sha1}}{}".format(
821-
hashlib.sha1(entity_id.encode("utf-8")).hexdigest())
825+
entity_id_sha1 = hashlib.sha1(entity_id.encode("utf-8")).hexdigest()
826+
transform = "{{sha1}}{digest}".format(digest=entity_id_sha1)
827+
return transform
822828

823829
def __init__(self, url=None, security=None, cert=None,
824830
entity_transform=None, freshness_period=None, **kwargs):
@@ -847,53 +853,61 @@ def __init__(self, url=None, security=None, cert=None,
847853

848854
self.cert = cert
849855
self.security = security
850-
self.freshness_period = freshness_period
851-
if freshness_period:
852-
self.expiration_date = {}
856+
self.freshness_period = freshness_period or DEFAULT_FRESHNESS_PERIOD
857+
self.expiration_date = {}
853858

854859
# We assume that the MDQ server will return a single entity
855860
# described by a single <EntityDescriptor> element. The protocol
856861
# does allow multiple entities to be returned in an
857862
# <EntitiesDescriptor> element but we will not currently support
858863
# that use case since it is unlikely to be leveraged for most
859864
# flows.
860-
self.node_name = "%s:%s" % (md.EntityDescriptor.c_namespace,
861-
md.EntityDescriptor.c_tag)
865+
self.node_name = "{ns}:{tag}".format(
866+
ns=md.EntityDescriptor.c_namespace, tag=md.EntityDescriptor.c_tag
867+
)
862868

863869
def load(self, *args, **kwargs):
864870
# Do nothing
865871
pass
866872

867-
def fetch_metadata(self, item):
868-
mdx_url = "%s/entities/%s" % (self.url, self.entity_transform(item))
869-
response = requests.get(mdx_url, headers={
870-
'Accept': SAML_METADATA_CONTENT_TYPE})
871-
if response.status_code == 200:
872-
_txt = response.content
873-
if self.parse_and_check_signature(_txt):
874-
if self.freshness_period:
875-
curr_time = str_to_time(instant())
876-
self.expiration_date[item] = add_duration(
877-
curr_time, self.freshness_period)
878-
return self.entity[item]
879-
else:
880-
logger.info("Response status: %s", response.status_code)
881-
raise KeyError
873+
def _fetch_metadata(self, item):
874+
mdx_url = "{url}/entities/{id}".format(
875+
url=self.url, id=self.entity_transform(item)
876+
)
882877

883-
def _is_fresh(self, item):
884-
return self.freshness_period and before(self.expiration_date[item])
878+
response = requests.get(mdx_url, headers={"Accept": SAML_METADATA_CONTENT_TYPE})
879+
if response.status_code != 200:
880+
error_msg = "Fething {item}: Got response status {status}".format(
881+
item=item, status=response.status_code
882+
)
883+
logger.info(error_msg)
884+
raise KeyError(error_msg)
885+
886+
_txt = response.content
887+
if not self.parse_and_check_signature(_txt):
888+
error_msg = "Fething {item}: invalid signature".format(
889+
item=item, status=response.status_code
890+
)
891+
logger.info(error_msg)
892+
raise KeyError(error_msg)
893+
894+
curr_time = str_to_time(instant())
895+
self.expiration_date[item] = add_duration(curr_time, self.freshness_period)
896+
return self.entity[item]
897+
898+
def _is_metadata_fresh(self, item):
899+
return before(self.expiration_date[item])
885900

886901
def __getitem__(self, item):
887-
if item in self.entity:
888-
if self._is_fresh(item):
889-
entity = self.entity[item]
890-
else:
891-
logger.info("Metadata for {} have expired, refreshing "
892-
"metadata".format(item))
893-
self.entity.pop(item)
894-
entity = self.fetch_metadata(item)
902+
if item not in self.entity:
903+
entity = self._fetch_metadata(item)
904+
elif not self._is_metadata_fresh(item):
905+
msg = "Metadata for {} have expired; refreshing metadata".format(item)
906+
logger.info(msg)
907+
old_entity = self.entity.pop(item)
908+
entity = self._fetch_metadata(item)
895909
else:
896-
entity = self.fetch_metadata(item)
910+
entity = self.entity[item]
897911
return entity
898912

899913
def single_sign_on_service(self, entity_id, binding=None, typ="idpsso"):

tests/test_30_mdstore.py

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44
import os
55
import re
66
from collections import OrderedDict
7+
from unittest.mock import Mock
78
from unittest.mock import patch
89

10+
import responses
11+
912
from six.moves.urllib import parse
1013

1114
from saml2.config import Config
@@ -26,9 +29,6 @@
2629
from saml2.s_utils import UnknownPrincipal
2730
from pathutils import full_path
2831

29-
import responses
30-
import mock
31-
3232

3333
TESTS_DIR = os.path.dirname(__file__)
3434

@@ -336,24 +336,55 @@ def test_mdx_single_sign_on_service():
336336

337337

338338
@responses.activate
339-
@mock.patch('saml2.mdstore.before')
340-
def test_mdx_metadata_freshness_period(mock_datetime):
341-
"""Ensure that metadata is refreshed only when they have expired."""
342-
entity_id = \
343-
"http://xenosmilus.umdc.umu.se/simplesaml/saml2/idp/metadata.php"
339+
def test_mdx_metadata_freshness_period_not_expired():
340+
"""Ensure that metadata is not refreshed if not expired."""
344341

342+
entity_id = "http://xenosmilus.umdc.umu.se/simplesaml/saml2/idp/metadata.php"
345343
url = "http://mdx.example.com/entities/{}".format(
346-
parse.quote_plus(MetaDataMDX.sha1_entity_transform(entity_id)))
347-
responses.add(responses.GET, url, body=TEST_METADATA_STRING, status=200,
348-
content_type=SAML_METADATA_CONTENT_TYPE)
344+
parse.quote_plus(MetaDataMDX.sha1_entity_transform(entity_id))
345+
)
346+
347+
responses.add(
348+
responses.GET,
349+
url,
350+
body=TEST_METADATA_STRING,
351+
status=200,
352+
content_type=SAML_METADATA_CONTENT_TYPE,
353+
)
354+
355+
mdx = MetaDataMDX("http://mdx.example.com", freshness_period="P0Y0M0DT0H2M0S")
356+
mdx._is_metadata_fresh = Mock(return_value=True)
349357

350-
mock_datetime.return_value = True
351-
mdx = MetaDataMDX("http://mdx.example.com",
352-
freshness_period="P0Y0M0DT0H2M0S")
353358
mdx.single_sign_on_service(entity_id, BINDING_HTTP_REDIRECT)
359+
assert entity_id in mdx.entity
360+
354361
mdx.single_sign_on_service(entity_id, BINDING_HTTP_REDIRECT)
355362
assert len(responses.calls) == 1
356-
mock_datetime.return_value = False
363+
364+
365+
@responses.activate
366+
def test_mdx_metadata_freshness_period_expired():
367+
"""Ensure that metadata is not refreshed if not expired."""
368+
369+
entity_id = "http://xenosmilus.umdc.umu.se/simplesaml/saml2/idp/metadata.php"
370+
url = "http://mdx.example.com/entities/{}".format(
371+
parse.quote_plus(MetaDataMDX.sha1_entity_transform(entity_id))
372+
)
373+
374+
responses.add(
375+
responses.GET,
376+
url,
377+
body=TEST_METADATA_STRING,
378+
status=200,
379+
content_type=SAML_METADATA_CONTENT_TYPE,
380+
)
381+
382+
mdx = MetaDataMDX("http://mdx.example.com", freshness_period="P0Y0M0DT0H2M0S")
383+
mdx._is_metadata_fresh = Mock(return_value=False)
384+
385+
mdx.single_sign_on_service(entity_id, BINDING_HTTP_REDIRECT)
386+
assert entity_id in mdx.entity
387+
357388
mdx.single_sign_on_service(entity_id, BINDING_HTTP_REDIRECT)
358389
assert len(responses.calls) == 2
359390

0 commit comments

Comments
 (0)