diff options
author | Daniel Drexler <aeturnum@gmail.com> | 2020-08-27 01:22:51 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-08-27 01:22:51 +0300 |
commit | ae7b4a1755655b0bf87614ff6eb75531e6c454b0 (patch) | |
tree | e3db5fc065993bf150b5df9b4a66c665f17cac2b /acme | |
parent | f66a592e37d9426edcd21a7db046043efa84536e (diff) |
Support Register Unsafely in Update (#8212)
* Allow user to remove email using update command
Fixes #3162. Slight change to control flow to replace current email
addresses with an empty list. Also add appropriate result message when
an email is removed.
* Update ACME to allow update to remove fields
- New field type "UnFalseyField" that treats all non-None fields as
non-empty
- Contact changed to new field type to allow sending of empty contact
field
- Certbot update adjusted to use tuple instead of None when empty
- Test updated to check more logic
- Unrelated type hint added to keep pycharm gods happy
* Moved some mocks into decorators
* Restore default to `contact` but do not serialize
- Add `to_partial_json` and `fields_to_partial_json` to Registration
- Store private variable noting if the value of the `contact` field was
provided by the user.
- Change message when updating without email to reflect removal of
all contact info.
- Add note in changelog that `update_account` with the
`--register-unsafely-without-email` flag will remove contact
from an account.
* Reverse logic for field handling on serialization
Now forcably add contact when serilizing, but go back to base `jose`
field type.
* Responding to Review
- change out of date name
- update several comments
- update `from_data` function of `Registration`
- Update test to remove superfluous mock
* Responding to review
- Change comments to make from_data more clear
- Remove code worried about None (omitempty has got my back)
- Update test to be more reliable
- Add typing import with comment to avoid pylint bug
Diffstat (limited to 'acme')
-rw-r--r-- | acme/acme/messages.py | 56 | ||||
-rw-r--r-- | acme/tests/messages_test.py | 13 |
2 files changed, 67 insertions, 2 deletions
diff --git a/acme/acme/messages.py b/acme/acme/messages.py index 030e62d6c..8c9bf9701 100644 --- a/acme/acme/messages.py +++ b/acme/acme/messages.py @@ -315,6 +315,9 @@ class Registration(ResourceBody): # on new-reg key server ignores 'key' and populates it based on # JWS.signature.combined.jwk key = jose.Field('key', omitempty=True, decoder=jose.JWK.from_json) + # Contact field implements special behavior to allow messages that clear existing + # contacts while not expecting the `contact` field when loading from json. + # This is implemented in the constructor and *_json methods. contact = jose.Field('contact', omitempty=True, default=()) agreement = jose.Field('agreement', omitempty=True) status = jose.Field('status', omitempty=True) @@ -327,24 +330,73 @@ class Registration(ResourceBody): @classmethod def from_data(cls, phone=None, email=None, external_account_binding=None, **kwargs): - """Create registration resource from contact details.""" + """ + Create registration resource from contact details. + + The `contact` keyword being passed to a Registration object is meaningful, so + this function represents empty iterables in its kwargs by passing on an empty + `tuple`. + """ + + # Note if `contact` was in kwargs. + contact_provided = 'contact' in kwargs + + # Pop `contact` from kwargs and add formatted email or phone numbers details = list(kwargs.pop('contact', ())) if phone is not None: details.append(cls.phone_prefix + phone) if email is not None: details.extend([cls.email_prefix + mail for mail in email.split(',')]) - kwargs['contact'] = tuple(details) + + # Insert formatted contact information back into kwargs + # or insert an empty tuple if `contact` provided. + if details or contact_provided: + kwargs['contact'] = tuple(details) if external_account_binding: kwargs['external_account_binding'] = external_account_binding return cls(**kwargs) + def __init__(self, **kwargs): + """Note if the user provides a value for the `contact` member.""" + if 'contact' in kwargs: + # Avoid the __setattr__ used by jose.TypedJSONObjectWithFields + object.__setattr__(self, '_add_contact', True) + super(Registration, self).__init__(**kwargs) + def _filter_contact(self, prefix): return tuple( detail[len(prefix):] for detail in self.contact # pylint: disable=not-an-iterable if detail.startswith(prefix)) + def _add_contact_if_appropriate(self, jobj): + """ + The `contact` member of Registration objects should not be required when + de-serializing (as it would be if the Fields' `omitempty` flag were `False`), but + it should be included in serializations if it was provided. + + :param jobj: Dictionary containing this Registrations' data + :type jobj: dict + + :returns: Dictionary containing Registrations data to transmit to the server + :rtype: dict + """ + if getattr(self, '_add_contact', False): + jobj['contact'] = self.encode('contact') + + return jobj + + def to_partial_json(self): + """Modify josepy.JSONDeserializable.to_partial_json()""" + jobj = super(Registration, self).to_partial_json() + return self._add_contact_if_appropriate(jobj) + + def fields_to_partial_json(self): + """Modify josepy.JSONObjectWithFields.fields_to_partial_json()""" + jobj = super(Registration, self).fields_to_partial_json() + return self._add_contact_if_appropriate(jobj) + @property def phones(self): """All phones found in the ``contact`` field.""" diff --git a/acme/tests/messages_test.py b/acme/tests/messages_test.py index 890a5f413..3458105b2 100644 --- a/acme/tests/messages_test.py +++ b/acme/tests/messages_test.py @@ -254,6 +254,19 @@ class RegistrationTest(unittest.TestCase): from acme.messages import Registration hash(Registration.from_json(self.jobj_from)) + def test_default_not_transmitted(self): + from acme.messages import NewRegistration + empty_new_reg = NewRegistration() + new_reg_with_contact = NewRegistration(contact=()) + + self.assertEqual(empty_new_reg.contact, ()) + self.assertEqual(new_reg_with_contact.contact, ()) + + self.assertTrue('contact' not in empty_new_reg.to_partial_json()) + self.assertTrue('contact' not in empty_new_reg.fields_to_partial_json()) + self.assertTrue('contact' in new_reg_with_contact.to_partial_json()) + self.assertTrue('contact' in new_reg_with_contact.fields_to_partial_json()) + class UpdateRegistrationTest(unittest.TestCase): """Tests for acme.messages.UpdateRegistration.""" |