summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBarry Warsaw2014-04-14 23:12:01 -0400
committerBarry Warsaw2014-04-14 23:12:01 -0400
commitee8abcd62038917e0b1baf6f99631f88b464add2 (patch)
treef404d3bc6dbe8a005ed5d2e17a50c483a224f865 /src
parent243b4dd66d1c6bd412ae0ce3770e36aebd3b6a36 (diff)
parent00ea00ffa489501993035bb96734506bbe939ab9 (diff)
downloadmailman-ee8abcd62038917e0b1baf6f99631f88b464add2.tar.gz
mailman-ee8abcd62038917e0b1baf6f99631f88b464add2.tar.zst
mailman-ee8abcd62038917e0b1baf6f99631f88b464add2.zip
Diffstat (limited to 'src')
-rw-r--r--src/mailman/docs/NEWS.rst2
-rw-r--r--src/mailman/model/address.py5
-rw-r--r--src/mailman/model/tests/test_address.py43
-rw-r--r--src/mailman/rest/addresses.py31
-rw-r--r--src/mailman/rest/docs/addresses.rst53
-rw-r--r--src/mailman/rest/tests/test_addresses.py83
6 files changed, 215 insertions, 2 deletions
diff --git a/src/mailman/docs/NEWS.rst b/src/mailman/docs/NEWS.rst
index 9abab04b6..90dc8852a 100644
--- a/src/mailman/docs/NEWS.rst
+++ b/src/mailman/docs/NEWS.rst
@@ -30,6 +30,8 @@ REST
[Sneha Priscilla.]
* Mailing lists can now individually enable or disable any archiver available
site-wide. [Joanna Skrzeszewska] (LP: #1158040)
+ * Addresses can be added to existing users, including display names, via the
+ REST API. [Florian Fuchs]
Commands
--------
diff --git a/src/mailman/model/address.py b/src/mailman/model/address.py
index f5e7ddbfd..f69679210 100644
--- a/src/mailman/model/address.py
+++ b/src/mailman/model/address.py
@@ -27,11 +27,13 @@ __all__ = [
from email.utils import formataddr
from storm.locals import DateTime, Int, Reference, Unicode
+from zope.component import getUtility
from zope.event import notify
from zope.interface import implementer
from mailman.database.model import Model
-from mailman.interfaces.address import AddressVerificationEvent, IAddress
+from mailman.interfaces.address import (
+ AddressVerificationEvent, IAddress, IEmailValidator)
from mailman.utilities.datetime import now
@@ -54,6 +56,7 @@ class Address(Model):
def __init__(self, email, display_name):
super(Address, self).__init__()
+ getUtility(IEmailValidator).validate(email)
lower_case = email.lower()
self.email = lower_case
self.display_name = display_name
diff --git a/src/mailman/model/tests/test_address.py b/src/mailman/model/tests/test_address.py
new file mode 100644
index 000000000..130ec3bae
--- /dev/null
+++ b/src/mailman/model/tests/test_address.py
@@ -0,0 +1,43 @@
+# Copyright (C) 2011-2014 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
+
+"""Test addresses."""
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+ 'TestAddress',
+ ]
+
+
+import unittest
+
+from mailman.email.validate import InvalidEmailAddressError
+from mailman.model.address import Address
+from mailman.testing.layers import ConfigLayer
+
+
+
+class TestAddress(unittest.TestCase):
+ """Test addresses."""
+
+ layer = ConfigLayer
+
+ def test_invalid_email_string_raises_exception(self):
+ with self.assertRaises(InvalidEmailAddressError):
+ Address('not_a_valid_email_string', '')
diff --git a/src/mailman/rest/addresses.py b/src/mailman/rest/addresses.py
index 51bbe2046..cb8861a2e 100644
--- a/src/mailman/rest/addresses.py
+++ b/src/mailman/rest/addresses.py
@@ -31,10 +31,13 @@ from operator import attrgetter
from restish import http, resource
from zope.component import getUtility
+from mailman.interfaces.address import (
+ ExistingAddressError, InvalidEmailAddressError)
+from mailman.interfaces.usermanager import IUserManager
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.rest.validator import Validator
from mailman.utilities.datetime import now
@@ -174,6 +177,32 @@ class UserAddresses(_AddressBase):
resource = self._make_collection(request)
return http.ok([], etag(resource))
+ @resource.POST()
+ def create(self, request):
+ """POST to /addresses
+
+ Add a new address to the user record.
+ """
+ if self._user is None:
+ return http.not_found()
+ user_manager = getUtility(IUserManager)
+ validator = Validator(email=unicode,
+ display_name=unicode,
+ _optional=('display_name',))
+ try:
+ address = user_manager.create_address(**validator(request))
+ except ValueError as error:
+ return http.bad_request([], str(error))
+ except InvalidEmailAddressError:
+ return http.bad_request([], b'Invalid email address')
+ except ExistingAddressError:
+ return http.bad_request([], b'Address already exists')
+ else:
+ # Link the address to the current user and return it.
+ address.user = self._user
+ location = path_to('addresses/{0}'.format(address.email))
+ return http.created(location, [], None)
+
def membership_key(member):
diff --git a/src/mailman/rest/docs/addresses.rst b/src/mailman/rest/docs/addresses.rst
index f05b6b9b2..be01dd623 100644
--- a/src/mailman/rest/docs/addresses.rst
+++ b/src/mailman/rest/docs/addresses.rst
@@ -173,6 +173,59 @@ addresses live in the /addresses namespace.
registered_on: 2005-08-01T07:49:23
self_link: http://localhost:9001/3.0/addresses/dave@example.com
+A user can be associated with multiple email addresses. You can add new
+addresses to an existing user.
+
+ >>> dump_json(
+ ... 'http://localhost:9001/3.0/users/dave@example.com/addresses', {
+ ... 'email': 'dave.person@example.org'
+ ... })
+ content-length: 0
+ date: ...
+ location: http://localhost:9001/3.0/addresses/dave.person@example.org
+ server: ...
+ status: 201
+
+When you add the new address, you can give it an optional display name.
+
+ >>> dump_json(
+ ... 'http://localhost:9001/3.0/users/dave@example.com/addresses', {
+ ... 'email': 'dp@example.org',
+ ... 'display_name': 'Davie P',
+ ... })
+ content-length: 0
+ date: ...
+ location: http://localhost:9001/3.0/addresses/dp@example.org
+ server: ...
+ status: 201
+
+The user controls these new addresses.
+
+ >>> dump_json('http://localhost:9001/3.0/users/dave@example.com/addresses')
+ entry 0:
+ email: dave.person@example.org
+ http_etag: "..."
+ original_email: dave.person@example.org
+ registered_on: 2005-08-01T07:49:23
+ self_link: http://localhost:9001/3.0/addresses/dave.person@example.org
+ entry 1:
+ display_name: Dave Person
+ email: dave@example.com
+ http_etag: "..."
+ original_email: dave@example.com
+ registered_on: 2005-08-01T07:49:23
+ self_link: http://localhost:9001/3.0/addresses/dave@example.com
+ entry 2:
+ display_name: Davie P
+ email: dp@example.org
+ http_etag: "..."
+ original_email: dp@example.org
+ registered_on: 2005-08-01T07:49:23
+ self_link: http://localhost:9001/3.0/addresses/dp@example.org
+ http_etag: "..."
+ start: 0
+ total_size: 3
+
Memberships
===========
diff --git a/src/mailman/rest/tests/test_addresses.py b/src/mailman/rest/tests/test_addresses.py
index 9d9e44c22..198674b22 100644
--- a/src/mailman/rest/tests/test_addresses.py
+++ b/src/mailman/rest/tests/test_addresses.py
@@ -109,3 +109,86 @@ class TestAddresses(unittest.TestCase):
call_api('http://localhost:9001/3.0/addresses/'
'anne@example.com/unverify/foo', {})
self.assertEqual(cm.exception.code, 400)
+
+ def test_address_added_to_user(self):
+ # Address is added to a user record.
+ user_manager = getUtility(IUserManager)
+ with transaction():
+ anne = user_manager.create_user('anne@example.com')
+ response, content = call_api(
+ 'http://localhost:9001/3.0/users/anne@example.com/addresses', {
+ 'email': 'anne.person@example.org',
+ })
+ self.assertIn('anne.person@example.org',
+ [addr.email for addr in anne.addresses])
+ self.assertEqual(content['status'], '201')
+ self.assertEqual(
+ content['location'],
+ 'http://localhost:9001/3.0/addresses/anne.person@example.org')
+ # The address has no display name.
+ anne_person = user_manager.get_address('anne.person@example.org')
+ self.assertEqual(anne_person.display_name, '')
+
+ def test_address_and_display_name_added_to_user(self):
+ # Address with a display name is added to the user record.
+ user_manager = getUtility(IUserManager)
+ with transaction():
+ anne = user_manager.create_user('anne@example.com')
+ response, content = call_api(
+ 'http://localhost:9001/3.0/users/anne@example.com/addresses', {
+ 'email': 'anne.person@example.org',
+ 'display_name': 'Ann E Person',
+ })
+ self.assertIn('anne.person@example.org',
+ [addr.email for addr in anne.addresses])
+ self.assertEqual(content['status'], '201')
+ self.assertEqual(
+ content['location'],
+ 'http://localhost:9001/3.0/addresses/anne.person@example.org')
+ # The address has no display name.
+ anne_person = user_manager.get_address('anne.person@example.org')
+ self.assertEqual(anne_person.display_name, 'Ann E Person')
+
+ def test_existing_address_bad_request(self):
+ # Trying to add an existing address returns 400.
+ with transaction():
+ getUtility(IUserManager).create_user('anne@example.com')
+ with self.assertRaises(HTTPError) as cm:
+ call_api(
+ 'http://localhost:9001/3.0/users/anne@example.com/addresses', {
+ 'email': 'anne@example.com',
+ })
+ self.assertEqual(cm.exception.code, 400)
+ self.assertEqual(cm.exception.reason, 'Address already exists')
+
+ def test_invalid_address_bad_request(self):
+ # Trying to add an invalid address string returns 400.
+ with transaction():
+ getUtility(IUserManager).create_user('anne@example.com')
+ with self.assertRaises(HTTPError) as cm:
+ call_api(
+ 'http://localhost:9001/3.0/users/anne@example.com/addresses', {
+ 'email': 'invalid_address_string'
+ })
+ self.assertEqual(cm.exception.code, 400)
+ self.assertEqual(cm.exception.reason, 'Invalid email address')
+
+ def test_empty_address_bad_request(self):
+ # The address is required.
+ with transaction():
+ getUtility(IUserManager).create_user('anne@example.com')
+ with self.assertRaises(HTTPError) as cm:
+ call_api(
+ 'http://localhost:9001/3.0/users/anne@example.com/addresses',
+ {})
+ self.assertEqual(cm.exception.code, 400)
+ self.assertEqual(cm.exception.reason, 'Missing parameters: email')
+
+ def test_add_address_to_missing_user(self):
+ # The user that the address is being added to must exist.
+ with self.assertRaises(HTTPError) as cm:
+ call_api(
+ 'http://localhost:9001/3.0/users/anne@example.com/addresses', {
+ 'email': 'anne.person@example.org',
+ })
+ self.assertEqual(cm.exception.code, 404)