Revert a few incorrect renames.
[mailman.git] / src / mailman / rest / root.py
blob3c38ded5dc07336b00b907e0d58c05a6d4a6e3bc
1 # Copyright (C) 2010-2016 by the Free Software Foundation, Inc.
3 # This file is part of GNU Mailman.
5 # GNU Mailman is free software: you can redistribute it and/or modify it under
6 # the terms of the GNU General Public License as published by the Free
7 # Software Foundation, either version 3 of the License, or (at your option)
8 # any later version.
10 # GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 # more details.
15 # You should have received a copy of the GNU General Public License along with
16 # GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
18 """The root of the REST API."""
20 from mailman import public
21 from mailman.config import config
22 from mailman.core.api import API30, API31
23 from mailman.core.constants import system_preferences
24 from mailman.core.system import system
25 from mailman.interfaces.listmanager import IListManager
26 from mailman.model.uid import UID
27 from mailman.rest.addresses import AllAddresses, AnAddress
28 from mailman.rest.bans import BannedEmail, BannedEmails
29 from mailman.rest.domains import ADomain, AllDomains
30 from mailman.rest.helpers import (
31 BadRequest, NotFound, child, etag, no_content, not_found, okay)
32 from mailman.rest.lists import AList, AllLists, Styles
33 from mailman.rest.members import AMember, AllMembers, FindMembers
34 from mailman.rest.preferences import ReadOnlyPreferences
35 from mailman.rest.queues import AQueue, AQueueFile, AllQueues
36 from mailman.rest.templates import TemplateFinder
37 from mailman.rest.users import AUser, AllUsers, ServerOwners
38 from zope.component import getUtility
41 SLASH = '/'
44 @public
45 class Root:
46 """The RESTful root resource.
48 At the root of the tree are the API version numbers. Everything else
49 lives underneath those. Currently there is only one API version number,
50 and we start at 3.0 to match the Mailman version number. That may not
51 always be the case though.
52 """
54 @child('3.0')
55 def api_version_30(self, context, segments):
56 # API version 3.0 was introduced in Mailman 3.0.
57 context['api'] = API30
58 return TopLevel()
60 @child('3.1')
61 def api_version_31(self, context, segments):
62 # API version 3.1 was introduced in Mailman 3.1. Primary backward
63 # incompatible difference is that uuids are represented as hex strings
64 # instead of 128 bit integers. The latter is not compatible with all
65 # versions of JavaScript.
66 context['api'] = API31
67 return TopLevel()
70 @public
71 class Versions:
72 def on_get(self, request, response):
73 """/<api>/system/versions"""
74 resource = dict(
75 mailman_version=system.mailman_version,
76 python_version=system.python_version,
77 api_version=self.api.version,
78 self_link=self.api.path_to('system/versions'),
80 okay(response, etag(resource))
83 @public
84 class SystemConfiguration:
85 def __init__(self, section=None):
86 self._section = section
88 def on_get(self, request, response):
89 if self._section is None:
90 resource = dict(
91 sections=sorted(section.name for section in config))
92 okay(response, etag(resource))
93 return
94 missing = object()
95 section = getattr(config, self._section, missing)
96 if section is missing:
97 not_found(response)
98 return
99 # Sections don't have .keys(), .values(), or .items() but we can
100 # iterate over them.
101 resource = {key: section[key] for key in section}
102 okay(response, etag(resource))
105 @public
106 class Pipelines:
107 def on_get(self, request, response):
108 resource = dict(pipelines=sorted(config.pipelines))
109 okay(response, etag(resource))
112 @public
113 class Chains:
114 def on_get(self, request, response):
115 resource = dict(chains=sorted(config.chains))
116 okay(response, etag(resource))
119 @public
120 class Reserved:
121 """Top level API for reserved operations.
123 Nothing under this resource should be considered part of the stable API.
124 The resources that appear here are purely for the support of external
125 non-production systems, such as testing infrastructures for cooperating
126 components. Use at your own risk.
128 def __init__(self, segments):
129 self._resource_path = SLASH.join(segments)
131 def on_delete(self, request, response):
132 if self._resource_path != 'uids/orphans':
133 not_found(response)
134 return
135 UID.cull_orphans()
136 no_content(response)
139 @public
140 class TopLevel:
141 """Top level collections and entries."""
143 @child()
144 def system(self, context, segments):
145 """/<api>/system"""
146 if len(segments) == 0:
147 # This provides backward compatibility; see /system/versions.
148 return Versions()
149 elif segments[0] == 'preferences':
150 if len(segments) > 1:
151 return BadRequest(), []
152 return ReadOnlyPreferences(system_preferences, 'system'), []
153 elif segments[0] == 'versions':
154 if len(segments) > 1:
155 return BadRequest(), []
156 return Versions(), []
157 elif segments[0] == 'configuration':
158 if len(segments) <= 2:
159 return SystemConfiguration(*segments[1:]), []
160 return BadRequest(), []
161 elif segments[0] == 'pipelines':
162 if len(segments) > 1:
163 return BadRequest(), []
164 return Pipelines(), []
165 elif segments[0] == 'chains':
166 if len(segments) > 1:
167 return BadRequest(), []
168 return Chains(), []
169 else:
170 return NotFound(), []
172 @child()
173 def addresses(self, context, segments):
174 """/<api>/addresses
175 /<api>/addresses/<email>
177 if len(segments) == 0:
178 return AllAddresses()
179 else:
180 email = segments.pop(0)
181 return AnAddress(email), segments
183 @child()
184 def domains(self, context, segments):
185 """/<api>/domains
186 /<api>/domains/<domain>
188 if len(segments) == 0:
189 return AllDomains()
190 else:
191 domain = segments.pop(0)
192 return ADomain(domain), segments
194 @child()
195 def lists(self, context, segments):
196 """/<api>/lists
197 /<api>/lists/styles
198 /<api>/lists/<list>
199 /<api>/lists/<list>/...
201 if len(segments) == 0:
202 return AllLists()
203 elif len(segments) == 1 and segments[0] == 'styles':
204 return Styles(), []
205 else:
206 # list-id is preferred, but for backward compatibility,
207 # fqdn_listname is also accepted.
208 list_identifier = segments.pop(0)
209 return AList(list_identifier), segments
211 @child()
212 def members(self, context, segments):
213 """/<api>/members"""
214 if len(segments) == 0:
215 return AllMembers()
216 # Either the next segment is the string "find" or a member id. They
217 # cannot collide.
218 segment = segments.pop(0)
219 if segment == 'find':
220 resource = FindMembers()
221 else:
222 try:
223 member_id = context['api'].to_uuid(segment)
224 except ValueError:
225 member_id = None
226 resource = AMember(member_id)
227 return resource, segments
229 @child()
230 def users(self, context, segments):
231 """/<api>/users"""
232 if len(segments) == 0:
233 return AllUsers()
234 else:
235 user_identifier = segments.pop(0)
236 return AUser(context['api'], user_identifier), segments
238 @child()
239 def owners(self, context, segments):
240 """/<api>/owners"""
241 if len(segments) != 0:
242 return BadRequest(), []
243 else:
244 return ServerOwners(), segments
246 @child()
247 def templates(self, context, segments):
248 """/<api>/templates/<fqdn_listname>/<template>/[<language>]
250 Use content negotiation to context language and suffix (content-type).
252 if len(segments) == 3:
253 fqdn_listname, template, language = segments
254 elif len(segments) == 2:
255 fqdn_listname, template = segments
256 language = 'en'
257 else:
258 return BadRequest(), []
259 mlist = getUtility(IListManager).get(fqdn_listname)
260 if mlist is None:
261 return NotFound(), []
262 # XXX dig out content-type from context.
263 content_type = None
264 return TemplateFinder(
265 fqdn_listname, template, language, content_type)
267 @child()
268 def queues(self, context, segments):
269 """/<api>/queues[/<name>[/file]]"""
270 if len(segments) == 0:
271 return AllQueues()
272 elif len(segments) == 1:
273 return AQueue(segments[0]), []
274 elif len(segments) == 2:
275 return AQueueFile(segments[0], segments[1]), []
276 else:
277 return BadRequest(), []
279 @child()
280 def bans(self, context, segments):
281 """/<api>/bans
282 /<api>/bans/<email>
284 if len(segments) == 0:
285 return BannedEmails(None)
286 else:
287 email = segments.pop(0)
288 return BannedEmail(None, email), segments
290 @child()
291 def reserved(self, context, segments):
292 """/<api>/reserved/[...]"""
293 return Reserved(segments), []