summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBarry Warsaw2015-04-06 16:07:04 -0400
committerBarry Warsaw2015-04-06 16:07:04 -0400
commitf17be16aeac385854ca9693626027dbd553c2944 (patch)
tree1eb6be6826b99159eb7325895347aca8ef8a44b1
parentb0a841d634ad5a44b44af17548636e17c38541f1 (diff)
parent17fa7ac10ddd6ca0916cdcdd3a5e8c1414e9bcbc (diff)
downloadmailman-f17be16aeac385854ca9693626027dbd553c2944.tar.gz
mailman-f17be16aeac385854ca9693626027dbd553c2944.tar.zst
mailman-f17be16aeac385854ca9693626027dbd553c2944.zip
-rw-r--r--src/mailman/app/registrar.py2
-rw-r--r--src/mailman/commands/docs/create.rst3
-rw-r--r--src/mailman/commands/docs/membership.rst2
-rw-r--r--src/mailman/commands/tests/test_lists.py2
-rw-r--r--src/mailman/database/alembic/versions/46e92facee7_add_serverowner_domainowner.py36
-rw-r--r--src/mailman/interfaces/domain.py13
-rw-r--r--src/mailman/model/docs/domains.rst49
-rw-r--r--src/mailman/model/docs/registration.rst2
-rw-r--r--src/mailman/model/domain.py51
-rw-r--r--src/mailman/model/tests/test_domain.py24
-rw-r--r--src/mailman/model/user.py12
-rw-r--r--src/mailman/rest/docs/addresses.rst1
-rw-r--r--src/mailman/rest/docs/domains.rst33
-rw-r--r--src/mailman/rest/docs/users.rst17
-rw-r--r--src/mailman/rest/domains.py25
-rw-r--r--src/mailman/rest/lists.py2
-rw-r--r--src/mailman/rest/tests/test_domains.py12
-rw-r--r--src/mailman/rest/users.py53
-rw-r--r--src/mailman/testing/layers.py2
19 files changed, 249 insertions, 92 deletions
diff --git a/src/mailman/app/registrar.py b/src/mailman/app/registrar.py
index 2544e233c..9272b95d7 100644
--- a/src/mailman/app/registrar.py
+++ b/src/mailman/app/registrar.py
@@ -162,7 +162,7 @@ def handle_ConfirmationNeededEvent(event):
confirm_url = mlist.domain.confirm_url(event.token)
email_address = event.pendable['email']
domain_name = mlist.domain.mail_host
- contact_address = mlist.domain.contact_address
+ contact_address = mlist.owner_address
# Send a verification email to the address.
template = getUtility(ITemplateLoader).get(
'mailman:///{0}/{1}/confirm.txt'.format(
diff --git a/src/mailman/commands/docs/create.rst b/src/mailman/commands/docs/create.rst
index bec4ea8b6..1a5d2a3ab 100644
--- a/src/mailman/commands/docs/create.rst
+++ b/src/mailman/commands/docs/create.rst
@@ -44,8 +44,7 @@ Now both the domain and the mailing list exist in the database.
>>> from mailman.interfaces.domain import IDomainManager
>>> getUtility(IDomainManager).get('example.xx')
- <Domain example.xx, base_url: http://example.xx,
- contact_address: postmaster@example.xx>
+ <Domain example.xx, base_url: http://example.xx>
You can also create mailing lists in existing domains without the
auto-creation flag.
diff --git a/src/mailman/commands/docs/membership.rst b/src/mailman/commands/docs/membership.rst
index a260e930a..bdebba8f7 100644
--- a/src/mailman/commands/docs/membership.rst
+++ b/src/mailman/commands/docs/membership.rst
@@ -127,7 +127,7 @@ Mailman has sent her the confirmation message.
message. If you think you are being maliciously subscribed to the list, or
have any other questions, you may contact
<BLANKLINE>
- postmaster@example.com
+ alpha-owner@example.com
<BLANKLINE>
Once Anne confirms her registration, she will be made a member of the mailing
diff --git a/src/mailman/commands/tests/test_lists.py b/src/mailman/commands/tests/test_lists.py
index dad15eec8..229e7c96d 100644
--- a/src/mailman/commands/tests/test_lists.py
+++ b/src/mailman/commands/tests/test_lists.py
@@ -48,7 +48,7 @@ class TestLists(unittest.TestCase):
# LP: #1166911 - non-matching lists were returned.
getUtility(IDomainManager).add(
'example.net', 'An example domain.',
- 'http://lists.example.net', 'postmaster@example.net')
+ 'http://lists.example.net')
create_list('test1@example.com')
create_list('test2@example.com')
# Only this one should show up.
diff --git a/src/mailman/database/alembic/versions/46e92facee7_add_serverowner_domainowner.py b/src/mailman/database/alembic/versions/46e92facee7_add_serverowner_domainowner.py
new file mode 100644
index 000000000..e4dc25ab8
--- /dev/null
+++ b/src/mailman/database/alembic/versions/46e92facee7_add_serverowner_domainowner.py
@@ -0,0 +1,36 @@
+"""add_serverowner_domainowner
+
+Revision ID: 46e92facee7
+Revises: 33e1f5f6fa8
+Create Date: 2015-03-20 16:01:25.007242
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '46e92facee7'
+down_revision = '33e1f5f6fa8'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+ op.create_table('domain_owner',
+ sa.Column('user_id', sa.Integer(), nullable=False),
+ sa.Column('domain_id', sa.Integer(), nullable=False),
+ sa.ForeignKeyConstraint(['domain_id'], ['domain.id'], ),
+ sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
+ sa.PrimaryKeyConstraint('user_id', 'domain_id')
+ )
+ op.add_column('user', sa.Column('is_server_owner', sa.Boolean(),
+ nullable=True))
+ if op.get_bind().dialect.name != 'sqlite':
+ op.drop_column('domain', 'contact_address')
+
+
+def downgrade():
+ if op.get_bind().dialect.name != 'sqlite':
+ op.drop_column('user', 'is_server_owner')
+ op.add_column('domain', sa.Column('contact_address', sa.VARCHAR(),
+ nullable=True))
+ op.drop_table('domain_owner')
diff --git a/src/mailman/interfaces/domain.py b/src/mailman/interfaces/domain.py
index 3ebfb0d6e..ee5eafc78 100644
--- a/src/mailman/interfaces/domain.py
+++ b/src/mailman/interfaces/domain.py
@@ -88,9 +88,8 @@ class IDomain(Interface):
description = Attribute(
'The human readable description of the domain name.')
- contact_address = Attribute("""\
- The contact address for the human at this domain.
- E.g. postmaster@example.com""")
+ owners = Attribute("""\
+ The relationship with the user database representing domain owners""")
mailing_lists = Attribute(
"""All mailing lists for this domain.
@@ -112,7 +111,7 @@ class IDomain(Interface):
class IDomainManager(Interface):
"""The manager of domains."""
- def add(mail_host, description=None, base_url=None, contact_address=None):
+ def add(mail_host, description=None, base_url=None, owner_id=None):
"""Add a new domain.
:param mail_host: The email host name for the domain.
@@ -123,10 +122,8 @@ class IDomainManager(Interface):
interface of the domain. If not given, it defaults to
http://`mail_host`/
:type base_url: string
- :param contact_address: The email contact address for the human
- managing the domain. If not given, defaults to
- postmaster@`mail_host`
- :type contact_address: string
+ :param owners: List of owners of the domain, defaults to None
+ :type owners: list
:return: The new domain object
:rtype: `IDomain`
:raises `BadDomainSpecificationError`: when the `mail_host` is
diff --git a/src/mailman/model/docs/domains.rst b/src/mailman/model/docs/domains.rst
index abb594a62..ded52f817 100644
--- a/src/mailman/model/docs/domains.rst
+++ b/src/mailman/model/docs/domains.rst
@@ -9,6 +9,10 @@ Domains
>>> manager = getUtility(IDomainManager)
>>> manager.remove('example.com')
<Domain example.com...>
+ >>> from mailman.interfaces.usermanager import IUserManager
+ >>> user_manager = getUtility(IUserManager)
+ >>> user = user_manager.create_user('test@example.org')
+ >>> config.db.commit()
Domains are how Mailman interacts with email host names and web host names.
::
@@ -28,17 +32,14 @@ Adding a domain requires some basic information, of which the email host name
is the only required piece. The other parts are inferred from that.
>>> manager.add('example.org')
- <Domain example.org, base_url: http://example.org,
- contact_address: postmaster@example.org>
+ <Domain example.org, base_url: http://example.org>
>>> show_domains()
- <Domain example.org, base_url: http://example.org,
- contact_address: postmaster@example.org>
+ <Domain example.org, base_url: http://example.org>
We can remove domains too.
>>> manager.remove('example.org')
- <Domain example.org, base_url: http://example.org,
- contact_address: postmaster@example.org>
+ <Domain example.org, base_url: http://example.org>
>>> show_domains()
no domains
@@ -46,30 +47,34 @@ Sometimes the email host name is different than the base url for hitting the
web interface for the domain.
>>> manager.add('example.com', base_url='https://mail.example.com')
- <Domain example.com, base_url: https://mail.example.com,
- contact_address: postmaster@example.com>
+ <Domain example.com, base_url: https://mail.example.com>
>>> show_domains()
- <Domain example.com, base_url: https://mail.example.com,
- contact_address: postmaster@example.com>
+ <Domain example.com, base_url: https://mail.example.com>
-Domains can have explicit descriptions and contact addresses.
+Domains can have explicit descriptions.
::
>>> manager.add(
... 'example.net',
... base_url='http://lists.example.net',
- ... contact_address='postmaster@example.com',
- ... description='The example domain')
+ ... description='The example domain',
+ ... owners=['user@domain.com'])
<Domain example.net, The example domain,
- base_url: http://lists.example.net,
- contact_address: postmaster@example.com>
+ base_url: http://lists.example.net>
>>> show_domains()
- <Domain example.com, base_url: https://mail.example.com,
- contact_address: postmaster@example.com>
+ <Domain example.com, base_url: https://mail.example.com>
<Domain example.net, The example domain,
- base_url: http://lists.example.net,
- contact_address: postmaster@example.com>
+ base_url: http://lists.example.net>
+
+Domains can have multiple owners, ideally one of the owners should have a
+verified preferred address. However this is not checked right now and
+contact_address from config can be used as a fallback.
+::
+
+ >>> net_domain = manager['example.net']
+ >>> net_domain.add_owner('test@example.org')
+
Domains can list all associated mailing lists with the mailing_lists property.
::
@@ -105,8 +110,7 @@ In the global domain manager, domains are indexed by their email host name.
>>> print(manager['example.net'])
<Domain example.net, The example domain,
- base_url: http://lists.example.net,
- contact_address: postmaster@example.com>
+ base_url: http://lists.example.net>
As with dictionaries, you can also get the domain. If the domain does not
exist, ``None`` or a default is returned.
@@ -114,8 +118,7 @@ exist, ``None`` or a default is returned.
>>> print(manager.get('example.net'))
<Domain example.net, The example domain,
- base_url: http://lists.example.net,
- contact_address: postmaster@example.com>
+ base_url: http://lists.example.net>
>>> print(manager.get('doesnotexist.com'))
None
diff --git a/src/mailman/model/docs/registration.rst b/src/mailman/model/docs/registration.rst
index 47f9f951d..2d2aa8ec7 100644
--- a/src/mailman/model/docs/registration.rst
+++ b/src/mailman/model/docs/registration.rst
@@ -120,7 +120,7 @@ message is sent to the user in order to verify the registered address.
message. If you think you are being maliciously subscribed to the list,
or have any other questions, you may contact
<BLANKLINE>
- postmaster@example.com
+ alpha-owner@example.com
<BLANKLINE>
>>> dump_msgdata(items[0].msgdata)
_parsemsg : False
diff --git a/src/mailman/model/domain.py b/src/mailman/model/domain.py
index 9e627c119..32ea7db9b 100644
--- a/src/mailman/model/domain.py
+++ b/src/mailman/model/domain.py
@@ -28,11 +28,15 @@ from mailman.database.transaction import dbconnection
from mailman.interfaces.domain import (
BadDomainSpecificationError, DomainCreatedEvent, DomainCreatingEvent,
DomainDeletedEvent, DomainDeletingEvent, IDomain, IDomainManager)
+from mailman.interfaces.usermanager import IUserManager
from mailman.model.mailinglist import MailingList
+from mailman.model.user import User, DomainOwner
from urllib.parse import urljoin, urlparse
from sqlalchemy import Column, Integer, Unicode
+from sqlalchemy.orm import relationship, backref
from zope.event import notify
from zope.interface import implementer
+from zope.component import getUtility
@@ -44,15 +48,17 @@ class Domain(Model):
id = Column(Integer, primary_key=True)
- mail_host = Column(Unicode) # TODO: add index?
+ mail_host = Column(Unicode)
base_url = Column(Unicode)
description = Column(Unicode)
- contact_address = Column(Unicode)
+ owners = relationship("User",
+ secondary="domain_owner",
+ backref="domains")
def __init__(self, mail_host,
description=None,
base_url=None,
- contact_address=None):
+ owners=[]):
"""Create and register a domain.
:param mail_host: The host name for the email interface.
@@ -63,18 +69,16 @@ class Domain(Model):
scheme. If not given, it will be constructed from the
`mail_host` using the http protocol.
:type base_url: string
- :param contact_address: The email address to contact a human for this
- domain. If not given, postmaster@`mail_host` will be used.
- :type contact_address: string
+ :param owners: List of `User` who are the owners of this domain
+ :type owners: list
"""
self.mail_host = mail_host
self.base_url = (base_url
if base_url is not None
else 'http://' + mail_host)
self.description = description
- self.contact_address = (contact_address
- if contact_address is not None
- else 'postmaster@' + mail_host)
+ if len(owners):
+ self.add_owners(owners)
@property
def url_host(self):
@@ -103,13 +107,29 @@ class Domain(Model):
def __repr__(self):
"""repr(a_domain)"""
if self.description is None:
- return ('<Domain {0.mail_host}, base_url: {0.base_url}, '
- 'contact_address: {0.contact_address}>').format(self)
+ return ('<Domain {0.mail_host}, base_url: {0.base_url}>').format(self)
else:
return ('<Domain {0.mail_host}, {0.description}, '
- 'base_url: {0.base_url}, '
- 'contact_address: {0.contact_address}>').format(self)
+ 'base_url: {0.base_url}>').format(self)
+ def add_owner(self, owner):
+ """Add a domain owner"""
+ user_manager = getUtility(IUserManager)
+ user = user_manager.get_user(owner)
+ if user is None:
+ user = user_manager.create_user(owner)
+ self.owners.append(user)
+
+ def add_owners(self, owners):
+ """Add multiple owners"""
+ assert(isinstance(owners, list))
+ for owner in owners:
+ self.add_owner(owner)
+
+ def remove_owner(self, owner):
+ """ Remove a domain owner"""
+ user_manager = getUtility(IUserManager)
+ self.owners.remove(user_manager.get_user(owner))
@implementer(IDomainManager)
@@ -121,15 +141,16 @@ class DomainManager:
mail_host,
description=None,
base_url=None,
- contact_address=None):
+ owners=[]):
"""See `IDomainManager`."""
# Be sure the mail_host is not already registered. This is probably
# a constraint that should (also) be maintained in the database.
if self.get(mail_host) is not None:
raise BadDomainSpecificationError(
'Duplicate email host: %s' % mail_host)
+
notify(DomainCreatingEvent(mail_host))
- domain = Domain(mail_host, description, base_url, contact_address)
+ domain = Domain(mail_host, description, base_url, owners)
store.add(domain)
notify(DomainCreatedEvent(domain))
return domain
diff --git a/src/mailman/model/tests/test_domain.py b/src/mailman/model/tests/test_domain.py
index b4a6dd75c..8223aa00b 100644
--- a/src/mailman/model/tests/test_domain.py
+++ b/src/mailman/model/tests/test_domain.py
@@ -26,9 +26,11 @@ __all__ = [
import unittest
from mailman.app.lifecycle import create_list
+from mailman.config import config
from mailman.interfaces.domain import (
DomainCreatedEvent, DomainCreatingEvent, DomainDeletedEvent,
- DomainDeletingEvent, IDomainManager)
+ DomainDeletingEvent, IDomainManager, BadDomainSpecificationError)
+from mailman.interfaces.usermanager import IUserManager
from mailman.interfaces.listmanager import IListManager
from mailman.testing.helpers import event_subscribers
from mailman.testing.layers import ConfigLayer
@@ -78,6 +80,26 @@ class TestDomainManager(unittest.TestCase):
# Trying to delete a missing domain gives you a KeyError.
self.assertRaises(KeyError, self._manager.remove, 'doesnotexist.com')
+ def test_domain_create_with_owner(self):
+ domain = self._manager.add('example.org',
+ owners=['someuser@example.org'])
+ self.assertEqual(len(domain.owners), 1)
+ self.assertEqual(domain.owners[0].addresses[0].email,
+ 'someuser@example.org')
+
+ def test_add_domain_owner(self):
+ domain = self._manager.add('example.org')
+ domain.add_owner('someuser@example.org')
+ self.assertEqual(len(domain.owners), 1)
+ self.assertEqual(domain.owners[0].addresses[0].email,
+ 'someuser@example.org')
+
+ def test_remove_domain_owner(self):
+ domain = self._manager.add('example.org',
+ owners=['someuser@example.org'])
+ domain.remove_owner('someuser@example.org')
+ self.assertEqual(len(domain.owners), 0)
+
class TestDomainLifecycleEvents(unittest.TestCase):
diff --git a/src/mailman/model/user.py b/src/mailman/model/user.py
index 66197d72e..5fecc1836 100644
--- a/src/mailman/model/user.py
+++ b/src/mailman/model/user.py
@@ -19,6 +19,7 @@
__all__ = [
'User',
+ 'DomainOwner'
]
@@ -34,7 +35,7 @@ from mailman.model.preferences import Preferences
from mailman.model.roster import Memberships
from mailman.utilities.datetime import factory as date_factory
from mailman.utilities.uid import UniqueIDFactory
-from sqlalchemy import Column, DateTime, ForeignKey, Integer, Unicode
+from sqlalchemy import Column, DateTime, ForeignKey, Integer, Unicode, Boolean
from sqlalchemy.orm import relationship, backref
from zope.event import notify
from zope.interface import implementer
@@ -55,6 +56,7 @@ class User(Model):
_password = Column('password', Unicode)
_user_id = Column(UUID, index=True)
_created_on = Column(DateTime)
+ is_server_owner = Column(Boolean, default=False)
addresses = relationship(
'Address', backref='user',
@@ -176,3 +178,11 @@ class User(Model):
@property
def memberships(self):
return Memberships(self)
+
+
+class DomainOwner(Model):
+ """Domain to owners(user) association class"""
+
+ __tablename__ = 'domain_owner'
+ user_id = Column(Integer, ForeignKey('user.id'), primary_key=True)
+ domain_id = Column(Integer, ForeignKey('domain.id'), primary_key=True)
diff --git a/src/mailman/rest/docs/addresses.rst b/src/mailman/rest/docs/addresses.rst
index fd3520be9..f70b64a39 100644
--- a/src/mailman/rest/docs/addresses.rst
+++ b/src/mailman/rest/docs/addresses.rst
@@ -190,6 +190,7 @@ representation:
created_on: 2005-08-01T07:49:23
display_name: Cris X. Person
http_etag: "..."
+ is_server_owner: False
password: ...
self_link: http://localhost:9001/3.0/users/1
user_id: 1
diff --git a/src/mailman/rest/docs/domains.rst b/src/mailman/rest/docs/domains.rst
index a78dacd85..16204548c 100644
--- a/src/mailman/rest/docs/domains.rst
+++ b/src/mailman/rest/docs/domains.rst
@@ -29,14 +29,12 @@ Once a domain is added, it is accessible through the API.
>>> domain_manager.add(
... 'example.com', 'An example domain', 'http://lists.example.com')
<Domain example.com, An example domain,
- base_url: http://lists.example.com,
- contact_address: postmaster@example.com>
+ base_url: http://lists.example.com>
>>> transaction.commit()
>>> dump_json('http://localhost:9001/3.0/domains')
entry 0:
base_url: http://lists.example.com
- contact_address: postmaster@example.com
description: An example domain
http_etag: "..."
mail_host: example.com
@@ -51,24 +49,19 @@ At the top level, all domains are returned as separate entries.
>>> domain_manager.add(
... 'example.org',
- ... base_url='http://mail.example.org',
- ... contact_address='listmaster@example.org')
- <Domain example.org, base_url: http://mail.example.org,
- contact_address: listmaster@example.org>
+ ... base_url='http://mail.example.org')
+ <Domain example.org, base_url: http://mail.example.org>
>>> domain_manager.add(
... 'lists.example.net',
... 'Porkmasters',
- ... 'http://example.net',
- ... 'porkmaster@example.net')
+ ... 'http://example.net')
<Domain lists.example.net, Porkmasters,
- base_url: http://example.net,
- contact_address: porkmaster@example.net>
+ base_url: http://example.net>
>>> transaction.commit()
>>> dump_json('http://localhost:9001/3.0/domains')
entry 0:
base_url: http://lists.example.com
- contact_address: postmaster@example.com
description: An example domain
http_etag: "..."
mail_host: example.com
@@ -76,7 +69,6 @@ At the top level, all domains are returned as separate entries.
url_host: lists.example.com
entry 1:
base_url: http://mail.example.org
- contact_address: listmaster@example.org
description: None
http_etag: "..."
mail_host: example.org
@@ -84,7 +76,6 @@ At the top level, all domains are returned as separate entries.
url_host: mail.example.org
entry 2:
base_url: http://example.net
- contact_address: porkmaster@example.net
description: Porkmasters
http_etag: "..."
mail_host: lists.example.net
@@ -103,7 +94,6 @@ The information for a single domain is available by following one of the
>>> dump_json('http://localhost:9001/3.0/domains/lists.example.net')
base_url: http://example.net
- contact_address: porkmaster@example.net
description: Porkmasters
http_etag: "..."
mail_host: lists.example.net
@@ -165,7 +155,6 @@ Now the web service knows about our new domain.
>>> dump_json('http://localhost:9001/3.0/domains/lists.example.com')
base_url: http://lists.example.com
- contact_address: postmaster@lists.example.com
description: None
http_etag: "..."
mail_host: lists.example.com
@@ -176,9 +165,7 @@ And the new domain is in our database.
::
>>> domain_manager['lists.example.com']
- <Domain lists.example.com,
- base_url: http://lists.example.com,
- contact_address: postmaster@lists.example.com>
+ <Domain lists.example.com, base_url: http://lists.example.com>
# Unlock the database.
>>> transaction.abort()
@@ -190,8 +177,7 @@ address.
>>> dump_json('http://localhost:9001/3.0/domains', {
... 'mail_host': 'my.example.com',
... 'description': 'My new domain',
- ... 'base_url': 'http://allmy.example.com',
- ... 'contact_address': 'helpme@example.com'
+ ... 'base_url': 'http://allmy.example.com'
... })
content-length: 0
date: ...
@@ -200,7 +186,6 @@ address.
>>> dump_json('http://localhost:9001/3.0/domains/my.example.com')
base_url: http://allmy.example.com
- contact_address: helpme@example.com
description: My new domain
http_etag: "..."
mail_host: my.example.com
@@ -208,9 +193,7 @@ address.
url_host: allmy.example.com
>>> domain_manager['my.example.com']
- <Domain my.example.com, My new domain,
- base_url: http://allmy.example.com,
- contact_address: helpme@example.com>
+ <Domain my.example.com, My new domain, base_url: http://allmy.example.com>
# Unlock the database.
>>> transaction.abort()
diff --git a/src/mailman/rest/docs/users.rst b/src/mailman/rest/docs/users.rst
index 824492333..343598518 100644
--- a/src/mailman/rest/docs/users.rst
+++ b/src/mailman/rest/docs/users.rst
@@ -34,6 +34,7 @@ Anne's user record is returned as an entry into the collection of all users.
created_on: 2005-08-01T07:49:23
display_name: Anne Person
http_etag: "..."
+ is_server_owner: False
self_link: http://localhost:9001/3.0/users/1
user_id: 1
http_etag: "..."
@@ -50,11 +51,13 @@ returned in the REST API.
created_on: 2005-08-01T07:49:23
display_name: Anne Person
http_etag: "..."
+ is_server_owner: False
self_link: http://localhost:9001/3.0/users/1
user_id: 1
entry 1:
created_on: 2005-08-01T07:49:23
http_etag: "..."
+ is_server_owner: False
self_link: http://localhost:9001/3.0/users/2
user_id: 2
http_etag: "..."
@@ -76,6 +79,7 @@ page.
created_on: 2005-08-01T07:49:23
display_name: Anne Person
http_etag: "..."
+ is_server_owner: False
self_link: http://localhost:9001/3.0/users/1
user_id: 1
http_etag: "..."
@@ -86,6 +90,7 @@ page.
entry 0:
created_on: 2005-08-01T07:49:23
http_etag: "..."
+ is_server_owner: False
self_link: http://localhost:9001/3.0/users/2
user_id: 2
http_etag: "..."
@@ -120,6 +125,7 @@ one was assigned to her.
>>> dump_json('http://localhost:9001/3.0/users/3')
created_on: 2005-08-01T07:49:23
http_etag: "..."
+ is_server_owner: False
password: {plaintext}...
self_link: http://localhost:9001/3.0/users/3
user_id: 3
@@ -131,6 +137,7 @@ address.
>>> dump_json('http://localhost:9001/3.0/users/cris@example.com')
created_on: 2005-08-01T07:49:23
http_etag: "..."
+ is_server_owner: False
password: {plaintext}...
self_link: http://localhost:9001/3.0/users/3
user_id: 3
@@ -158,6 +165,7 @@ Dave's user record includes his display name.
created_on: 2005-08-01T07:49:23
display_name: Dave Person
http_etag: "..."
+ is_server_owner: False
password: {plaintext}...
self_link: http://localhost:9001/3.0/users/4
user_id: 4
@@ -190,6 +198,7 @@ because it has the hash algorithm prefix (i.e. the *{plaintext}* marker).
created_on: 2005-08-01T07:49:23
display_name: Elly Person
http_etag: "..."
+ is_server_owner: False
password: {plaintext}supersekrit
self_link: http://localhost:9001/3.0/users/5
user_id: 5
@@ -214,6 +223,7 @@ Dave's display name has been updated.
created_on: 2005-08-01T07:49:23
display_name: David Person
http_etag: "..."
+ is_server_owner: False
password: {plaintext}...
self_link: http://localhost:9001/3.0/users/4
user_id: 4
@@ -238,6 +248,7 @@ addition of the algorithm prefix.
created_on: 2005-08-01T07:49:23
display_name: David Person
http_etag: "..."
+ is_server_owner: False
password: {plaintext}clockwork angels
self_link: http://localhost:9001/3.0/users/4
user_id: 4
@@ -260,6 +271,7 @@ Dave's user record has been updated.
created_on: 2005-08-01T07:49:23
display_name: David Personhood
http_etag: "..."
+ is_server_owner: False
password: {plaintext}the garden
self_link: http://localhost:9001/3.0/users/4
user_id: 4
@@ -343,6 +355,7 @@ addresses can be used to look up Fred's user record.
created_on: 2005-08-01T07:49:23
display_name: Fred Person
http_etag: "..."
+ is_server_owner: False
self_link: http://localhost:9001/3.0/users/6
user_id: 6
@@ -350,6 +363,7 @@ addresses can be used to look up Fred's user record.
created_on: 2005-08-01T07:49:23
display_name: Fred Person
http_etag: "..."
+ is_server_owner: False
self_link: http://localhost:9001/3.0/users/6
user_id: 6
@@ -357,6 +371,7 @@ addresses can be used to look up Fred's user record.
created_on: 2005-08-01T07:49:23
display_name: Fred Person
http_etag: "..."
+ is_server_owner: False
self_link: http://localhost:9001/3.0/users/6
user_id: 6
@@ -364,6 +379,7 @@ addresses can be used to look up Fred's user record.
created_on: 2005-08-01T07:49:23
display_name: Fred Person
http_etag: "..."
+ is_server_owner: False
self_link: http://localhost:9001/3.0/users/6
user_id: 6
@@ -382,6 +398,7 @@ password is hashed and getting her user record returns the hashed password.
created_on: 2005-08-01T07:49:23
display_name: Elly Person
http_etag: "..."
+ is_server_owner: False
password: {plaintext}supersekrit
self_link: http://localhost:9001/3.0/users/5
user_id: 5
diff --git a/src/mailman/rest/domains.py b/src/mailman/rest/domains.py
index 345e8327d..41a1c50bd 100644
--- a/src/mailman/rest/domains.py
+++ b/src/mailman/rest/domains.py
@@ -25,10 +25,12 @@ __all__ = [
from mailman.interfaces.domain import (
BadDomainSpecificationError, IDomainManager)
+from mailman.interfaces.usermanager import IUserManager
from mailman.rest.helpers import (
BadRequest, CollectionMixin, NotFound, bad_request, child, created, etag,
no_content, not_found, okay, path_to)
from mailman.rest.lists import ListsForDomain
+from mailman.rest.users import OwnersForDomain
from mailman.rest.validator import Validator
from zope.component import getUtility
@@ -41,7 +43,6 @@ class _DomainBase(CollectionMixin):
"""See `CollectionMixin`."""
return dict(
base_url=domain.base_url,
- contact_address=domain.contact_address,
description=domain.description,
mail_host=domain.mail_host,
self_link=path_to('domains/{0}'.format(domain.mail_host)),
@@ -88,6 +89,17 @@ class ADomain(_DomainBase):
else:
return BadRequest(), []
+ @child()
+ def owners(self, request, segments):
+ """/domains/<domain>/owners"""
+ if len(segments) == 0:
+ domain = getUtility(IDomainManager).get(self._domain)
+ if domain is None:
+ return NotFound()
+ return OwnersForDomain(domain)
+ else:
+ return BadRequest(), []
+
class AllDomains(_DomainBase):
"""The domains."""
@@ -99,12 +111,13 @@ class AllDomains(_DomainBase):
validator = Validator(mail_host=str,
description=str,
base_url=str,
- contact_address=str,
+ owners=list,
_optional=('description', 'base_url',
- 'contact_address'))
- domain = domain_manager.add(**validator(request))
- except BadDomainSpecificationError:
- bad_request(response, b'Domain exists')
+ 'owners'))
+ values = validator(request)
+ domain = domain_manager.add(**values)
+ except BadDomainSpecificationError as error:
+ bad_request(response, str(error))
except ValueError as error:
bad_request(response, str(error))
else:
diff --git a/src/mailman/rest/lists.py b/src/mailman/rest/lists.py
index f6bc27917..641ddec8e 100644
--- a/src/mailman/rest/lists.py
+++ b/src/mailman/rest/lists.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2015 by the Free Software Foundation, Inc.
+ # Copyright (C) 2010-2015 by the Free Software Foundation, Inc.
#
# This file is part of GNU Mailman.
#
diff --git a/src/mailman/rest/tests/test_domains.py b/src/mailman/rest/tests/test_domains.py
index bf53c8e70..13299516c 100644
--- a/src/mailman/rest/tests/test_domains.py
+++ b/src/mailman/rest/tests/test_domains.py
@@ -27,6 +27,7 @@ import unittest
from mailman.app.lifecycle import create_list
from mailman.database.transaction import transaction
from mailman.interfaces.listmanager import IListManager
+from mailman.interfaces.domain import IDomainManager
from mailman.testing.helpers import call_api
from mailman.testing.layers import RESTLayer
from urllib.error import HTTPError
@@ -41,6 +42,17 @@ class TestDomains(unittest.TestCase):
with transaction():
self._mlist = create_list('test@example.com')
+ def test_create_domains(self):
+ """Test Create domain via REST"""
+ data = {'mail_host': 'example.org',
+ 'description': 'Example domain',
+ 'base_url': 'http://example.org',
+ 'owners': ['someone@example.com',
+ 'secondowner@example.com',]}
+ content, response = call_api('http://localhost:9001/3.0/domains',
+ data, method="POST")
+ self.assertEqual(response.status, 201)
+
def test_bogus_endpoint_extension(self):
# /domains/<domain>/lists/<anything> is not a valid endpoint.
with self.assertRaises(HTTPError) as cm:
diff --git a/src/mailman/rest/users.py b/src/mailman/rest/users.py
index a912b6129..b8eaee448 100644
--- a/src/mailman/rest/users.py
+++ b/src/mailman/rest/users.py
@@ -22,6 +22,7 @@ __all__ = [
'AddressUser',
'AllUsers',
'Login',
+ 'OwnersForDomain',
]
@@ -67,8 +68,9 @@ CREATION_FIELDS = dict(
email=str,
display_name=str,
password=str,
- _optional=('display_name', 'password'),
- )
+ is_server_owner=bool,
+ _optional=('display_name', 'password', 'is_server_owner'),
+)
@@ -108,7 +110,8 @@ class _UserBase(CollectionMixin):
user_id=user_id,
created_on=user.created_on,
self_link=path_to('users/{}'.format(user_id)),
- )
+ is_server_owner=user.is_server_owner,
+ )
# Add the password attribute, only if the user has a password. Same
# with the real name. These could be None or the empty string.
if user.password:
@@ -293,7 +296,8 @@ class AddressUser(_UserBase):
del fields['email']
fields['user_id'] = int
fields['auto_create'] = as_boolean
- fields['_optional'] = fields['_optional'] + ('user_id', 'auto_create')
+ fields['_optional'] = fields['_optional'] + ('user_id', 'auto_create',
+ 'is_server_owner')
try:
validator = Validator(**fields)
arguments = validator(request)
@@ -328,7 +332,8 @@ class AddressUser(_UserBase):
# Process post data and check for an existing user.
fields = CREATION_FIELDS.copy()
fields['user_id'] = int
- fields['_optional'] = fields['_optional'] + ('user_id', 'email')
+ fields['_optional'] = fields['_optional'] + ('user_id', 'email',
+ 'is_server_owner')
try:
validator = Validator(**fields)
arguments = validator(request)
@@ -377,3 +382,41 @@ class Login:
no_content(response)
else:
forbidden(response)
+
+class OwnersForDomain(_UserBase):
+ """Owners for a particular domain."""
+
+ def __init__(self, domain):
+ self._domain = domain
+
+ def on_get(self, request, response):
+ """/domains/<domain>/owners"""
+ resource = self._make_collection(request)
+ okay(response, etag(resource))
+
+ def on_post(self, request, response):
+ """POST to /domains/<domain>/owners """
+ validator = Validator(owner=GetterSetter(str))
+ try:
+ values = validator(request)
+ except ValueError as error:
+ bad_request(response, str(error))
+ return
+ self._domain.add_owner(values['owner'])
+ return no_content(response)
+
+ def on_delete(self, request, response):
+ """DELETE to /domains/<domain>/owners"""
+ validator = Validator(owner=GetterSetter(str))
+ try:
+ values = validator(request)
+ except ValueError as error:
+ bad_request(response, str(error))
+ return
+ self._domain.remove_owner(owner)
+ return no_content(response)
+
+ @paginate
+ def _get_collection(self, request):
+ """See `CollectionMixin`."""
+ return list(self._domain.owners)
diff --git a/src/mailman/testing/layers.py b/src/mailman/testing/layers.py
index 8618f39d3..3328efefc 100644
--- a/src/mailman/testing/layers.py
+++ b/src/mailman/testing/layers.py
@@ -200,7 +200,7 @@ class ConfigLayer(MockAndMonkeyLayer):
with transaction():
getUtility(IDomainManager).add(
'example.com', 'An example domain.',
- 'http://lists.example.com', 'postmaster@example.com')
+ 'http://lists.example.com')
@classmethod
def testTearDown(cls):