1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
|
# Copyright (C) 2009-2010 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/>.
"""Module stuff."""
from __future__ import absolute_import, unicode_literals
__metaclass__ = type
__all__ = [
'AdminWebServiceApplication',
'AdminWebServiceRequest',
'make_server',
]
import json
import hashlib
import logging
from restish.app import RestishApp
from restish import http, resource
from wsgiref.simple_server import (
make_server as wsgi_server, WSGIRequestHandler)
from zope.component import getUtility
from zope.interface import implements
from mailman.config import config
from mailman.core.system import system
from mailman.interfaces.domain import (
BadDomainSpecificationError, IDomain, IDomainManager)
from mailman.interfaces.listmanager import IListManager
from mailman.interfaces.mailinglist import IMailingList
from mailman.interfaces.member import IMember
from mailman.interfaces.membership import ISubscriptionService
from mailman.interfaces.rest import IResolvePathNames
#from mailman.rest.publication import AdminWebServicePublication
COMMASPACE = ', '
log = logging.getLogger('mailman.http')
class Root(resource.Resource):
"""The RESTful root resource."""
@resource.child('3.0')
def api_version(self, request, segments):
return TopLevel()
class TopLevel(resource.Resource):
"""Top level collections and entries."""
@resource.child()
def system(self, request, segments):
response = dict(
mailman_version=system.mailman_version,
python_version=system.python_version,
resource_type_link='http://localhost:8001/3.0/#system',
self_link='http://localhost:8001/3.0/system',
)
etag = hashlib.sha1(repr(response)).hexdigest()
response['http_etag'] = '"{0}"'.format(etag)
return http.ok([], json.dumps(response))
@resource.child()
def domains(self, request, segments):
if len(segments) == 0:
return AllDomains()
elif len(segments) == 1:
return ADomain(segments[0]), []
else:
return http.bad_request()
class _DomainBase(resource.Resource):
"""Shared base class for domain representations."""
def _format_domain(self, domain):
"""Format the data for a single domain."""
domain_data = dict(
base_url=domain.base_url,
contact_address=domain.contact_address,
description=domain.description,
email_host=domain.email_host,
resource_type_link='http://localhost:8001/3.0/#domain',
self_link='http://localhost:8001/3.0/domains/{0}'.format(
domain.email_host),
url_host=domain.url_host,
)
etag = hashlib.sha1(repr(domain_data)).hexdigest()
domain_data['http_etag'] = '"{0}"'.format(etag)
return domain_data
class ADomain(_DomainBase):
"""A domain."""
def __init__(self, domain):
self._domain = domain
@resource.GET()
def domain(self, request):
"""Return a single domain end-point."""
domain = getUtility(IDomainManager).get(self._domain)
if domain is None:
return http.not_found()
return http.ok([], json.dumps(self._format_domain(domain)))
class AllDomains(_DomainBase):
"""The domains."""
@resource.POST()
def create(self, request):
"""Create a new domain."""
# XXX 2010-02-23 barry Sanity check the POST arguments by
# introspection of the target method, or via descriptors.
domain_manager = getUtility(IDomainManager)
try:
# Hmmm... webob gives this to us as a string, but we need
# unicodes. For backward compatibility with lazr.restful style
# requests, ignore any ws.op parameter.
kws = dict((key, unicode(value))
for key, value in request.POST.items()
if key != 'ws.op')
domain = domain_manager.add(**kws)
except BadDomainSpecificationError:
return http.bad_request([], 'Domain exists')
# wsgiref wants headers to be strings, not unicodes.
location = 'http://localhost:8001/3.0/domains/{0}'.format(
domain.email_host)
# Include no extra headers or body.
return http.created(str(location), [], None)
@resource.GET()
def container(self, request):
"""Return the /domains end-point."""
domains = list(getUtility(IDomainManager))
if len(domains) == 0:
return http.ok(
[], json.dumps(dict(resource_type_link=
'http://localhost:8001/3.0/#domains',
start=None,
total_size=0)))
entries = []
response = dict(
resource_type_link='http://localhost:8001/3.0/#domains',
start=0,
total_size=len(domains),
entries=entries,
)
for domain in domains:
domain_data = self._format_domain(domain)
entries.append(domain_data)
return http.ok([], json.dumps(response))
## class AdminWebServiceRootResource(RootResource):
## """The lazr.restful non-versioned root resource."""
## implements(IResolvePathNames)
## # XXX 2010-02-16 barry lazr.restful really wants this class to exist and
## # be a subclass of RootResource. Our own traversal really wants this to
## # implement IResolvePathNames. RootResource says to override
## # _build_top_level_objects() to return the top-level objects, but that
## # appears to never be called by lazr.restful, so you've got me. I don't
## # understand this, which sucks, so just ensure that it doesn't do anything
## # useful so if/when I do understand this, I can resolve the conflict
## # between the way lazr.restful wants us to do things and the way our
## # traversal wants to do things.
## def _build_top_level_objects(self):
## """See `RootResource`."""
## raise NotImplementedError('Magic suddenly got invoked')
## def get(self, name):
## """See `IResolvePathNames`."""
## top_names = dict(
## domains=getUtility(IDomainCollection),
## lists=getUtility(IListManager),
## members=getUtility(ISubscriptionService),
## system=system,
## )
## return top_names.get(name)
## class AdminWebServiceApplication(WSGIApplication):
## """A WSGI application for the admin REST interface."""
## # The only thing we need to override is the publication class.
## publication_class = AdminWebServicePublication
class AdminWebServiceWSGIRequestHandler(WSGIRequestHandler):
"""Handler class which just logs output to the right place."""
def log_message(self, format, *args):
"""See `BaseHTTPRequestHandler`."""
log.info('%s - - %s', self.address_string(), format % args)
class AdminWebServiceApplication(RestishApp):
"""Interpose in the restish request processor."""
def __call__(self, environ, start_response):
"""See `RestishApp`."""
try:
response = super(AdminWebServiceApplication, self).__call__(
environ, start_response)
except:
config.db.abort()
raise
else:
config.db.commit()
return response
def make_server():
"""Create the WSGI admin REST server."""
app = AdminWebServiceApplication(Root())
host = config.webservice.hostname
port = int(config.webservice.port)
server = wsgi_server(
host, port, app,
handler_class=AdminWebServiceWSGIRequestHandler)
return server
|