Commit 6c75191a authored by Barry Warsaw's avatar Barry Warsaw

A new top-level resource `<api>/owners` can be used to get the list of server

owners as `IUser`s.  (Closes #135)
parents 4e92face 3023f73c
Pipeline #146435 passed with stage
......@@ -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.
......
......@@ -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.
......
......@@ -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.""")
......@@ -201,3 +201,47 @@ The user has a single unverified address object.
>>> for address in cris.addresses:
... print(repr(address))
<Address: Cris Person <[email protected]> [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 ...>
......@@ -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
===============
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('[email protected]', '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('[email protected]', 'Bart Person')
>>> cate = user_manager.create_user('[email protected]', '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
......@@ -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
......@@ -233,6 +233,14 @@ class TopLevel:
user_id = segments.pop(0)
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>]
......
# 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)
......@@ -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)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment