def from_elem(elem): type = elem.get('{%s}type' % XMLNS_XSI) if type == 'CanonicalUser': value = elem.find('./ID').text return User(value) elif type == 'Group': value = elem.find('./URI').text subclass = get_group_subclass_from_uri(value) return subclass() elif type == 'AmazonCustomerByEmail': raise S3NotImplemented() else: raise MalformedACLError()
def swift_acl_translate(acl, group='', user='', xml=False): """ Takes an S3 style ACL and returns a list of header/value pairs that implement that ACL in Swift, or "NotImplemented" if there isn't a way to do that yet. """ swift_acl = {} swift_acl['public-read'] = [['X-Container-Read', '.r:*,.rlistings']] # Swift does not support public write: # https://answers.launchpad.net/swift/+question/169541 swift_acl['public-read-write'] = [['X-Container-Write', '.r:*'], ['X-Container-Read', '.r:*,.rlistings']] # TODO: if there's a way to get group and user, this should work for # private: # swift_acl['private'] = \ # [['HTTP_X_CONTAINER_WRITE', group + ':' + user], \ # ['HTTP_X_CONTAINER_READ', group + ':' + user]] swift_acl['private'] = [['X-Container-Write', '.'], ['X-Container-Read', '.']] if xml: # We are working with XML and need to parse it try: elem = fromstring(acl, 'AccessControlPolicy') except (XMLSyntaxError, DocumentInvalid): raise MalformedACLError() acl = 'unknown' for grant in elem.findall('./AccessControlList/Grant'): permission = grant.find('./Permission').text grantee = grant.find('./Grantee').get('{%s}type' % XMLNS_XSI) if permission == "FULL_CONTROL" and grantee == 'CanonicalUser' and\ acl != 'public-read' and acl != 'public-read-write': acl = 'private' elif permission == "READ" and grantee == 'Group' and\ acl != 'public-read-write': acl = 'public-read' elif permission == "WRITE" and grantee == 'Group': acl = 'public-read-write' else: acl = 'unsupported' if acl == 'authenticated-read': raise S3NotImplemented() elif acl not in swift_acl: raise ACLError() return swift_acl[acl]
def from_header(grantee): """ Convert a grantee string in the HTTP header to an Grantee instance. """ type, value = grantee.split('=', 1) value = value.strip('"\'') if type == 'id': return User(value) elif type == 'emailAddress': raise S3NotImplemented() elif type == 'uri': # return a subclass instance of Group class subclass = get_group_subclass_from_uri(value) return subclass() else: raise InvalidArgument(type, value, 'Argument format not recognized')
def xml(self, max_length, check_md5=False): """ Similar to swob.Request.body, but it checks the content length before creating a body string. """ if self.headers.get('transfer-encoding'): # FIXME: Raise error only when the input body is larger than # 'max_length'. raise S3NotImplemented('A header you provided implies ' 'functionality that is not implemented', header='Transfer-Encoding') if self.message_length() > max_length: raise MalformedXML() body = swob.Request.body.fget(self) if check_md5: self.check_md5(body) return body
def xml(self, max_length, check_md5=False): """ Similar to swob.Request.body, but it checks the content length before creating a body string. """ te = self.headers.get('transfer-encoding', '') te = [x.strip() for x in te.split(',') if x.strip()] if te and (len(te) > 1 or te[-1] != 'chunked'): raise S3NotImplemented('A header you provided implies ' 'functionality that is not implemented', header='Transfer-Encoding') if self.message_length() > max_length: raise MalformedXML() # Limit the read similar to how SLO handles manifests body = self.body_file.read(max_length) if check_md5: self.check_md5(body) return body
def controller(self): if self.is_service_request: return ServiceController if not self.slo_enabled: multi_part = ['partNumber', 'uploadId', 'uploads'] if len([p for p in multi_part if p in self.params]): LOGGER.warning('multipart: No SLO middleware in pipeline') raise S3NotImplemented("Multi-part feature isn't support") if 'acl' in self.params: if CONF.s3_acl: return S3AclController else: return AclController if 'delete' in self.params: return MultiObjectDeleteController if 'location' in self.params: return LocationController if 'logging' in self.params: return LoggingStatusController if 'partNumber' in self.params: return PartController if 'uploadId' in self.params: return UploadController if 'uploads' in self.params: return UploadsController if 'versioning' in self.params: return VersioningController unsupported = ('notification', 'policy', 'requestPayment', 'torrent', 'website', 'cors', 'tagging', 'restore') if set(unsupported) & set(self.params): return UnsupportedController if self.is_object_request: return ObjectController return BucketController
def POST(self, req): """ Handles Delete Multiple Objects. """ def object_key_iter(elem): for obj in elem.iterchildren('Object'): key = obj.find('./Key').text if not key: raise UserKeyMustBeSpecified() version = obj.find('./VersionId') if version is not None: version = version.text yield key, version try: xml = req.xml(MAX_MULTI_DELETE_BODY_SIZE, check_md5=True) elem = fromstring(xml, 'Delete') quiet = elem.find('./Quiet') if quiet is not None and quiet.text.lower() == 'true': self.quiet = True else: self.quiet = False delete_list = list(object_key_iter(elem)) if len(delete_list) > CONF.max_multi_delete_objects: raise MalformedXML() except (XMLSyntaxError, DocumentInvalid): raise MalformedXML() except ErrorResponse: raise except Exception as e: exc_type, exc_value, exc_traceback = sys.exc_info() LOGGER.error(e) raise exc_type, exc_value, exc_traceback elem = Element('DeleteResult') # check bucket existence try: req.get_response(self.app, 'HEAD') except AccessDenied as error: body = self._gen_error_body(error, elem, delete_list) return HTTPOk(body=body) for key, version in delete_list: if version is not None: # TODO: delete the specific version of the object raise S3NotImplemented() req.object_name = key try: query = req.gen_multipart_manifest_delete_query(self.app) req.get_response(self.app, method='DELETE', query=query) except ErrorResponse as e: error = SubElement(elem, 'Error') SubElement(error, 'Key').text = key SubElement(error, 'Code').text = e.__class__.__name__ SubElement(error, 'Message').text = e._msg continue if not self.quiet: deleted = SubElement(elem, 'Deleted') SubElement(deleted, 'Key').text = key body = tostring(elem) return HTTPOk(body=body)
def _validate_headers(self): if 'CONTENT_LENGTH' in self.environ: try: if self.content_length < 0: raise InvalidArgument('Content-Length', self.content_length) except (ValueError, TypeError): raise InvalidArgument('Content-Length', self.environ['CONTENT_LENGTH']) if 'Date' in self.headers: now = datetime.datetime.utcnow() date = email.utils.parsedate(self.headers['Date']) if 'Expires' in self.params: try: d = email.utils.formatdate(float(self.params['Expires'])) except ValueError: raise AccessDenied() # check expiration expdate = email.utils.parsedate(d) ex = datetime.datetime(*expdate[0:6]) if now > ex: raise AccessDenied('Request has expired') elif date is not None: epoch = datetime.datetime(1970, 1, 1, 0, 0, 0, 0) d1 = datetime.datetime(*date[0:6]) if d1 < epoch: raise AccessDenied() # If the standard date is too far ahead or behind, it is an # error delta = datetime.timedelta(seconds=60 * 5) if abs(d1 - now) > delta: raise RequestTimeTooSkewed() else: raise AccessDenied() if 'Content-MD5' in self.headers: value = self.headers['Content-MD5'] if not re.match('^[A-Za-z0-9+/]+={0,2}$', value): # Non-base64-alphabet characters in value. raise InvalidDigest(content_md5=value) try: self.headers['ETag'] = value.decode('base64').encode('hex') except Exception: raise InvalidDigest(content_md5=value) if 'X-Amz-Copy-Source' in self.headers: try: check_path_header(self, 'X-Amz-Copy-Source', 2, '') except swob.HTTPException: msg = 'Copy Source must mention the source bucket and key: ' \ 'sourcebucket/sourcekey' raise InvalidArgument('x-amz-copy-source', self.headers['X-Amz-Copy-Source'], msg) if 'x-amz-metadata-directive' in self.headers: value = self.headers['x-amz-metadata-directive'] if value not in ('COPY', 'REPLACE'): err_msg = 'Unknown metadata directive.' raise InvalidArgument('x-amz-metadata-directive', value, err_msg) if 'x-amz-storage-class' in self.headers: # Only STANDARD is supported now. if self.headers['x-amz-storage-class'] != 'STANDARD': raise InvalidStorageClass() if 'x-amz-mfa' in self.headers: raise S3NotImplemented('MFA Delete is not supported.') if 'x-amz-server-side-encryption' in self.headers: raise S3NotImplemented('Server-side encryption is not supported.') if 'x-amz-website-redirect-location' in self.headers: raise S3NotImplemented('Website redirection is not supported.')
def elem(self): """ Get an etree element of this instance. """ raise S3NotImplemented()
def __contains__(self, key): """ The key argument is a S3 user id. This method checks that the user id belongs to this class. """ raise S3NotImplemented()
def _validate_headers(self): if 'CONTENT_LENGTH' in self.environ: try: if self.content_length < 0: raise InvalidArgument('Content-Length', self.content_length) except (ValueError, TypeError): raise InvalidArgument('Content-Length', self.environ['CONTENT_LENGTH']) self._validate_dates() value = _header_strip(self.headers.get('Content-MD5')) if value is not None: if not re.match('^[A-Za-z0-9+/]+={0,2}$', value): # Non-base64-alphabet characters in value. raise InvalidDigest(content_md5=value) try: self.headers['ETag'] = value.decode('base64').encode('hex') except Exception: raise InvalidDigest(content_md5=value) if len(self.headers['ETag']) != 32: raise InvalidDigest(content_md5=value) if self.method == 'PUT' and any(h in self.headers for h in ( 'If-Match', 'If-None-Match', 'If-Modified-Since', 'If-Unmodified-Since')): raise S3NotImplemented( 'Conditional object PUTs are not supported.') if 'X-Amz-Copy-Source' in self.headers: try: check_path_header(self, 'X-Amz-Copy-Source', 2, '') except swob.HTTPException: msg = 'Copy Source must mention the source bucket and key: ' \ 'sourcebucket/sourcekey' raise InvalidArgument('x-amz-copy-source', self.headers['X-Amz-Copy-Source'], msg) if 'x-amz-metadata-directive' in self.headers: value = self.headers['x-amz-metadata-directive'] if value not in ('COPY', 'REPLACE'): err_msg = 'Unknown metadata directive.' raise InvalidArgument('x-amz-metadata-directive', value, err_msg) if 'x-amz-storage-class' in self.headers: # Only STANDARD is supported now. if self.headers['x-amz-storage-class'] != 'STANDARD': raise InvalidStorageClass() if 'x-amz-mfa' in self.headers: raise S3NotImplemented('MFA Delete is not supported.') if 'x-amz-server-side-encryption' in self.headers: raise S3NotImplemented('Server-side encryption is not supported.') if 'x-amz-website-redirect-location' in self.headers: raise S3NotImplemented('Website redirection is not supported.')
def POST(self, req): """ Handle POST Bucket request """ raise S3NotImplemented()
def __init__(self, app, **kwargs): raise S3NotImplemented('The requested resource is not implemented')
def PUT(self, req): """ Handles PUT Bucket logging. """ raise S3NotImplemented()
def POST(self, req): raise S3NotImplemented()