Barry Warsaw pushed to branch master at mailman / Mailman
Commits: 811f10df by Barry Warsaw at 2015-09-20T14:56:47Z Document an attribute. Add a doctest for the `owners` top-level resource. - - - - - b2a05024 by Barry Warsaw at 2015-09-20T14:56:47Z Start of owners top level resource. - - - - - 50757a93 by Barry Warsaw at 2015-09-20T15:43:28Z Add server_owners attribute to the user manager. - - - - - 77b8745f by Barry Warsaw at 2015-09-20T15:54:06Z Implement the REST API for <api>/owners. - - - - - 6060ae33 by Barry Warsaw at 2015-09-20T15:59:11Z Add another code-path test. - - - - - 4188b533 by Barry Warsaw at 2015-09-20T17:23:54Z Add NEWS. - - - - - 61877ba9 by Barry Warsaw at 2015-09-22T23:35:47Z Document an attribute. Add a doctest for the `owners` top-level resource. - - - - - 8dfa62d6 by Barry Warsaw at 2015-09-22T23:35:47Z Start of owners top level resource. - - - - - f27103bf by Barry Warsaw at 2015-09-22T23:35:47Z Add server_owners attribute to the user manager. - - - - - 3b90cb8e by Barry Warsaw at 2015-09-22T23:35:47Z Implement the REST API for <api>/owners. - - - - - f6e09955 by Barry Warsaw at 2015-09-22T23:35:47Z Add another code-path test. - - - - - 2f6a9c1f by Barry Warsaw at 2015-09-22T23:35:47Z Add NEWS. - - - - - d68b6a21 by Barry Warsaw at 2015-09-23T16:25:15Z Merge branch 'issue135' of gitlab.com:warsaw/mailman into issue135 - - - - - c8811695 by Barry Warsaw at 2015-09-23T16:27:20Z Document an attribute. Add a doctest for the `owners` top-level resource. - - - - - 4f1fba02 by Barry Warsaw at 2015-09-23T16:27:20Z Start of owners top level resource. - - - - - dfc1cff7 by Barry Warsaw at 2015-09-23T16:27:20Z Add server_owners attribute to the user manager. - - - - - c8779a10 by Barry Warsaw at 2015-09-23T16:27:20Z Implement the REST API for <api>/owners. - - - - - b0a9d9e3 by Barry Warsaw at 2015-09-23T16:27:20Z Add another code-path test. - - - - - afb59427 by Barry Warsaw at 2015-09-23T16:27:20Z Add NEWS. - - - - - 3023f73c by Barry Warsaw at 2015-09-23T16:30:37Z Merge branch 'issue135' of gitlab.com:warsaw/mailman into issue135 - - - - - 6c75191a by Barry Warsaw at 2015-09-23T20:45:41Z A new top-level resource `<api>/owners` can be used to get the list of server owners as `IUser`s. (Closes #135) - - - - - 9 changed files: - src/mailman/docs/NEWS.rst - src/mailman/interfaces/user.py - src/mailman/interfaces/usermanager.py - src/mailman/model/docs/usermanager.rst - src/mailman/model/usermanager.py - + src/mailman/rest/docs/owners.rst - src/mailman/rest/root.py - + src/mailman/rest/tests/test_owners.py - src/mailman/rest/users.py Changes: ===================================== src/mailman/docs/NEWS.rst ===================================== --- 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. ===================================== src/mailman/interfaces/user.py ===================================== --- 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. ===================================== src/mailman/interfaces/usermanager.py ===================================== --- 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.""") ===================================== src/mailman/model/docs/usermanager.rst ===================================== --- 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 <c...@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 ...> ===================================== src/mailman/model/usermanager.py ===================================== --- 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 ===================================== src/mailman/rest/docs/owners.rst ===================================== --- /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('a...@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('b...@example.com', 'Bart Person') + >>> cate = user_manager.create_user('c...@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 ===================================== src/mailman/rest/root.py ===================================== --- 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>] ===================================== src/mailman/rest/tests/test_owners.py ===================================== --- /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) ===================================== src/mailman/rest/users.py ===================================== --- 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) View it on GitLab: https://gitlab.com/mailman/mailman/compare/4e92faced9c8b215420971164fd5e1de97db7a46...6c75191a230474d51505ba3269f14b9093b35863
_______________________________________________ Mailman-checkins mailing list Mailman-checkins@python.org Unsubscribe: https://mail.python.org/mailman/options/mailman-checkins/archive%40jab.org