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)
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
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
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.
55 def api_version_30(self
, context
, segments
):
56 # API version 3.0 was introduced in Mailman 3.0.
57 context
['api'] = API30
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
72 def on_get(self
, request
, response
):
73 """/<api>/system/versions"""
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
))
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:
91 sections
=sorted(section
.name
for section
in config
))
92 okay(response
, etag(resource
))
95 section
= getattr(config
, self
._section
, missing
)
96 if section
is missing
:
99 # Sections don't have .keys(), .values(), or .items() but we can
101 resource
= {key
: section
[key
] for key
in section
}
102 okay(response
, etag(resource
))
107 def on_get(self
, request
, response
):
108 resource
= dict(pipelines
=sorted(config
.pipelines
))
109 okay(response
, etag(resource
))
114 def on_get(self
, request
, response
):
115 resource
= dict(chains
=sorted(config
.chains
))
116 okay(response
, etag(resource
))
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':
141 """Top level collections and entries."""
144 def system(self
, context
, segments
):
146 if len(segments
) == 0:
147 # This provides backward compatibility; see /system/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(), []
170 return NotFound(), []
173 def addresses(self
, context
, segments
):
175 /<api>/addresses/<email>
177 if len(segments
) == 0:
178 return AllAddresses()
180 email
= segments
.pop(0)
181 return AnAddress(email
), segments
184 def domains(self
, context
, segments
):
186 /<api>/domains/<domain>
188 if len(segments
) == 0:
191 domain
= segments
.pop(0)
192 return ADomain(domain
), segments
195 def lists(self
, context
, segments
):
199 /<api>/lists/<list>/...
201 if len(segments
) == 0:
203 elif len(segments
) == 1 and segments
[0] == 'styles':
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
212 def members(self
, context
, segments
):
214 if len(segments
) == 0:
216 # Either the next segment is the string "find" or a member id. They
218 segment
= segments
.pop(0)
219 if segment
== 'find':
220 resource
= FindMembers()
223 member_id
= context
['api'].to_uuid(segment
)
226 resource
= AMember(member_id
)
227 return resource
, segments
230 def users(self
, context
, segments
):
232 if len(segments
) == 0:
235 user_identifier
= segments
.pop(0)
236 return AUser(context
['api'], user_identifier
), segments
239 def owners(self
, context
, segments
):
241 if len(segments
) != 0:
242 return BadRequest(), []
244 return ServerOwners(), segments
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
258 return BadRequest(), []
259 mlist
= getUtility(IListManager
).get(fqdn_listname
)
261 return NotFound(), []
262 # XXX dig out content-type from context.
264 return TemplateFinder(
265 fqdn_listname
, template
, language
, content_type
)
268 def queues(self
, context
, segments
):
269 """/<api>/queues[/<name>[/file]]"""
270 if len(segments
) == 0:
272 elif len(segments
) == 1:
273 return AQueue(segments
[0]), []
274 elif len(segments
) == 2:
275 return AQueueFile(segments
[0], segments
[1]), []
277 return BadRequest(), []
280 def bans(self
, context
, segments
):
284 if len(segments
) == 0:
285 return BannedEmails(None)
287 email
= segments
.pop(0)
288 return BannedEmail(None, email
), segments
291 def reserved(self
, context
, segments
):
292 """/<api>/reserved/[...]"""
293 return Reserved(segments
), []