summaryrefslogtreecommitdiff
path: root/src/mailman/rest/listconf.py
diff options
context:
space:
mode:
authorBarry Warsaw2014-12-26 22:59:02 -0500
committerBarry Warsaw2014-12-26 22:59:02 -0500
commit37ee257a8b0ccdcab41703b04d68e3f939017988 (patch)
tree54f5a586f2c8a1f09ccd5ba7826b3646f0e49624 /src/mailman/rest/listconf.py
parentee2fdc578b5e0209a9e661cc1455aaff5dc8443a (diff)
downloadmailman-37ee257a8b0ccdcab41703b04d68e3f939017988.tar.gz
mailman-37ee257a8b0ccdcab41703b04d68e3f939017988.tar.zst
mailman-37ee257a8b0ccdcab41703b04d68e3f939017988.zip
Diffstat (limited to 'src/mailman/rest/listconf.py')
-rw-r--r--src/mailman/rest/listconf.py228
1 files changed, 228 insertions, 0 deletions
diff --git a/src/mailman/rest/listconf.py b/src/mailman/rest/listconf.py
new file mode 100644
index 000000000..6cf54a00e
--- /dev/null
+++ b/src/mailman/rest/listconf.py
@@ -0,0 +1,228 @@
+# Copyright (C) 2010-2014 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/>.
+
+"""Mailing list configuration via REST API."""
+
+__all__ = [
+ 'ListConfiguration',
+ ]
+
+
+import six
+
+from lazr.config import as_boolean, as_timedelta
+from mailman.config import config
+from mailman.core.errors import (
+ ReadOnlyPATCHRequestError, UnknownPATCHRequestError)
+from mailman.interfaces.action import Action
+from mailman.interfaces.archiver import ArchivePolicy
+from mailman.interfaces.autorespond import ResponseAction
+from mailman.interfaces.mailinglist import IAcceptableAliasSet, ReplyToMunging
+from mailman.rest.helpers import (
+ GetterSetter, bad_request, etag, no_content, okay)
+from mailman.rest.validator import PatchValidator, Validator, enum_validator
+
+
+
+class AcceptableAliases(GetterSetter):
+ """Resource for the acceptable aliases of a mailing list."""
+
+ def get(self, mlist, attribute):
+ """Return the mailing list's acceptable aliases."""
+ assert attribute == 'acceptable_aliases', (
+ 'Unexpected attribute: {}'.format(attribute))
+ aliases = IAcceptableAliasSet(mlist)
+ return sorted(aliases.aliases)
+
+ def put(self, mlist, attribute, value):
+ """Change the acceptable aliases.
+
+ Because this is a PUT operation, all previous aliases are cleared
+ first. Thus, this is an overwrite. The keys in the request are
+ ignored.
+ """
+ assert attribute == 'acceptable_aliases', (
+ 'Unexpected attribute: {}'.format(attribute))
+ alias_set = IAcceptableAliasSet(mlist)
+ alias_set.clear()
+ for alias in value:
+ alias_set.add(alias)
+
+
+
+# Additional validators for converting from web request strings to internal
+# data types. See below for details.
+
+def pipeline_validator(pipeline_name):
+ """Convert the pipeline name to a string, but only if it's known."""
+ if pipeline_name in config.pipelines:
+ return pipeline_name
+ raise ValueError('Unknown pipeline: {}'.format(pipeline_name))
+
+
+def list_of_str(values):
+ """Turn a list of things into a list of unicodes."""
+ for value in values:
+ if not isinstance(value, str):
+ raise ValueError('Expected str, got {!r}'.format(value))
+ return values
+
+
+
+# This is the list of IMailingList attributes that are exposed through the
+# REST API. The values of the keys are the GetterSetter instance holding the
+# decoder used to convert the web request string to an internally valid value.
+# The instance also contains the get() and put() methods used to retrieve and
+# set the attribute values. Its .decoder attribute will be None for read-only
+# attributes.
+#
+# The decoder must either return the internal value or raise a ValueError if
+# the conversion failed (e.g. trying to turn 'Nope' into a boolean).
+#
+# Many internal value types can be automatically JSON encoded, but see
+# mailman.rest.helpers.ExtendedEncoder for specializations of certain types
+# (e.g. datetimes, timedeltas, enums).
+
+ATTRIBUTES = dict(
+ acceptable_aliases=AcceptableAliases(list_of_str),
+ admin_immed_notify=GetterSetter(as_boolean),
+ admin_notify_mchanges=GetterSetter(as_boolean),
+ administrivia=GetterSetter(as_boolean),
+ advertised=GetterSetter(as_boolean),
+ anonymous_list=GetterSetter(as_boolean),
+ autorespond_owner=GetterSetter(enum_validator(ResponseAction)),
+ autorespond_postings=GetterSetter(enum_validator(ResponseAction)),
+ autorespond_requests=GetterSetter(enum_validator(ResponseAction)),
+ autoresponse_grace_period=GetterSetter(as_timedelta),
+ autoresponse_owner_text=GetterSetter(six.text_type),
+ autoresponse_postings_text=GetterSetter(six.text_type),
+ autoresponse_request_text=GetterSetter(six.text_type),
+ archive_policy=GetterSetter(enum_validator(ArchivePolicy)),
+ bounces_address=GetterSetter(None),
+ collapse_alternatives=GetterSetter(as_boolean),
+ convert_html_to_plaintext=GetterSetter(as_boolean),
+ created_at=GetterSetter(None),
+ default_member_action=GetterSetter(enum_validator(Action)),
+ default_nonmember_action=GetterSetter(enum_validator(Action)),
+ description=GetterSetter(six.text_type),
+ digest_last_sent_at=GetterSetter(None),
+ digest_size_threshold=GetterSetter(float),
+ filter_content=GetterSetter(as_boolean),
+ first_strip_reply_to=GetterSetter(as_boolean),
+ fqdn_listname=GetterSetter(None),
+ mail_host=GetterSetter(None),
+ allow_list_posts=GetterSetter(as_boolean),
+ include_rfc2369_headers=GetterSetter(as_boolean),
+ join_address=GetterSetter(None),
+ last_post_at=GetterSetter(None),
+ leave_address=GetterSetter(None),
+ list_name=GetterSetter(None),
+ next_digest_number=GetterSetter(None),
+ no_reply_address=GetterSetter(None),
+ owner_address=GetterSetter(None),
+ post_id=GetterSetter(None),
+ posting_address=GetterSetter(None),
+ posting_pipeline=GetterSetter(pipeline_validator),
+ display_name=GetterSetter(six.text_type),
+ reply_goes_to_list=GetterSetter(enum_validator(ReplyToMunging)),
+ reply_to_address=GetterSetter(six.text_type),
+ request_address=GetterSetter(None),
+ scheme=GetterSetter(None),
+ send_welcome_message=GetterSetter(as_boolean),
+ subject_prefix=GetterSetter(six.text_type),
+ volume=GetterSetter(None),
+ web_host=GetterSetter(None),
+ welcome_message_uri=GetterSetter(six.text_type),
+ )
+
+
+VALIDATORS = ATTRIBUTES.copy()
+for attribute, gettersetter in list(VALIDATORS.items()):
+ if gettersetter.decoder is None:
+ del VALIDATORS[attribute]
+
+
+
+class ListConfiguration:
+ """A mailing list configuration resource."""
+
+ def __init__(self, mailing_list, attribute):
+ self._mlist = mailing_list
+ self._attribute = attribute
+
+ def on_get(self, request, response):
+ """Get a mailing list configuration."""
+ resource = {}
+ if self._attribute is None:
+ # Return all readable attributes.
+ for attribute in ATTRIBUTES:
+ value = ATTRIBUTES[attribute].get(self._mlist, attribute)
+ resource[attribute] = value
+ elif self._attribute not in ATTRIBUTES:
+ bad_request(
+ response, b'Unknown attribute: {}'.format(self._attribute))
+ return
+ else:
+ attribute = self._attribute
+ value = ATTRIBUTES[attribute].get(self._mlist, attribute)
+ resource[attribute] = value
+ okay(response, etag(resource))
+
+ def on_put(self, request, response):
+ """Set a mailing list configuration."""
+ attribute = self._attribute
+ if attribute is None:
+ validator = Validator(**VALIDATORS)
+ try:
+ validator.update(self._mlist, request)
+ except ValueError as error:
+ bad_request(response, str(error))
+ return
+ elif attribute not in ATTRIBUTES:
+ bad_request(response, b'Unknown attribute: {}'.format(attribute))
+ return
+ elif ATTRIBUTES[attribute].decoder is None:
+ bad_request(
+ response, b'Read-only attribute: {}'.format(attribute))
+ return
+ else:
+ validator = Validator(**{attribute: VALIDATORS[attribute]})
+ try:
+ validator.update(self._mlist, request)
+ except ValueError as error:
+ bad_request(response, str(error))
+ return
+ no_content(response)
+
+ def on_patch(self, request, response):
+ """Patch the configuration (i.e. partial update)."""
+ try:
+ validator = PatchValidator(request, ATTRIBUTES)
+ except UnknownPATCHRequestError as error:
+ bad_request(
+ response, b'Unknown attribute: {}'.format(error.attribute))
+ return
+ except ReadOnlyPATCHRequestError as error:
+ bad_request(
+ response, b'Read-only attribute: {}'.format(error.attribute))
+ return
+ try:
+ validator.update(self._mlist, request)
+ except ValueError as error:
+ bad_request(response, str(error))
+ else:
+ no_content(response)