summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/mailman/database/alembic/versions/46e92facee7_add_serverowner_domainowner.py34
-rw-r--r--src/mailman/model/docs/users.rst15
-rw-r--r--src/mailman/model/user.py8
-rw-r--r--src/mailman/rest/docs/domains.rst76
-rw-r--r--src/mailman/rest/docs/users.rst82
-rw-r--r--src/mailman/rest/domains.py1
-rw-r--r--src/mailman/rest/lists.py2
-rw-r--r--src/mailman/rest/users.py25
8 files changed, 216 insertions, 27 deletions
diff --git a/src/mailman/database/alembic/versions/46e92facee7_add_serverowner_domainowner.py b/src/mailman/database/alembic/versions/46e92facee7_add_serverowner_domainowner.py
index e4dc25ab8..fc489cae5 100644
--- a/src/mailman/database/alembic/versions/46e92facee7_add_serverowner_domainowner.py
+++ b/src/mailman/database/alembic/versions/46e92facee7_add_serverowner_domainowner.py
@@ -1,3 +1,20 @@
+# Copyright (C) 2015 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/>.
+
"""add_serverowner_domainowner
Revision ID: 46e92facee7
@@ -6,7 +23,7 @@ Create Date: 2015-03-20 16:01:25.007242
"""
-# revision identifiers, used by Alembic.
+# Revision identifiers, used by Alembic.
revision = '46e92facee7'
down_revision = '33e1f5f6fa8'
@@ -15,15 +32,17 @@ import sqlalchemy as sa
def upgrade():
- op.create_table('domain_owner',
+ 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))
+ )
+ 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')
@@ -31,6 +50,7 @@ def upgrade():
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.add_column(
+ 'domain',
+ sa.Column('contact_address', sa.VARCHAR(), nullable=True))
op.drop_table('domain_owner')
diff --git a/src/mailman/model/docs/users.rst b/src/mailman/model/docs/users.rst
index 0b926d6a7..0d6a0f368 100644
--- a/src/mailman/model/docs/users.rst
+++ b/src/mailman/model/docs/users.rst
@@ -295,4 +295,19 @@ membership role.
zperson@example.org xtest_2.example.com MemberRole.owner
+Server owners
+=============
+
+Some users are server owners. Zoe is not yet a server owner.
+
+ >>> user_1.is_server_owner
+ False
+
+So, let's make her one.
+
+ >>> user_1.is_server_owner = True
+ >>> user_1.is_server_owner
+ True
+
+
.. _`usermanager.txt`: usermanager.html
diff --git a/src/mailman/model/user.py b/src/mailman/model/user.py
index 5fecc1836..f6aedd132 100644
--- a/src/mailman/model/user.py
+++ b/src/mailman/model/user.py
@@ -18,8 +18,8 @@
"""Model for users."""
__all__ = [
+ 'DomainOwner',
'User',
- 'DomainOwner'
]
@@ -35,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, Boolean
+from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, Unicode
from sqlalchemy.orm import relationship, backref
from zope.event import notify
from zope.interface import implementer
@@ -180,9 +180,11 @@ class User(Model):
return Memberships(self)
+
class DomainOwner(Model):
- """Domain to owners(user) association class"""
+ """Internal table for associating domains to their owners."""
__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/domains.rst b/src/mailman/rest/docs/domains.rst
index 16204548c..f57dee572 100644
--- a/src/mailman/rest/docs/domains.rst
+++ b/src/mailman/rest/docs/domains.rst
@@ -28,8 +28,7 @@ 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>
+ <Domain example.com, An example domain, base_url: http://lists.example.com>
>>> transaction.commit()
>>> dump_json('http://localhost:9001/3.0/domains')
@@ -55,8 +54,7 @@ At the top level, all domains are returned as separate entries.
... 'lists.example.net',
... 'Porkmasters',
... 'http://example.net')
- <Domain lists.example.net, Porkmasters,
- base_url: http://example.net>
+ <Domain lists.example.net, Porkmasters, base_url: http://example.net>
>>> transaction.commit()
>>> dump_json('http://localhost:9001/3.0/domains')
@@ -212,4 +210,74 @@ Domains can also be deleted via the API.
status: 204
+Domain owners
+=============
+
+Domains can have owners. By posting some addresses to the owners resource,
+you can add some domain owners. Currently our domain has no owners:
+
+ >>> dump_json('http://localhost:9001/3.0/domains/my.example.com/owners')
+ http_etag: ...
+ start: 0
+ total_size: 0
+
+Anne and Bart volunteer to be a domain owners.
+::
+
+ >>> dump_json('http://localhost:9001/3.0/domains/my.example.com/owners', {
+ ... 'owner': 'anne@example.com',
+ ... })
+ content-length: 0
+ date: ...
+ server: ...
+ status: 204
+
+ >>> dump_json('http://localhost:9001/3.0/domains/my.example.com/owners', {
+ ... 'owner': 'bart@example.com',
+ ... })
+ content-length: 0
+ date: ...
+ server: ...
+ status: 204
+
+ >>> dump_json('http://localhost:9001/3.0/domains/my.example.com/owners')
+ entry 0:
+ created_on: 2005-08-01T07:49:23
+ 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: ...
+ start: 0
+ total_size: 2
+
+We can delete all the domain owners.
+
+ >>> dump_json('http://localhost:9001/3.0/domains/my.example.com/owners',
+ ... method='DELETE')
+
+ >>> dump_json('http://localhost:9001/3.0/domains/my.example.com/owners')
+ entry 0:
+ created_on: 2005-08-01T07:49:23
+ 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: ...
+ start: 0
+ total_size: 2
+
+
.. _Domains: ../../model/docs/domains.html
diff --git a/src/mailman/rest/docs/users.rst b/src/mailman/rest/docs/users.rst
index 343598518..13390a00f 100644
--- a/src/mailman/rest/docs/users.rst
+++ b/src/mailman/rest/docs/users.rst
@@ -257,8 +257,9 @@ You can change both the display name and the password by PUTing the full
resource.
>>> dump_json('http://localhost:9001/3.0/users/4', {
- ... 'display_name': 'David Personhood',
... 'cleartext_password': 'the garden',
+ ... 'display_name': 'David Personhood',
+ ... 'is_server_owner': False,
... }, method='PUT')
content-length: 0
date: ...
@@ -416,3 +417,82 @@ This time, Elly successfully logs into Mailman.
date: ...
server: ...
status: 204
+
+
+Server owners
+=============
+
+Users can be designated as server owners. Elly is not currently a server
+owner.
+
+ >>> dump_json('http://localhost:9001/3.0/users/5')
+ 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
+
+Let's make her a server owner.
+::
+
+ >>> dump_json('http://localhost:9001/3.0/users/5', {
+ ... 'is_server_owner': True,
+ ... }, method='PATCH')
+ content-length: 0
+ date: ...
+ server: ...
+ status: 204
+
+ >>> dump_json('http://localhost:9001/3.0/users/5')
+ created_on: 2005-08-01T07:49:23
+ display_name: Elly Person
+ http_etag: "..."
+ is_server_owner: True
+ password: {plaintext}supersekrit
+ self_link: http://localhost:9001/3.0/users/5
+ user_id: 5
+
+Elly later retires as server owner.
+::
+
+ >>> dump_json('http://localhost:9001/3.0/users/5', {
+ ... 'is_server_owner': False,
+ ... }, method='PATCH')
+ content-length: 0
+ date: ...
+ server: ...
+ status: 204
+
+ >>> dump_json('http://localhost:9001/3.0/users/5')
+ created_on: 2005-08-01T07:49:23
+ display_name: Elly Person
+ http_etag: "..."
+ is_server_owner: False
+ password: {plaintext}...
+ self_link: http://localhost:9001/3.0/users/5
+ user_id: 5
+
+Gwen, a new users, takes over as a server owner.
+::
+
+ >>> dump_json('http://localhost:9001/3.0/users', {
+ ... 'display_name': 'Gwen Person',
+ ... 'email': 'gwen@example.com',
+ ... 'is_server_owner': True,
+ ... })
+ content-length: 0
+ date: ...
+ location: http://localhost:9001/3.0/users/7
+ server: ...
+ status: 201
+
+ >>> dump_json('http://localhost:9001/3.0/users/7')
+ created_on: 2005-08-01T07:49:23
+ display_name: Gwen Person
+ http_etag: "..."
+ is_server_owner: True
+ password: {plaintext}...
+ self_link: http://localhost:9001/3.0/users/7
+ user_id: 7
diff --git a/src/mailman/rest/domains.py b/src/mailman/rest/domains.py
index 41a1c50bd..dff2f2073 100644
--- a/src/mailman/rest/domains.py
+++ b/src/mailman/rest/domains.py
@@ -25,7 +25,6 @@ __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)
diff --git a/src/mailman/rest/lists.py b/src/mailman/rest/lists.py
index 641ddec8e..f6bc27917 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/users.py b/src/mailman/rest/users.py
index b8eaee448..a38299d00 100644
--- a/src/mailman/rest/users.py
+++ b/src/mailman/rest/users.py
@@ -59,18 +59,19 @@ class PasswordEncrypterGetterSetter(GetterSetter):
ATTRIBUTES = dict(
- display_name=GetterSetter(str),
cleartext_password=PasswordEncrypterGetterSetter(),
+ display_name=GetterSetter(str),
+ is_server_owner=GetterSetter(as_boolean),
)
CREATION_FIELDS = dict(
- email=str,
display_name=str,
- password=str,
+ email=str,
is_server_owner=bool,
+ password=str,
_optional=('display_name', 'password', 'is_server_owner'),
-)
+ )
@@ -80,6 +81,7 @@ def create_user(arguments, response):
# strip that out (if it exists), then create the user, adding the password
# after the fact if successful.
password = arguments.pop('password', None)
+ is_server_owner = arguments.pop('is_server_owner', False)
try:
user = getUtility(IUserManager).create_user(**arguments)
except ExistingAddressError as error:
@@ -90,6 +92,7 @@ def create_user(arguments, response):
# This will have to be reset since it cannot be retrieved.
password = generate(int(config.passwords.password_length))
user.password = config.password_context.encrypt(password)
+ user.is_server_owner = is_server_owner
location = path_to('users/{}'.format(user.user_id.int))
created(response, location)
return user
@@ -107,10 +110,10 @@ class _UserBase(CollectionMixin):
# but we serialize its integer equivalent.
user_id = user.user_id.int
resource = dict(
- user_id=user_id,
created_on=user.created_on,
- self_link=path_to('users/{}'.format(user_id)),
is_server_owner=user.is_server_owner,
+ self_link=path_to('users/{}'.format(user_id)),
+ user_id=user_id,
)
# Add the password attribute, only if the user has a password. Same
# with the real name. These could be None or the empty string.
@@ -296,8 +299,8 @@ class AddressUser(_UserBase):
del fields['email']
fields['user_id'] = int
fields['auto_create'] = as_boolean
- fields['_optional'] = fields['_optional'] + ('user_id', 'auto_create',
- 'is_server_owner')
+ fields['_optional'] = fields['_optional'] + (
+ 'user_id', 'auto_create', 'is_server_owner')
try:
validator = Validator(**fields)
arguments = validator(request)
@@ -332,8 +335,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',
- 'is_server_owner')
+ fields['_optional'] = fields['_optional'] + (
+ 'user_id', 'email', 'is_server_owner')
try:
validator = Validator(**fields)
arguments = validator(request)
@@ -383,6 +386,8 @@ class Login:
else:
forbidden(response)
+
+
class OwnersForDomain(_UserBase):
"""Owners for a particular domain."""