diff options
| author | Barry Warsaw | 2015-09-23 20:45:41 -0400 |
|---|---|---|
| committer | Barry Warsaw | 2015-09-23 20:45:41 -0400 |
| commit | 6c75191a230474d51505ba3269f14b9093b35863 (patch) | |
| tree | 8d2f66779c50bd64c449b9f0ad975fd12c5dd695 /src | |
| parent | 4e92faced9c8b215420971164fd5e1de97db7a46 (diff) | |
| parent | 3023f73c87588de46b7096123e778d999d6b4111 (diff) | |
| download | mailman-6c75191a230474d51505ba3269f14b9093b35863.tar.gz mailman-6c75191a230474d51505ba3269f14b9093b35863.tar.zst mailman-6c75191a230474d51505ba3269f14b9093b35863.zip | |
Diffstat (limited to 'src')
| -rw-r--r-- | src/mailman/docs/NEWS.rst | 2 | ||||
| -rw-r--r-- | src/mailman/interfaces/user.py | 3 | ||||
| -rw-r--r-- | src/mailman/interfaces/usermanager.py | 3 | ||||
| -rw-r--r-- | src/mailman/model/docs/usermanager.rst | 44 | ||||
| -rw-r--r-- | src/mailman/model/usermanager.py | 9 | ||||
| -rw-r--r-- | src/mailman/rest/docs/owners.rst | 89 | ||||
| -rw-r--r-- | src/mailman/rest/root.py | 10 | ||||
| -rw-r--r-- | src/mailman/rest/tests/test_owners.py | 40 | ||||
| -rw-r--r-- | src/mailman/rest/users.py | 15 |
9 files changed, 213 insertions, 2 deletions
diff --git a/src/mailman/docs/NEWS.rst b/src/mailman/docs/NEWS.rst index db86363e4..2f45fd31b 100644 --- a/src/mailman/docs/NEWS.rst +++ b/src/mailman/docs/NEWS.rst @@ -78,6 +78,8 @@ REST Bompard. * The REST API incorrectly parsed `is_server_owner` values when given explicitly in the POST that creates a user. (Closes #136) + * A new top-level resource `<api>/owners` can be used to get the list of + server owners as `IUser`s. (Closes #135) * By POSTing to a user resource with an existing unlinked address, you can link the address to the user. Given by Abhilash Raj. diff --git a/src/mailman/interfaces/user.py b/src/mailman/interfaces/user.py index 1068890c1..76ceb3f37 100644 --- a/src/mailman/interfaces/user.py +++ b/src/mailman/interfaces/user.py @@ -70,6 +70,9 @@ class IUser(Interface): memberships = Attribute( """A roster of this user's memberships.""") + is_server_owner = Attribute( + """Boolean flag indicating whether the user is a server owner.""") + def register(email, display_name=None): """Register the given email address and link it to this user. diff --git a/src/mailman/interfaces/usermanager.py b/src/mailman/interfaces/usermanager.py index 5f3a324cc..da2f460df 100644 --- a/src/mailman/interfaces/usermanager.py +++ b/src/mailman/interfaces/usermanager.py @@ -126,3 +126,6 @@ class IUserManager(Interface): members = Attribute( """An iterator of all the `IMembers` in the database.""") + + server_owners = Attribute( + """An iterator over all the `IUsers` who are server owners.""") diff --git a/src/mailman/model/docs/usermanager.rst b/src/mailman/model/docs/usermanager.rst index 8e40b621e..a98f88670 100644 --- a/src/mailman/model/docs/usermanager.rst +++ b/src/mailman/model/docs/usermanager.rst @@ -201,3 +201,47 @@ The user has a single unverified address object. >>> for address in cris.addresses: ... print(repr(address)) <Address: Cris Person <cris@example.com> [not verified] at ...> + + +Server owners +============= + +Some users are designated as *server owners*. At first there are no server +owners. + + >>> len(list(user_manager.server_owners)) + 0 + +Dan is made a server owner. + + >>> user_4.is_server_owner = True + >>> owners = list(user_manager.server_owners) + >>> len(owners) + 1 + >>> owners[0] + <User "Dan Person" (...) at ...> + +Now Ben and Claire are also made server owners. + + >>> user_2.is_server_owner = True + >>> user_3.is_server_owner = True + >>> owners = list(user_manager.server_owners) + >>> len(owners) + 3 + >>> from operator import attrgetter + >>> for user in sorted(owners, key=attrgetter('display_name')): + ... print(user) + <User "Ben Person" (...) at ...> + <User "Claire Person" (...) at ...> + <User "Dan Person" (...) at ...> + +Clair retires as a server owner. + + >>> user_3.is_server_owner = False + >>> owners = list(user_manager.server_owners) + >>> len(owners) + 2 + >>> for user in sorted(owners, key=attrgetter('display_name')): + ... print(user) + <User "Ben Person" (...) at ...> + <User "Dan Person" (...) at ...> diff --git a/src/mailman/model/usermanager.py b/src/mailman/model/usermanager.py index 3d7777099..5d63a2146 100644 --- a/src/mailman/model/usermanager.py +++ b/src/mailman/model/usermanager.py @@ -141,4 +141,11 @@ class UserManager: def members(self, store): """See `IUserManager.""" for member in store.query(Member).all(): - yield member + yield member + + @property + @dbconnection + def server_owners(self, store): + """ See `IUserManager.""" + users = store.query(User).filter_by(is_server_owner=True) + yield from users diff --git a/src/mailman/rest/docs/owners.rst b/src/mailman/rest/docs/owners.rst new file mode 100644 index 000000000..42be49c02 --- /dev/null +++ b/src/mailman/rest/docs/owners.rst @@ -0,0 +1,89 @@ +=============== + Server owners +=============== + +Certain users can be designated as *server owners*. This role has no direct +function in the core, but it can be used by clients of the REST API to +determine additional permissions. For example, Postorius might allow server +owners to create new domains. + +Initially, there are no server owners. + + >>> dump_json('http://localhost:9001/3.0/owners') + http_etag: "..." + start: 0 + total_size: 0 + +When new users are created in the core, they do not become server owners by +default. + + >>> from zope.component import getUtility + >>> from mailman.interfaces.usermanager import IUserManager + >>> user_manager = getUtility(IUserManager) + >>> anne = user_manager.create_user('anne@example.com', 'Anne Person') + >>> transaction.commit() + >>> dump_json('http://localhost:9001/3.0/owners') + http_etag: "..." + start: 0 + total_size: 0 + +Anne's server owner flag is set. + + >>> anne.is_server_owner = True + >>> transaction.commit() + +And now we can find her user record. + + >>> dump_json('http://localhost:9001/3.0/owners') + entry 0: + created_on: 2005-08-01T07:49:23 + display_name: Anne Person + http_etag: "..." + is_server_owner: True + self_link: http://localhost:9001/3.0/users/1 + user_id: 1 + http_etag: "..." + start: 0 + total_size: 1 + +Bart and Cate are also users, but not server owners. + + >>> bart = user_manager.create_user('bart@example.com', 'Bart Person') + >>> cate = user_manager.create_user('cate@example.com', 'Cate Person') + >>> transaction.commit() + >>> dump_json('http://localhost:9001/3.0/owners') + entry 0: + created_on: 2005-08-01T07:49:23 + display_name: Anne Person + http_etag: "..." + is_server_owner: True + self_link: http://localhost:9001/3.0/users/1 + user_id: 1 + http_etag: "..." + start: 0 + total_size: 1 + +Anne retires as a server owner, with Bart and Cate taking over. + + >>> anne.is_server_owner = False + >>> bart.is_server_owner = True + >>> cate.is_server_owner = True + >>> transaction.commit() + >>> dump_json('http://localhost:9001/3.0/owners') + entry 0: + created_on: 2005-08-01T07:49:23 + display_name: Bart Person + http_etag: "..." + is_server_owner: True + self_link: http://localhost:9001/3.0/users/2 + user_id: 2 + entry 1: + created_on: 2005-08-01T07:49:23 + display_name: Cate Person + http_etag: "..." + is_server_owner: True + self_link: http://localhost:9001/3.0/users/3 + user_id: 3 + http_etag: "..." + start: 0 + total_size: 2 diff --git a/src/mailman/rest/root.py b/src/mailman/rest/root.py index ca8bbc4a9..0682619b1 100644 --- a/src/mailman/rest/root.py +++ b/src/mailman/rest/root.py @@ -39,7 +39,7 @@ from mailman.rest.members import AMember, AllMembers, FindMembers from mailman.rest.preferences import ReadOnlyPreferences from mailman.rest.queues import AQueue, AQueueFile, AllQueues from mailman.rest.templates import TemplateFinder -from mailman.rest.users import AUser, AllUsers +from mailman.rest.users import AUser, AllUsers, ServerOwners from zope.component import getUtility @@ -234,6 +234,14 @@ class TopLevel: return AUser(user_id), segments @child() + def owners(self, request, segments): + """/<api>/owners""" + if len(segments) != 0: + return BadRequest(), [] + else: + return ServerOwners(), segments + + @child() def templates(self, request, segments): """/<api>/templates/<fqdn_listname>/<template>/[<language>] diff --git a/src/mailman/rest/tests/test_owners.py b/src/mailman/rest/tests/test_owners.py new file mode 100644 index 000000000..2cbebc5c4 --- /dev/null +++ b/src/mailman/rest/tests/test_owners.py @@ -0,0 +1,40 @@ +# 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/>. + +"""Additional tests for the top-level owners resource.""" + +__all__ = [ + 'TestOwners', + ] + + +import unittest + +from mailman.testing.helpers import call_api +from mailman.testing.layers import RESTLayer +from urllib.error import HTTPError + + + +class TestOwners(unittest.TestCase): + layer = RESTLayer + + def test_bogus_trailing_path(self): + # Nothing is allowed after the top-level /owners resource. + with self.assertRaises(HTTPError) as cm: + call_api('http://localhost:9001/3.0/owners/anne') + self.assertEqual(cm.exception.code, 400) diff --git a/src/mailman/rest/users.py b/src/mailman/rest/users.py index 252e7757b..6c352fd8c 100644 --- a/src/mailman/rest/users.py +++ b/src/mailman/rest/users.py @@ -464,3 +464,18 @@ class OwnersForDomain(_UserBase): def _get_collection(self, request): """See `CollectionMixin`.""" return list(self._domain.owners) + + + +class ServerOwners(_UserBase): + """All server owners.""" + + def on_get(self, request, response): + """/owners""" + resource = self._make_collection(request) + okay(response, etag(resource)) + + @paginate + def _get_collection(self, request): + """See `CollectionMixin`.""" + return list(getUtility(IUserManager).server_owners) |
