def on_post(self, request, response): """POST to /domains/<domain>/owners """ if self._domain is None: not_found(response) return validator = Validator( owner=ListOfDomainOwners(list_of_strings_validator)) try: validator.update(self._domain, request) except ValueError as error: bad_request(response, str(error)) return return no_content(response)
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 _find(self, request, response): validator = Validator( header=str, action=enum_validator(Action), tag=str, _optional=('action', 'tag', 'header') ) try: data = validator(request) except ValueError as error: bad_request(response, str(error)) return # Remove any optional pagination elements. action = data.pop('action', None) if action is not None: data['chain'] = action.name service = IHeaderMatchList(self._mlist) self.header_matches = list(service.filter(**data)) # Return 404 if no values were found. if len(self.header_matches) == 0: return not_found( response, 'Cound not find any HeaderMatch for provided search options.') resource = self._make_collection(request) okay(response, etag(resource))
def on_post(self, request, response): """Create a new member.""" service = getUtility(ISubscriptionService) try: validator = Validator(list_id=str, subscriber=subscriber_validator, display_name=str, delivery_mode=enum_validator(DeliveryMode), role=enum_validator(MemberRole), _optional=('delivery_mode', 'display_name', 'role')) member = service.join(**validator(request)) except AlreadySubscribedError: conflict(response, b'Member already subscribed') except NoSuchListError: bad_request(response, b'No such list') except InvalidEmailAddressError: bad_request(response, b'Invalid email address') except ValueError as error: bad_request(response, str(error)) else: # The member_id are UUIDs. We need to use the integer equivalent # in the URL. member_id = member.member_id.int location = path_to('members/{0}'.format(member_id)) created(response, location)
def _find(self, request, response): """Find a member""" service = getUtility(ISubscriptionService) validator = Validator( list_id=str, subscriber=str, role=enum_validator(MemberRole), # Allow pagination. page=int, count=int, fields=list_of_strings_validator, _optional=('list_id', 'subscriber', 'role', 'page', 'count', 'fields')) try: data = validator(request) except ValueError as error: bad_request(response, str(error)) else: # Remove any optional pagination query elements; they will be # handled later. data.pop('page', None) data.pop('count', None) fields = data.pop('fields', None) members = service.find_members(**data) resource = _FoundMembers(members, self.api) try: collection = resource._make_collection(request, fields) except ValueError as ex: bad_request(response, str(ex)) return okay(response, etag(collection))
def on_post(self, request, response): try: validator = Validator(send=as_boolean, bump=as_boolean, periodic=as_boolean, _optional=('send', 'bump', 'periodic')) values = validator(request) except ValueError as error: bad_request(response, str(error)) return if values.get('send', False) and values.get('periodic', False): # Send and periodic and mutually exclusive options. bad_request(response, 'send and periodic options are mutually exclusive') return if len(values) == 0: # There's nothing to do, but that's okay. okay(response) return if values.get('bump', False): bump_digest_number_and_volume(self._mlist) if values.get('send', False): maybe_send_digest_now(self._mlist, force=True) if values.get('periodic', False) and self._mlist.digest_send_periodic: maybe_send_digest_now(self._mlist, force=True) accepted(response)
def on_post(self, request, response): try: validator = Validator(action=enum_validator(Action)) arguments = validator(request) except ValueError as error: bad_request(response, str(error)) return requests = IListRequests(self._mlist) try: request_id = int(self._request_id) except ValueError: bad_request(response) return results = requests.get_request(request_id) if results is None: not_found(response) return key, data = results try: request_type = RequestType[data['_request_type']] except ValueError: bad_request(response) return if request_type is RequestType.subscription: handle_subscription(self._mlist, request_id, **arguments) elif request_type is RequestType.unsubscription: handle_unsubscription(self._mlist, request_id, **arguments) else: bad_request(response) return no_content(response)
def _patch_put(self, request, response, is_optional): """Update the header match.""" try: header_match = self.header_matches[self._position] except IndexError: not_found( response, 'No header match at this position: {}'.format(self._position)) return kws = dict( header=lowercase, pattern=str, position=int, action=enum_validator(Action), ) if is_optional: # For a PATCH, all attributes are optional. kws['_optional'] = kws.keys() else: # For a PUT, position can remain unchanged and action can be None. kws['_optional'] = ('action', 'position') validator = Validator(**kws) try: arguments = validator(request) action = arguments.pop('action', None) if action is not None: arguments['chain'] = action.name for key, value in arguments.items(): setattr(header_match, key, value) except ValueError as error: bad_request(response, str(error)) return else: no_content(response)
def on_put(self, request, response): """Set or replace the addresses's user.""" if self._user: self._user.unlink(self._address) # Process post data and check for an existing user. fields = CREATION_FIELDS.copy() fields['user_id'] = self.api.to_uuid fields['_optional'] = fields['_optional'] + ( 'user_id', 'email', 'is_server_owner') try: validator = Validator(**fields) arguments = validator(request) except ValueError as error: bad_request(response, str(error)) return user_manager = getUtility(IUserManager) if 'user_id' in arguments: user_id = arguments['user_id'] user = user_manager.get_user_by_id(user_id) if user is None: not_found(response, b'No user with ID {}'.format(user_id)) return okay(response) else: user = create_user(self.api, arguments, response) if user is None: return user.link(self._address)
def on_get(self, request, response): """/lists/listname/requests""" validator = Validator( token_owner=enum_validator(TokenOwner), request_type=enum_validator(PendType), page=int, count=int, _optional=['token_owner', 'page', 'count', 'request_type'], ) try: data = validator(request) except ValueError as error: bad_request(response, str(error)) else: data.pop('page', None) data.pop('count', None) token_owner = data.pop('token_owner', None) pend_type = data.pop('request_type', PendType.subscription) pendings = getUtility(IPendings).find( mlist=self._mlist, pend_type=pend_type.name, token_owner=token_owner) resource = _SubscriptionRequestsFound( [token for token, pendable in pendings]) okay(response, etag(resource._make_collection(request)))
def _find(self, request, response): validator = Validator( subscriber=subscriber_validator(self.api), role=enum_validator(MemberRole), # Allow pagination. page=int, count=int, _optional=('role', 'page', 'count')) try: data = validator(request) except ValueError as error: bad_request(response, str(error)) return else: # Remove any optional pagination query elements. data.pop('page', None) data.pop('count', None) service = getUtility(ISubscriptionService) # Get all membership records for given subscriber. memberships = service.find_members(**data) # Get all the lists from from the membership records. lists = [ getUtility(IListManager).get_by_list_id(member.list_id) for member in memberships ] # If there are no matching lists, return a 404. if not len(lists): return not_found(response) resource = _ListOfLists(lists, self.api) okay(response, etag(resource._make_collection(request)))
def on_patch(self, request, response): """Patch the membership. This is how subscription changes are done. """ if self._member is None: not_found(response) return try: values = Validator( address=str, delivery_mode=enum_validator(DeliveryMode), moderation_action=enum_validator(Action, allow_blank=True), _optional=('address', 'delivery_mode', 'moderation_action'), )(request) except ValueError as error: bad_request(response, str(error)) return if 'address' in values: email = values['address'] address = getUtility(IUserManager).get_address(email) if address is None: bad_request(response, b'Address not registered') return try: self._member.address = address except (MembershipError, UnverifiedAddressError) as error: bad_request(response, str(error)) return if 'delivery_mode' in values: self._member.preferences.delivery_mode = values['delivery_mode'] if 'moderation_action' in values: self._member.moderation_action = values['moderation_action'] no_content(response)
def on_post(self, request, response): """POST to /addresses Add a new address to the user record. """ if self._user is None: not_found(response) return user_manager = getUtility(IUserManager) validator = Validator(email=str, display_name=str, _optional=('display_name', )) try: address = user_manager.create_address(**validator(request)) except ValueError as error: bad_request(response, str(error)) except InvalidEmailAddressError: bad_request(response, b'Invalid email address') except ExistingAddressError: # Check if the address is not linked to any user, link it to the # current user and return it. Since we're linking to an existing # address, ignore any given display_name attribute. address = user_manager.get_address(validator(request)['email']) if address.user is None: address.user = self._user location = self.path_to('addresses/{}'.format(address.email)) created(response, location) else: bad_request(response, 'Address belongs to other user.') else: # Link the address to the current user and return it. address.user = self._user location = self.path_to('addresses/{}'.format(address.email)) created(response, location)
def put_update(self, request): """Put the user's configuration (i.e. full update).""" if self._user is None: return http.not_found() validator = Validator(**ATTRIBUTES) try: validator.update(self._user, request) except UnknownPATCHRequestError as error: return http.bad_request( [], b'Unknown attribute: {0}'.format(error.attribute)) except ReadOnlyPATCHRequestError as error: return http.bad_request( [], b'Read-only attribute: {0}'.format(error.attribute)) except ValueError as error: return http.bad_request([], str(error)) return no_content()
def on_post(self, request, response): try: resource = Validator(number=int)(request) self._plugin.number = resource['number'] except ValueError as error: bad_request(response, str(error)) else: no_content(response)
def on_post(self, request, response): """POST to /addresses Add a new address to the user record. """ assert self._user is not None preferred = None user_manager = getUtility(IUserManager) validator = Validator(email=str, display_name=str, preferred=as_boolean, absorb_existing=bool, _optional=('display_name', 'absorb_existing', 'preferred')) try: data = validator(request) # We cannot set the address to be preferred when it is # created so remove it from the arguments here and # set it below. preferred = data.pop('preferred', False) except ValueError as error: bad_request(response, str(error)) return absorb_existing = data.pop('absorb_existing', False) try: address = user_manager.create_address(**data) except InvalidEmailAddressError: bad_request(response, b'Invalid email address') return except ExistingAddressError: # If the address is not linked to any user, link it to the current # user and return it. Since we're linking to an existing address, # ignore any given display_name attribute. address = user_manager.get_address(data['email']) if address.user is None: address.user = self._user location = self.api.path_to('addresses/{}'.format( address.email)) created(response, location) return elif not absorb_existing: bad_request(response, 'Address belongs to other user') return else: # The address exists and is linked but we can merge the users. address = user_manager.get_address(data['email']) self._user.absorb(address.user) else: # Link the address to the current user and return it. address.user = self._user # Set the preferred address here if we were signalled to do so. if preferred: address.verified_on = now() self._user.preferred_address = address location = self.api.path_to('addresses/{}'.format(address.email)) created(response, location)
def on_put(self, request, response): """Put the user's configuration (i.e. full update).""" if self._user is None: not_found(response) return validator = Validator(**ATTRIBUTES) try: validator.update(self._user, request) except UnknownPATCHRequestError as error: bad_request( response, b'Unknown attribute: {0}'.format(error.attribute)) except ReadOnlyPATCHRequestError as error: bad_request( response, b'Read-only attribute: {0}'.format(error.attribute)) except ValueError as error: bad_request(response, str(error)) else: no_content(response)
def on_post(self, request, response): """Create a new user.""" try: validator = Validator(**CREATION_FIELDS) arguments = validator(request) except ValueError as error: bad_request(response, str(error)) return create_user(self.api, arguments, response)
def on_delete(self, request, response): """Delete the member (i.e. unsubscribe).""" # Leaving a list is a bit different than deleting a moderator or # owner. Handle the former case first. For now too, we will not send # an admin or user notification. if self._member is None: not_found(response) return mlist = getUtility(IListManager).get_by_list_id(self._member.list_id) if self._member.role is MemberRole.member: try: values = Validator( pre_confirmed=as_boolean, pre_approved=as_boolean, _optional=('pre_confirmed', 'pre_approved'), )(request) except ValueError as error: bad_request(response, str(error)) return manager = ISubscriptionManager(mlist) # XXX(maxking): For backwards compat, we are going to keep # pre-confirmed to be "True" by defualt instead of "False", that it # should be. Any, un-authenticated requests should manually specify # that it is *not* confirmed by the user. if 'pre_confirmed' in values: pre_confirmed = values.get('pre_confirmed') else: pre_confirmed = True token, token_owner, member = manager.unregister( self._member.address, pre_approved=values.get('pre_approved'), pre_confirmed=pre_confirmed) if member is None: assert token is None assert token_owner is TokenOwner.no_one no_content(response) else: assert token is not None content = dict(token=token, token_owner=token_owner.name) accepted(response, etag(content)) else: self._member.unsubscribe() no_content(response)
def patch_put(self, request, is_optional): if self._parent is None: return http.not_found() kws = dict( acknowledge_posts=as_boolean, delivery_mode=enum_validator(DeliveryMode), delivery_status=enum_validator(DeliveryStatus), preferred_language=language_validator, receive_list_copy=as_boolean, receive_own_postings=as_boolean, ) if is_optional: # For a PUT, all attributes are optional. kws['_optional'] = kws.keys() try: values = Validator(**kws)(request) except ValueError as error: return http.bad_request([], str(error)) for key, value in values.items(): setattr(self._parent, key, value) return no_content()
def on_post(self, request, response): """Ban some email from subscribing.""" validator = Validator(email=str) try: email = validator(request)['email'] except ValueError as error: bad_request(response, str(error)) return if self.ban_manager.is_banned(email): bad_request(response, b'Address is already banned') else: self.ban_manager.ban(email) created(response, self._location(email))
def patch_put(self, request, response, is_optional): archiver_set = IListArchiverSet(self._mlist) kws = {archiver.name: ArchiverGetterSetter(self._mlist) for archiver in archiver_set.archivers} if is_optional: # For a PATCH, all attributes are optional. kws['_optional'] = kws.keys() try: Validator(**kws).update(self._mlist, request) except ValueError as error: bad_request(response, str(error)) else: no_content(response)
def on_put(self, request, response): """Set a mailing list configuration.""" attribute = self._attribute # The list of required attributes differs between API version. For # backward compatibility, in API 3.0 all of the *_uri attributes are # optional. In API 3.1 none of these are allowed since they are # handled by the template manager API. validators = VALIDATORS.copy() attributes = api_attributes(self.api) if self.api.version_info == (3, 0): validators.update({ attribute: URIAttributeMapper(str) for attribute in TEMPLATE_ATTRIBUTES }) validators['_optional'] = TEMPLATE_ATTRIBUTES.keys() if attribute is None: # This is a request to update all the list's writable # configuration variables. All must be provided in the request. validator = Validator(**validators) try: validator.update(self._mlist, request) except ValueError as error: # Unlike the case where we're PUTting to a specific # configuration sub-resource, if we're PUTting to the list's # entire configuration, but the request has a bogus attribute, # the entire request is considered bad. We can also get here # if one of the attributes is read-only. The error will # contain sufficient details, so just return it as the reason. bad_request(response, str(error)) return elif attribute not in attributes: # Here we're PUTting to a specific resource, but that attribute is # bogus so the URL is considered pointing to a missing resource. not_found(response, 'Unknown attribute: {}'.format(attribute)) return elif attributes[attribute].decoder is None: bad_request( response, 'Read-only attribute: {}'.format(attribute)) return else: # We're PUTting to a specific configuration sub-resource. 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_post(self, request, response): """Find a member""" service = getUtility(ISubscriptionService) validator = Validator(list_id=str, subscriber=str, role=enum_validator(MemberRole), _optional=('list_id', 'subscriber', 'role')) try: members = service.find_members(**validator(request)) except ValueError as error: bad_request(response, str(error)) else: resource = _FoundMembers(members, self.api_version) okay(response, etag(resource._make_collection(request)))
def on_post(self, request, response): """Create a new mailing list.""" try: validator = Validator(fqdn_listname=str, style_name=str, _optional=('style_name', )) mlist = create_list(**validator(request)) except ListAlreadyExistsError: bad_request(response, b'Mailing list exists') except BadDomainSpecificationError as error: reason = 'Domain does not exist: {}'.format(error.domain) bad_request(response, reason.encode('utf-8')) else: location = self.api.path_to('lists/{0}'.format(mlist.list_id)) created(response, location)
def _patch_put(self, request, response, is_optional): kws = {uri: str for uri in self.URIs} optionals = ['username', 'password'] if is_optional: optionals.extend(self.URIs) # When PATCHing or PUTing all uris, a single optional # username/password applies to them all. kws['username'] = str kws['password'] = str kws['_optional'] = optionals try: arguments = Validator(**kws)(request) except ValueError as error: bad_request(response, str(error)) return username = arguments.pop('username', None) password = arguments.pop('password', None) if not username and not password: # Normalize arguments. set_kws = {} elif username and password: # It's fine if both are specified. set_kws = dict(username=username, password=password) else: bad_request(response, 'Specify both username and password, or neither') return manager = getUtility(ITemplateManager) for key, value in arguments.items(): if len(value) == 0: # The empty string is equivalent to DELETE. Yeah, this isn't # very RESTful, but practicality beats purity. manager.delete(key, self._raw_context) else: manager.set(key, self._raw_context, value, **set_kws) no_content(response)
def on_delete(self, request, response): """DELETE to /domains/<domain>/owners""" if self._domain is None: not_found(response) try: # No arguments. Validator()(request) except ValueError as error: bad_request(response, str(error)) return owner_email = [ owner.addresses[0].email for owner in self._domain.owners ] for email in owner_email: self._domain.remove_owner(email) return no_content(response)
def patch_put(self, request, response, is_optional): domain = getUtility(IDomainManager).get(self._domain) if domain is None: not_found(response) kws = dict( description=GetterSetter(str), owner=ListOfDomainOwners(list_of_strings_validator), ) if is_optional: # For a PATCH, all attributes are optional. kws['_optional'] = kws.keys() try: Validator(**kws).update(domain, request) except ValueError as error: bad_request(response, str(error)) else: no_content(response)
def on_get(self, request, response): validator = Validator( token_owner=enum_validator(TokenOwner), request_type=enum_validator(PendType), _optional=['token_owner', 'request_type']) try: data = validator(request) except ValueError as error: bad_request(response, str(error)) else: token_owner = data.pop('token_owner', None) pend_type = data.pop('request_type', PendType.subscription) count = getUtility(IPendings).count( mlist=self._mlist, pend_type=pend_type.name, token_owner=token_owner) okay(response, etag(dict(count=count)))
def on_delete(self, request, response): """Delete the members of the named mailing list.""" status = {} try: validator = Validator(emails=list_of_strings_validator) arguments = validator(request) except ValueError as error: bad_request(response, str(error)) return emails = arguments.pop('emails') success, fail = getUtility(ISubscriptionService).unsubscribe_members( self._mlist.list_id, emails) # There should be no email in both sets. assert success.isdisjoint(fail), (success, fail) status.update({email: True for email in success}) status.update({email: False for email in fail}) okay(response, etag(status))
def on_post(self, request, response): """Create a new domain.""" domain_manager = getUtility(IDomainManager) try: validator = Validator(mail_host=str, description=str, base_url=str, contact_address=str, _optional=('description', 'base_url', 'contact_address')) domain = domain_manager.add(**validator(request)) except BadDomainSpecificationError: bad_request(response, b'Domain exists') except ValueError as error: bad_request(response, str(error)) else: created(response, path_to('domains/{0}'.format(domain.mail_host)))
def on_patch(self, request, response): """Patch an existing Address.""" if self._address is None: not_found(response) else: # The only attribute of a address that can be PATCH'd is the # display_name. To change the verified_on, use the /verify # endpoint. validator = Validator(display_name=str) try: data = validator(request) except ValueError as error: bad_request(response, str(error)) return display_name = data.pop('display_name') self._address.display_name = display_name no_content(response)
def on_put(self, request, response): """Set a mailing list configuration.""" attribute = self._attribute if attribute is None: # This is a request to update all the list's writable # configuration variables. All must be provided in the request. validator = Validator(**VALIDATORS) try: validator.update(self._mlist, request) except ValueError as error: # Unlike the case where we're PUTting to a specific # configuration sub-resource, if we're PUTting to the list's # entire configuration, but the request has a bogus attribute, # the entire request is considered bad. We can also get here # if one of the attributes is read-only. The error will # contain sufficient details, so just return it as the reason. bad_request(response, str(error)) return elif attribute not in ATTRIBUTES: # Here we're PUTting to a specific resource, but that attribute is # bogus so the URL is considered pointing to a missing resource. not_found(response, 'Unknown attribute: {}'.format(attribute)) return elif ATTRIBUTES[attribute].decoder is None: bad_request( response, 'Read-only attribute: {}'.format(attribute)) return else: # We're PUTting to a specific configuration sub-resource. validator = Validator(**{attribute: VALIDATORS[attribute]}) try: validator.update(self._mlist, request) except ValueError as error: bad_request(response, str(error)) return no_content(response)