diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/mailman/database/alembic/versions/46e92facee7_add_serverowner_domainowner.py | 34 | ||||
| -rw-r--r-- | src/mailman/model/docs/users.rst | 15 | ||||
| -rw-r--r-- | src/mailman/model/user.py | 8 | ||||
| -rw-r--r-- | src/mailman/rest/docs/domains.rst | 76 | ||||
| -rw-r--r-- | src/mailman/rest/docs/users.rst | 82 | ||||
| -rw-r--r-- | src/mailman/rest/domains.py | 1 | ||||
| -rw-r--r-- | src/mailman/rest/lists.py | 2 | ||||
| -rw-r--r-- | src/mailman/rest/users.py | 25 |
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.""" |
