summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBarry Warsaw2012-09-22 17:13:13 -0400
committerBarry Warsaw2012-09-22 17:13:13 -0400
commit12b9839a5e7f1e9fda477c5e40ed190e08292da7 (patch)
tree838f38fd2a6d571da443a6c0335c17044ff86371
parent7e97811156ad3fbf9daafc253a2c473b05e07542 (diff)
downloadmailman-12b9839a5e7f1e9fda477c5e40ed190e08292da7.tar.gz
mailman-12b9839a5e7f1e9fda477c5e40ed190e08292da7.tar.zst
mailman-12b9839a5e7f1e9fda477c5e40ed190e08292da7.zip
-rw-r--r--src/mailman/docs/NEWS.rst5
-rw-r--r--src/mailman/rest/addresses.py41
-rw-r--r--src/mailman/rest/docs/addresses.rst42
-rw-r--r--src/mailman/rest/tests/test_addresses.py74
4 files changed, 159 insertions, 3 deletions
diff --git a/src/mailman/docs/NEWS.rst b/src/mailman/docs/NEWS.rst
index 50eeb0fda..8fec45957 100644
--- a/src/mailman/docs/NEWS.rst
+++ b/src/mailman/docs/NEWS.rst
@@ -23,6 +23,11 @@ REST
* You can now PUT and PATCH on user resources to change the user's display
name or password. For passwords, you pass in the clear text password and
Mailman will hash it before storing.
+ * You can now verify and unverify an email address through the REST API.
+ POST to .../addresses/<email>/verify and .../addresses/<email>/unverify
+ respectively. The POST data is ignored. It is not an error to verify or
+ unverify an address more than once, but verifying an already verified
+ address does not change its `.verified_on` date. (LP: #1054730)
3.0 beta 2 -- "Freeze"
diff --git a/src/mailman/rest/addresses.py b/src/mailman/rest/addresses.py
index 2e81cb030..1e043c2c7 100644
--- a/src/mailman/rest/addresses.py
+++ b/src/mailman/rest/addresses.py
@@ -31,10 +31,11 @@ from operator import attrgetter
from restish import http, resource
from zope.component import getUtility
-from mailman.rest.helpers import CollectionMixin, etag, path_to
+from mailman.rest.helpers import CollectionMixin, etag, no_content, path_to
from mailman.rest.members import MemberCollection
from mailman.rest.preferences import Preferences
from mailman.interfaces.usermanager import IUserManager
+from mailman.utilities.datetime import now
@@ -76,6 +77,24 @@ class AllAddresses(_AddressBase):
+class _VerifyResource(resource.Resource):
+ """A helper resource for verify/unverify POSTS."""
+
+ def __init__(self, address, action):
+ self._address = address
+ self._action = action
+ assert action in ('verify', 'unverify')
+
+ @resource.POST()
+ def verify(self, request):
+ # We don't care about the POST data, just do the action.
+ if self._action == 'verify' and self._address.verified_on is None:
+ self._address.verified_on = now()
+ elif self._action == 'unverify':
+ self._address.verified_on = None
+ return no_content()
+
+
class AnAddress(_AddressBase):
"""An address."""
@@ -115,6 +134,26 @@ class AnAddress(_AddressBase):
'addresses/{0}'.format(self._address.email))
return child, []
+ @resource.child()
+ def verify(self, request, segments):
+ """/addresses/<email>/verify"""
+ if len(segments) != 0:
+ return http.bad_request()
+ if self._address is None:
+ return http.not_found()
+ child = _VerifyResource(self._address, 'verify')
+ return child, []
+
+ @resource.child()
+ def unverify(self, request, segments):
+ """/addresses/<email>/verify"""
+ if len(segments) != 0:
+ return http.bad_request()
+ if self._address is None:
+ return http.not_found()
+ child = _VerifyResource(self._address, 'unverify')
+ return child, []
+
class UserAddresses(_AddressBase):
diff --git a/src/mailman/rest/docs/addresses.rst b/src/mailman/rest/docs/addresses.rst
index cb9242d2b..f05b6b9b2 100644
--- a/src/mailman/rest/docs/addresses.rst
+++ b/src/mailman/rest/docs/addresses.rst
@@ -90,7 +90,6 @@ Verifying
When the address gets verified, this attribute is available in the REST
representation.
-::
>>> from mailman.utilities.datetime import now
>>> anne.verified_on = now()
@@ -103,6 +102,47 @@ representation.
self_link: http://localhost:9001/3.0/addresses/anne@example.com
verified_on: 2005-08-01T07:49:23
+Addresses can also be verified through the REST API, by POSTing to the
+'verify' sub-resource. The POST data is ignored.
+
+ >>> dump_json('http://localhost:9001/3.0/addresses/'
+ ... 'cris@example.com/verify', {})
+ content-length: 0
+ date: ...
+ server: ...
+ status: 204
+
+Now Cris's address is verified.
+
+ >>> dump_json('http://localhost:9001/3.0/addresses/cris@example.com')
+ display_name: Cris Person
+ email: cris@example.com
+ http_etag: "..."
+ original_email: cris@example.com
+ registered_on: 2005-08-01T07:49:23
+ self_link: http://localhost:9001/3.0/addresses/cris@example.com
+ verified_on: 2005-08-01T07:49:23
+
+If you should ever need to 'unverify' an address, POST to the 'unverify'
+sub-resource. Again, the POST data is ignored.
+
+ >>> dump_json('http://localhost:9001/3.0/addresses/'
+ ... 'cris@example.com/unverify', {})
+ content-length: 0
+ date: ...
+ server: ...
+ status: 204
+
+Now Cris's address is unverified.
+
+ >>> dump_json('http://localhost:9001/3.0/addresses/cris@example.com')
+ display_name: Cris Person
+ email: cris@example.com
+ http_etag: "..."
+ original_email: cris@example.com
+ registered_on: 2005-08-01T07:49:23
+ self_link: http://localhost:9001/3.0/addresses/cris@example.com
+
User addresses
==============
diff --git a/src/mailman/rest/tests/test_addresses.py b/src/mailman/rest/tests/test_addresses.py
index 385b83912..01ce710b2 100644
--- a/src/mailman/rest/tests/test_addresses.py
+++ b/src/mailman/rest/tests/test_addresses.py
@@ -28,11 +28,14 @@ __all__ = [
import unittest
from urllib2 import HTTPError
+from zope.component import getUtility
from mailman.app.lifecycle import create_list
from mailman.database.transaction import transaction
+from mailman.interfaces.usermanager import IUserManager
from mailman.testing.helpers import call_api
from mailman.testing.layers import RESTLayer
+from mailman.utilities.datetime import now
@@ -52,4 +55,73 @@ class TestAddresses(unittest.TestCase):
except HTTPError as exc:
self.assertEqual(exc.code, 404)
else:
- raise AssertionError('Expected HTTPError')
+ raise AssertionError('Expected HTTPError 404')
+
+ def test_verify_a_missing_address(self):
+ # POSTing to the 'verify' sub-resource returns a 404.
+ try:
+ call_api('http://localhost:9001/3.0/addresses/'
+ 'nobody@example.com/verify', {})
+ except HTTPError as exc:
+ self.assertEqual(exc.code, 404)
+ else:
+ raise AssertionError('Expected HTTPError 404')
+
+ def test_unverify_a_missing_address(self):
+ # POSTing to the 'unverify' sub-resource returns a 404.
+ try:
+ call_api('http://localhost:9001/3.0/addresses/'
+ 'nobody@example.com/unverify', {})
+ except HTTPError as exc:
+ self.assertEqual(exc.code, 404)
+ else:
+ raise AssertionError('Expected HTTPError 404')
+
+ def test_verify_already_verified(self):
+ # It's okay to verify an already verified; it just doesn't change the
+ # value.
+ verified_on = now()
+ with transaction():
+ anne = getUtility(IUserManager).create_address('anne@example.com')
+ anne.verified_on = verified_on
+ response, content = call_api(
+ 'http://localhost:9001/3.0/addresses/anne@example.com/verify', {})
+ self.assertEqual(content['status'], '204')
+ self.assertEqual(anne.verified_on, verified_on)
+
+ def test_unverify_already_unverified(self):
+ # It's okay to unverify an already unverified; it just doesn't change
+ # the value.
+ with transaction():
+ anne = getUtility(IUserManager).create_address('anne@example.com')
+ self.assertEqual(anne.verified_on, None)
+ response, content = call_api(
+ 'http://localhost:9001/3.0/addresses/anne@example.com/unverify', {})
+ self.assertEqual(content['status'], '204')
+ self.assertEqual(anne.verified_on, None)
+
+ def test_verify_bad_request(self):
+ # Too many segments after /verify.
+ with transaction():
+ anne = getUtility(IUserManager).create_address('anne@example.com')
+ self.assertEqual(anne.verified_on, None)
+ try:
+ call_api('http://localhost:9001/3.0/addresses/'
+ 'anne@example.com/verify/foo', {})
+ except HTTPError as exc:
+ self.assertEqual(exc.code, 400)
+ else:
+ raise AssertionError('Expected HTTPError 400')
+
+ def test_unverify_bad_request(self):
+ # Too many segments after /verify.
+ with transaction():
+ anne = getUtility(IUserManager).create_address('anne@example.com')
+ self.assertEqual(anne.verified_on, None)
+ try:
+ call_api('http://localhost:9001/3.0/addresses/'
+ 'anne@example.com/unverify/foo', {})
+ except HTTPError as exc:
+ self.assertEqual(exc.code, 400)
+ else:
+ raise AssertionError('Expected HTTPError 400')