Repository: certbot/certbot

Issue:
Directory deserialization should allow 'meta' section
The spec has added support for a [`meta` section in the `directory` object](https://ietf-wg-acme.github.io/acme/#directory) which allows for a number of pieces of information that are not necessarily related to the ACME protocol. Currently the client will fail if these fields are returned which is causing letsencrypt/boulder#1692 to fail.

Also in general it would be good to be slightly less strict about JSON deserialization (i.e. ignoring unexpected fields) since we have other better ways of testing if the server/client start drifting out of sync.



Discussion:

Resolving patch:
diff --git a/acme/acme/messages.py b/acme/acme/messages.py
--- a/acme/acme/messages.py
+++ b/acme/acme/messages.py
@@ -123,6 +123,12 @@ class Directory(jose.JSONDeSerializable):
 
     _REGISTERED_TYPES = {}
 
+    class Meta(jose.JSONObjectWithFields):
+        """Directory Meta."""
+        terms_of_service = jose.Field('terms-of-service', omitempty=True)
+        website = jose.Field('website', omitempty=True)
+        caa_identities = jose.Field('caa-identities', omitempty=True)
+
     @classmethod
     def _canon_key(cls, key):
         return getattr(key, 'resource_type', key)
@@ -137,10 +143,11 @@ def register(cls, resource_body_cls):
 
     def __init__(self, jobj):
         canon_jobj = util.map_keys(jobj, self._canon_key)
-        if not set(canon_jobj).issubset(self._REGISTERED_TYPES):
+        if not set(canon_jobj).issubset(
+                set(self._REGISTERED_TYPES).union(['meta'])):
             # TODO: acme-spec is not clear about this: 'It is a JSON
             # dictionary, whose keys are the "resource" values listed
-            # in {{https-requests}}'z
+            # in {{https-requests}}'
             raise ValueError('Wrong directory fields')
         # TODO: check that everything is an absolute URL; acme-spec is
         # not clear on that
@@ -163,6 +170,7 @@ def to_partial_json(self):
 
     @classmethod
     def from_json(cls, jobj):
+        jobj['meta'] = cls.Meta.from_json(jobj.pop('meta', {}))
         try:
             return cls(jobj)
         except ValueError as error:


Test patch:
diff --git a/acme/acme/messages_test.py b/acme/acme/messages_test.py
--- a/acme/acme/messages_test.py
+++ b/acme/acme/messages_test.py
@@ -90,6 +90,11 @@ def setUp(self):
         self.dir = Directory({
             'new-reg': 'reg',
             mock.MagicMock(resource_type='new-cert'): 'cert',
+            'meta': Directory.Meta(
+                terms_of_service='https://example.com/acme/terms',
+                website='https://www.example.com/',
+                caa_identities=['example.com'],
+            ),
         })
 
     def test_init_wrong_key_value_error(self):
@@ -111,9 +116,16 @@ def test_getattr(self):
     def test_getattr_fails_with_attribute_error(self):
         self.assertRaises(AttributeError, self.dir.__getattr__, 'foo')
 
-    def test_to_partial_json(self):
-        self.assertEqual(
-            self.dir.to_partial_json(), {'new-reg': 'reg', 'new-cert': 'cert'})
+    def test_to_json(self):
+        self.assertEqual(self.dir.to_json(), {
+            'new-reg': 'reg',
+            'new-cert': 'cert',
+            'meta': {
+                'terms-of-service': 'https://example.com/acme/terms',
+                'website': 'https://www.example.com/',
+                'caa-identities': ['example.com'],
+            },
+        })
 
     def test_from_json_deserialization_error_on_wrong_key(self):
         from acme.messages import Directory