def authorize_anonymous(self, req): """ Authorize an anonymous request. :returns: None if authorization is granted, an error page otherwise. """ try: part = req.split_path(1, 4, True) version, account, container, obj = part except ValueError: return HTTPNotFound(request=req) #allow OPTIONS requests to proceed as normal if req.method == 'OPTIONS': return is_authoritative_authz = (account and account.startswith(self.reseller_prefix)) if not is_authoritative_authz: return self.denied_response(req) referrers, roles = osd_acl.parse_acl(getattr(req, 'acl', None)) authorized = self._authorize_unconfirmed_identity( req, obj, referrers, roles) if not authorized: return self.denied_response(req)
def get(self, filesystem, acc_dir, cont_dir, account, container, req): """ Handles GET request for container param:filesystem: Filesystem name param:account: Account name param:container: Container name param:req: HTTP Request object """ try: # create path path = self.create_path(filesystem, acc_dir, cont_dir, account, container) self.logger.debug(('GET container called for path: %(path)s'), {'path' : path}) # get headers for response headers = self.__head_or_get(path) if headers == HTTP_INTERNAL_SERVER_ERROR: self.logger.debug('Internal error raised from library in HEAD') raise HTTPInternalServerError(request=req) elif headers == HTTP_NOT_FOUND: self.logger.debug('File not found error raised from ' 'library in HEAD') raise HTTPNotFound(request=req) else: out_content_type = get_listing_content_type(req) headers['Content-Type'] = out_content_type # get response to be send ret = self.__get_cont_list(path, container, req) if ret == OsdExceptionCode.OSD_INTERNAL_ERROR: self.logger.debug('Internal error raised from library in GET') raise HTTPInternalServerError(request=req) elif ret == OsdExceptionCode.OSD_NOT_FOUND: self.logger.debug('File not found error raised from ' 'library in GET') raise HTTPNotFound(request=req) else: pass return ret, headers except HTTPException as error: self.logger.exception(error) raise error except Exception as err: self.logger.error(('GET request failed for ' 'container: %(container)s '), {'container' : container}) self.logger.exception(err) raise err
def head(self, filesystem, acc_dir, cont_dir, account, container, req): """ Handles HEAD request for container param:filesystem: Filesystem name param:account: Account name param:container: Container name param:req: HTTP Request object """ try: # create path path, headers = '', '' if 'x-updater-request' in req.headers: path = self.create_updater_path(filesystem, acc_dir, cont_dir, account, container) self.logger.debug(('HEAD container called for path: %(path)s'), {'path' : path}) # get headers for updater request headers = self.__updater_headers(path , True) #pass True in updater request else: path = self.create_path(filesystem, acc_dir, cont_dir, account, container) self.logger.debug(('HEAD container called for path: %(path)s'), {'path' : path}) # get headers for request headers = self.__head_or_get(path) if headers == HTTP_INTERNAL_SERVER_ERROR: self.logger.debug('Internal error raised from library') raise HTTPInternalServerError(request=req) if headers == HTTP_NOT_FOUND and 'x-updater-request' in req.headers: self.logger.debug('File not found error raised from library: updater case') raise HTTPNotFound(request=req) elif headers == HTTP_NOT_FOUND: self.logger.debug('File not found error raised from library') raise HTTPNotFound(request=req) else: out_content_type = get_listing_content_type(req) headers['Content-Type'] = out_content_type return headers except HTTPException as error: self.logger.exception(error) raise error except Exception as err: self.logger.error \ (('HEAD request failed for container: %(container)s '), {'container' : container}) self.logger.exception(err) raise err
def BULK_DELETE(self, req): """HTTP BULK_DELETE request handler.""" self.app.logger.debug("In BULK_DELETE____") account_node, account_filesystem, account_directory, container_count, \ account_component_number, head_status = self.account_info(\ self.account_name, req) if not account_node and not head_status: return HTTPInternalServerError(request=req) if head_status and int(str(head_status).split()[0]) == 503: #TODO need to check why head_status is int or sometimes str self.app.logger.info("account HEAD returning 503 service " \ "unavailable error due to which this request got failed") return HTTPServiceUnavailable(request=req) if not account_node: return HTTPNotFound(request=req) container_node, container_filesystem, container_directory, \ global_map_version, component_number = \ self.app.container_ring.get_node( \ self.account_name, self.container_name) if not len(container_node): self.app.logger.error( _('%(msg)s %(method)s %(path)s'), { 'msg': _('ERROR Wrong ring file content'), 'method': 'BULK_DELETE', 'path': req.path }) return HTTPInternalServerError(request=req) self.app.logger.debug("Going for backend call______") try: headers = self._backend_requests(req, len(container_node), account_node, account_filesystem, account_directory, \ global_map_version, component_number, account_component_number) except ZeroDivisionError: self.app.logger.error( _('%(msg)s %(method)s %(path)s'), { 'msg': _('ERROR Wrong ring file content'), 'method': 'BULK_DELETE', 'path': req.path }) return HTTPInternalServerError(request=req) clear_info_cache(self.app, req.environ, self.account_name, self.container_name) self.app.logger.debug("Now going to make request_____") resp = self.make_request_for_bulk_delete( req, self.app.container_ring, container_node, container_filesystem, container_directory, 'BULK_DELETE', req.swift_entity_path, headers) return resp
def handle_request(self, req): """ Entry point for auth requests (ones that match the self.auth_prefix). Should return a WSGI-style callable (such as swob.Response). :param req: swob.Request object """ req.start_time = time() handler = None try: version, account, user, _junk = req.split_path(1, 4, True) except ValueError: self.logger.increment('errors') return HTTPNotFound(request=req) if version in ('v1', 'v1.0', 'auth'): if req.method == 'GET': handler = self.handle_get_token if not handler: self.logger.increment('errors') req.response = HTTPBadRequest(request=req) else: req.response = handler(req) return req.response
def get_slo_segments(self, obj_name, req): """ Performs a swob.Request and returns the SLO manifest's segments. :raises HTTPServerError: on unable to load obj_name or on unable to load the SLO manifest data. :raises HTTPBadRequest: on not an SLO manifest :raises HTTPNotFound: on SLO manifest not found :returns: SLO manifest's segments """ vrs, account, _junk = req.split_path(2, 3, True) new_env = req.environ.copy() new_env['REQUEST_METHOD'] = 'GET' del (new_env['wsgi.input']) new_env['QUERY_STRING'] = 'multipart-manifest=get' new_env['CONTENT_LENGTH'] = 0 new_env['HTTP_USER_AGENT'] = \ '%s MultipartDELETE' % new_env.get('HTTP_USER_AGENT') new_env['swift.source'] = 'SLO' new_env['PATH_INFO'] = ( '/%s/%s/%s' % (vrs, account, obj_name.lstrip('/'))).encode('utf-8') resp = Request.blank('', new_env).get_response(self.app) if resp.is_success: if config_true_value(resp.headers.get('X-Static-Large-Object')): try: return json.loads(resp.body) except ValueError: raise HTTPServerError('Unable to load SLO manifest') else: raise HTTPBadRequest('Not an SLO manifest') elif resp.status_int == HTTP_NOT_FOUND: raise HTTPNotFound('SLO manifest not found') elif resp.status_int == HTTP_UNAUTHORIZED: raise HTTPUnauthorized('401 Unauthorized') else: raise HTTPServerError('Unable to load SLO manifest or segment.')
class AccountServiceInterface: """An interface class between account service and library.""" account_lib_obj = libaccountLib.AccountLibraryImpl() asyn_helper = AsyncLibraryHelper(account_lib_obj) @classmethod def start_event_wait_func(cls, logger): logger.info('wait_event called!') AccountServiceInterface.asyn_helper.wait_event() @classmethod def stop_event_wait_func(cls, logger): logger.info('wait_event_stop called!') AccountServiceInterface.asyn_helper.wait_event_stop() @classmethod def get_metadata(self, req, logger): """ Creates metadata string from request object and container stat if present param: Request object Returns metadata dictionary """ try: new_meta = {} metadata = {} # get metadata from request headers metadata.update((key.lower(), value) for key, value in req.headers.iteritems() if key.lower() in HEADERS or is_sys_or_user_meta('account', key)) for key, value in metadata.iteritems(): if key == 'x-account-read': new_meta.update({'r-': value}) elif key == 'x-account-write': new_meta.update({'w-': value}) else: ser_key = key.split('-')[2] if ser_key == 'meta': new_key = '%s-%s' % ('m', key.split('-', 3)[-1]) new_meta.update({new_key: value}) elif ser_key == 'sysmeta': new_key = '%s-%s' % ('sm', key.split('-', 3)[-1]) new_meta.update({new_key: value}) else: logger.debug('Expected metadata not found') return new_meta except Exception as err: logger.error( ('get_metadata failed ', 'close failure: %(exc)s : %(stack)s'), { 'exc': err, 'stack': ''.join(traceback.format_stack()) }) raise err @classmethod def create_AccountStat_object(cls, info): """An interface to create an object of AccountStat, from info map""" if 'account' not in info: info['account'] = '-1' if 'created_at' not in info: info['created_at'] = '0' if 'put_timestamp' not in info: info['put_timestamp'] = '0' if 'delete_timestamp' not in info: info['delete_timestamp'] = '0' if 'container_count' not in info: info['container_count'] = 0 if 'object_count' not in info: info['object_count'] = 0 if 'bytes_used' not in info: info['bytes_used'] = 0 if 'hash' not in info: info['hash'] = '-1' if 'id' not in info: info['id'] = '-1' if 'status' not in info: info['status'] = '-1' if 'status_changed_at' not in info: info['status_changed_at'] = '0' if 'metadata' not in info: info['metadata'] = {} return libaccountLib.AccountStat( info['account'], info['created_at'], normalize_timestamp(\ info['put_timestamp']), normalize_timestamp(\ info['delete_timestamp']), info['container_count'], info['object_count'], info['bytes_used'], info['hash'], info['id'], info['status'], info['status_changed_at'], info['metadata'] ) @classmethod def create_container_record(cls, name, hash, info, deleted): """An interface to create an object of ContainerRecord, from info map""" if 'x-object-count' not in info: info['x-object-count'] = 0 if not info['x-put-timestamp']: info['x-put-timestamp'] = '0' if not info['x-delete-timestamp']: info['x-delete-timestamp'] = '0' return libaccountLib.ContainerRecord(0, name, hash, \ normalize_timestamp(str(info['x-put-timestamp'])), \ normalize_timestamp(str(info['x-delete-timestamp'])), \ long(info['x-object-count']), \ long(info['x-bytes-used']), deleted) @classmethod def create_container_record_for_updater(cls, name, hash, info): """An interface to create an object of ContainerRecord for account \ updater, from info map """ if 'x-object-count' not in info: info['x-object-count'] = 0 if not info['put_timestamp']: info['put_timestamp'] = '0' if not info['delete_timestamp']: info['delete_timestamp'] = '0' return libaccountLib.ContainerRecord(0, name, hash, \ normalize_timestamp(str(info['put_timestamp'])), \ normalize_timestamp(str(info['delete_timestamp'])), \ long(info['object_count']), \ long(info['bytes_used']), \ info['deleted']) @classmethod def list_account(cls, temp_path, account_path, out_content_type, req, limit, marker, \ end_marker, prefix, delimiter, logger): """An interface to list the containers in account.""" logger.debug("Get account stats for path: %s" % account_path) container_record_list = [] resp = libaccountLib.AccountStatWithStatus() AccountServiceInterface.__get_account_stat(resp, temp_path, account_path, logger) logger.info("Account library responded with: %s for get_account_stat \ in GET" % resp.get_return_status()) if resp.get_return_status() == INFO_FILE_OPERATION_SUCCESS: resp_headers = { 'X-Account-Container-Count': \ resp.get_account_stat().get_container_count(), 'X-Account-Object-Count': \ resp.get_account_stat().get_object_count(), 'X-Account-Bytes-Used': resp.get_account_stat().get_bytes_used(), 'X-Timestamp': resp.get_account_stat().get_created_at(), 'X-PUT-Timestamp': resp.get_account_stat().get_put_timestamp() } modified_meta = {} for key, value in resp.get_account_stat().get_metadata().iteritems( ): if key == 'r-': modified_meta.update({'x-account-read': value}) elif key == 'w-': modified_meta.update({'x-account-write': value}) else: ser_key = key.split('-')[0] if ser_key == 'm': key = 'x-account-meta-' + key.split('-', 1)[1] modified_meta.update({key: value}) resp_headers.update( (key, value) for (key, value) in modified_meta.iteritems()) if limit: logger.debug("Calling list_container") resp = libaccountLib.ListContainerWithStatus() try: AccountServiceInterface.asyn_helper.call("list_container", \ resp, account_path, limit, marker, \ end_marker, prefix, delimiter) except Exception, err: logger.error(('list_container for %(ac_dir)s failed ' \ 'close failure: %(exc)s : %(stack)s'), {'ac_dir' : \ account_path, 'exc': err, 'stack': \ ''.join(traceback.format_stack())}) raise err logger.info("Account library responded with: %s for \ list_container in GET" % resp.get_return_status()) if resp.get_return_status() == INFO_FILE_OPERATION_SUCCESS: container_record_list = resp.get_container_record() if delimiter: container_list_new = [] for obj in container_record_list: name = obj.get_name() if prefix: match = re.match("^" + prefix + ".*", name) if match: replace = re.sub("^" + prefix, '', name) replace = replace.split(delimiter) if len(replace) >= 2: obj.set_name(prefix + replace[0] + delimiter) if marker != obj.get_name( ) or marker > obj.get_name(): container_list_new.append((obj, (0, 1)[delimiter in obj.get_name() and \ obj.get_name().endswith(delimiter)])) else: obj.set_name(prefix + replace[0]) if marker != obj.get_name( ) or marker > obj.get_name(): container_list_new.append((obj, 0)) else: replace = name.split(delimiter) if len(replace) >= 2: obj.set_name(replace[0] + delimiter) if marker != obj.get_name( ) or marker > obj.get_name(): container_list_new.append((obj, (0, 1)[delimiter in obj.get_name() and \ obj.get_name().endswith(delimiter)])) container_record_list = container_list_new else: container_record_list = [ (record, 0) for record in container_record_list ] if resp.get_return_status() == INFO_FILE_OPERATION_SUCCESS: logger.debug("Populating container list") elif resp.get_return_status() == INFO_FILE_NOT_FOUND: return HTTPNotFound(request=req, charset='utf-8') elif resp.get_return_status() == INFO_FILE_DELETED: headers = {'X-Account-Status': 'Deleted'} return HTTPNotFound(request=req, headers=headers, charset='utf-8', body='') else: return HTTPInternalServerError(request=req) if out_content_type == 'application/json': data = [] for (container_record, is_subdir) in container_record_list: if is_subdir: data.append({'subdir': container_record.get_name()}) else: data.append({ 'name': container_record.get_name(), 'created_at': time.strftime( "%a, %d %b %Y %H:%M:%S GMT", time.gmtime( float(container_record.get_put_timestamp()))), 'count': container_record.get_object_count(), 'bytes': container_record.get_bytes_used() }) container_record_list = json.dumps(data) elif out_content_type.endswith('/xml'): #Directly used req.path to get account name. output_list = ['<?xml version="1.0" encoding="UTF-8"?>', '<account name=%s>' % \ saxutils.quoteattr(req.path.split('/')[3])] for (container_record, is_subdir) in container_record_list: if is_subdir: output_list.append( '<subdir name=%s />' % saxutils.quoteattr(container_record.get_name())) else: item = '<container><name>%s</name><created_at>%s</created_at><count>%s</count>' \ '<bytes>%s</bytes></container>' % \ (saxutils.escape(container_record.get_name()), \ time.strftime("%a, %d %b %Y %H:%M:%S GMT",time.gmtime(float(container_record.get_put_timestamp()))), \ container_record.get_object_count(), \ container_record.get_bytes_used() ) output_list.append(item) output_list.append('</account>') container_record_list = '\n'.join(output_list) else: if not container_record_list: logger.debug("No any container in account") resp = HTTPNoContent(request=req, headers=resp_headers) resp.content_type = out_content_type resp.charset = 'utf-8' return resp container_record_list = '\n'.join(container_record.get_name() for \ (container_record, is_subdir) in container_record_list) + '\n' ret = HTTPOk(body=container_record_list, request=req, headers=resp_headers) ret.content_type = out_content_type ret.charset = 'utf-8' return ret
def handle_request(self, req): """ Entry point for proxy server. Should return a WSGI-style callable (such as swob.Response). :param req: swob.Request object """ try: if 'swift.trans_id' not in req.environ: # if this wasn't set by an earlier middleware, set it now trans_id = generate_trans_id(self.trans_id_suffix) req.environ['swift.trans_id'] = trans_id self.logger.txn_id = trans_id self.logger.info( 'Proxy service received request for %(method)s ' '%(path)s with %(headers)s (txn: %(trans_id)s)', { 'method': req.method, 'path': req.path, 'headers': req.headers, 'trans_id': self.logger.txn_id }) req.headers['x-trans-id'] = req.environ['swift.trans_id'] self.logger.set_statsd_prefix('proxy-server') #if self.stop_service_flag: # self.logger.info('Proxy is going to stop therefore no more request') # return HTTPForbidden(request=req, body='Proxy is going to stop\ # therefore no more request') #TODO would it be HTTPForbidden or something else if req.content_length and req.content_length < 0: self.logger.increment('errors') return HTTPBadRequest(request=req, body='Invalid Content-Length') try: if not check_utf8(req.path_info): self.logger.increment('errors') return HTTPPreconditionFailed( request=req, body='Invalid UTF8 or contains NULL') except UnicodeError: self.logger.increment('errors') return HTTPPreconditionFailed( request=req, body='Invalid UTF8 or contains NULL') try: error_response = constraints.check_non_allowed_headers(req) if error_response: return error_response obj_list = '' if ('bulk-delete' in req.params or 'X-Bulk-Delete' in \ req.headers) and req.method in ['POST', 'DELETE']: self.logger.debug("*** Bulk delete request ***") cont, obj_list = get_objs_to_delete(req, \ self.max_bulk_delete_entries, self.logger) self.logger.debug("Container: %s, Obj list: %s" \ % (cont, obj_list)) version, account, container = \ split_path(req.path, 1, 3, True) if container: container = container.strip('/') if cont != container: self.logger.error("Container in path is different") return HTTPBadRequest(request=req, \ body='Container name mismatch') req.path_info = req.path.rstrip('/') else: req.path_info = os.path.join(req.path, cont) req.method = 'BULK_DELETE' req.headers['Content-Length'] = len(str(obj_list)) req.body = str(obj_list) controller, path_parts = self.get_controller( req.path, req.method) p = req.path_info if isinstance(p, unicode): p = p.encode('utf-8') except ValueError: self.logger.increment('errors') return HTTPNotFound(request=req) if not controller: self.logger.increment('errors') return HTTPPreconditionFailed(request=req, body='Bad URL') if self.deny_host_headers and \ req.host.split(':')[0] in self.deny_host_headers: return HTTPForbidden(request=req, body='Invalid host header') self.logger.set_statsd_prefix('proxy-server.' + controller.server_type.lower()) controller = controller(self, **path_parts) controller.trans_id = req.environ['swift.trans_id'] #self.logger.client_ip = get_remote_client(req) try: handler = getattr(controller, req.method) getattr(handler, 'publicly_accessible') except AttributeError: allowed_methods = getattr(controller, 'allowed_methods', set()) return HTTPMethodNotAllowed( request=req, headers={'Allow': ', '.join(allowed_methods)}) if 'swift.authorize' in req.environ: # We call authorize before the handler, always. If authorized, # we remove the swift.authorize hook so isn't ever called # again. If not authorized, we return the denial unless the # controller's method indicates it'd like to gather more # information and try again later. resp = req.environ['swift.authorize'](req) if not resp: # No resp means authorized, no delayed recheck required. del req.environ['swift.authorize'] else: # Response indicates denial, but we might delay the denial # and recheck later. If not delayed, return the error now. if not getattr(handler, 'delay_denial', None): return resp # Save off original request method (GET, POST, etc.) in case it # gets mutated during handling. This way logging can display the # method the client actually sent. req.environ['swift.orig_req_method'] = req.method if not req.method == "STOP_SERVICE": self.ongoing_operation_list.append(req) resp = handler(req) self.logger.info( 'Proxy returning response %(status)s for ' '%(method)s %(path)s with %(headers)s ' '(txn: %(trans_id)s)', { 'status': resp.status, 'method': req.method, 'path': req.path, 'headers': resp.headers, 'trans_id': req.headers['x-trans-id'] }) return resp except HTTPException as error_response: return error_response except (Exception, Timeout): self.logger.exception(_('ERROR Unhandled exception in request')) return HTTPServerError(request=req)
def DELETE(self, req): """HTTP DELETE request handler.""" # account_partition, accounts, container_count = \ # self.account_info(self.account_name, req) # if not accounts: account_node, account_filesystem, account_directory, container_count, \ account_component_number, head_status = self.account_info(\ self.account_name, req) if not account_node and not head_status: return HTTPInternalServerError(request=req) if head_status and int(str(head_status).split()[0]) == 503: self.app.logger.info("account HEAD returning 503 service" \ "unavailable error due to which this request got failed") return HTTPServiceUnavailable(request=req) if head_status and int(str(head_status).split()[0]) == 500: return HTTPInternalServerError(request=req) if not account_node: return HTTPNotFound(request=req) # container_partition, containers = self.app.container_ring.get_nodes( # # self.account_name, self.container_name) container_node, container_filesystem, container_directory, \ global_map_version, component_number = \ self.app.container_ring.get_node( \ self.account_name, self.container_name) if not len(container_node): self.app.logger.error( _('%(msg)s %(method)s %(path)s'), { 'msg': _('ERROR Wrong ring file content'), 'method': 'DELETE', 'path': req.path }) return HTTPInternalServerError(request=req) # headers = self._backend_requests(req, len(containers), # account_partition, accounts) try: headers = self._backend_requests(req, len(container_node), account_node, account_filesystem, account_directory, global_map_version, component_number, account_component_number) except ZeroDivisionError: self.app.logger.error( _('%(msg)s %(method)s %(path)s'), { 'msg': _('ERROR Wrong ring file content'), 'method': 'DELETE', 'path': req.path }) return HTTPInternalServerError(request=req) clear_info_cache(self.app, req.environ, self.account_name, self.container_name) # resp = self.make_requests( # req, self.app.container_ring, container_partition, 'DELETE', # # req.swift_entity_path, headers) if not self.is_req_blocked(_('Container'), component_number): #return HTTPTemporaryRedirect(request=req, body = 'Component' # 'movement is in progress') resp = self.make_requests(req, self.app.container_ring, container_node, container_filesystem, container_directory, 'DELETE', req.swift_entity_path, headers) resp = self.container_req_for_comp_distribution(req, \ component_number, global_map_version, self.account_name, \ self.container_name, resp) if int(resp.status.split()[0]) in (301, 307): resp = HTTPInternalServerError(request=req) # Indicates no server had the container if resp.status_int == HTTP_ACCEPTED: return HTTPNotFound(request=req) return resp
def POST(self, req): """HTTP POST request handler.""" error_response = \ self.clean_acls(req) or check_metadata(req, 'container') if error_response: return error_response if not req.environ.get('swift_owner'): for key in self.app.swift_owner_headers: req.headers.pop(key, None) # account_partition, accounts, container_count = \ # self.account_info(self.account_name, req) # if not accounts: account_node, account_filesystem, account_directory, container_count, \ account_component_number, head_status = self.account_info(\ self.account_name, req) if not account_node and not head_status: return HTTPInternalServerError(request=req) if head_status and int(str(head_status).split()[0]) == 503: self.app.logger.info("account HEAD returning 503 service" \ "unavailable error due to which this request got failed") return HTTPServiceUnavailable(request=req) if head_status and int(str(head_status).split()[0]) == 500: return HTTPInternalServerError(request=req) if not account_node: return HTTPNotFound(request=req) # container_partition, containers = self.app.container_ring.get_nodes( # # self.account_name, self.container_name) container_node, container_filesystem, container_directory, \ global_map_version, component_number = \ self.app.container_ring.get_node( \ self.account_name, self.container_name) if not len(container_node): self.app.logger.error( _('%(msg)s %(method)s %(path)s'), { 'msg': _('ERROR Wrong ring file content'), 'method': 'POST', 'path': req.path }) return HTTPInternalServerError(request=req) headers = self.generate_request_headers(global_map_version, component_number, req, transfer=True) clear_info_cache(self.app, req.environ, self.account_name, self.container_name) if not self.is_req_blocked(_('Container'), component_number): #return HTTPTemporaryRedirect(request=req, body = 'Component' # 'movement is in progress') # resp = self.make_requests( # req, self.app.container_ring, container_partition, 'POST', # # req.swift_entity_path, [headers] * len(containers)) resp = self.make_requests(req, self.app.container_ring, container_node, container_filesystem, container_directory, 'POST', req.swift_entity_path, [headers] * len(container_node)) resp = self.container_req_for_comp_distribution(req, \ component_number, global_map_version, self.account_name, \ self.container_name, resp) if int(resp.status.split()[0]) in (301, 307): resp = HTTPInternalServerError(request=req) return resp
def PUT(self, req): """HTTP PUT request handler.""" error_response = \ self.clean_acls(req) or check_metadata(req, 'container') if error_response: return error_response if not req.environ.get('swift_owner'): for key in self.app.swift_owner_headers: req.headers.pop(key, None) if len(self.container_name) > MAX_CONTAINER_NAME_LENGTH: resp = HTTPBadRequest(request=req) resp.body = 'Container name length of %d longer than %d' % \ (len(self.container_name), MAX_CONTAINER_NAME_LENGTH) return resp # account_partition, accounts, container_count = \ # self.account_info(self.account_name, req) account_node, account_filesystem, account_directory, container_count, \ account_component_number, head_status = self.account_info(\ self.account_name, req) # if not accounts and self.app.account_autocreate: # self.autocreate_account(req.environ, self.account_name) # account_partition, accounts, container_count = \ # self.account_info(self.account_name, req) # if not accounts: if not account_node and not head_status: return HTTPInternalServerError(request=req) if head_status and int(str(head_status).split()[0]) == 503: #TODO need to check why head_status is int or sometimes str self.app.logger.info("account HEAD returning 503 service " \ "unavailable error due to which this request got failed") return HTTPServiceUnavailable(request=req) if head_status and int(str(head_status).split()[0]) == 500: return HTTPInternalServerError(request=req) if not account_node and self.app.account_autocreate: self.autocreate_account(req.environ, self.account_name) account_node, account_filesystem, account_directory, \ container_count, account_component_number, head_status = \ self.account_info(self.account_name, req) if not account_node and not head_status: return HTTPInternalServerError(request=req) if head_status and int(str(head_status).split()[0]) == 503: self.app.logger.info("account HEAD returning 503 service" \ "unavailable error due to which this request got failed") return HTTPServiceUnavailable(request=req) if head_status and int(str(head_status).split()[0]) == 500: return HTTPInternalServerError(request=req) if not account_node: return HTTPNotFound(request=req) if self.app.max_containers_per_account > 0 and \ container_count >= self.app.max_containers_per_account and \ self.account_name not in self.app.max_containers_whitelist: resp = HTTPForbidden(request=req) resp.body = 'Reached container limit of %s' % \ self.app.max_containers_per_account return resp # container_partition, containers = self.app.container_ring.get_nodes( # self.account_name, self.container_name) container_node, container_filesystem, container_directory, \ global_map_version, component_number = \ self.app.container_ring.get_node(self.account_name, self.container_name) if not len(container_node): self.app.logger.error( _('%(msg)s %(method)s %(path)s'), { 'msg': _('ERROR Wrong ring file content'), 'method': 'PUT', 'path': req.path }) return HTTPInternalServerError(request=req) # headers = self._backend_requests(req, len(containers), # account_partition, accounts) try: headers = self._backend_requests(req, len(container_node), account_node, account_filesystem, account_directory, global_map_version, component_number, account_component_number) except ZeroDivisionError: self.app.logger.error( _('%(msg)s %(method)s %(path)s'), { 'msg': _('ERROR Wrong ring file content'), 'method': 'PUT', 'path': req.path }) return HTTPInternalServerError(request=req) clear_info_cache(self.app, req.environ, self.account_name, self.container_name) if not self.is_req_blocked(_('Container'), component_number): #return HTTPTemporaryRedirect(request=req, body = 'Component' # 'movement is in progress') # resp = self.make_requests( # # # container_partition, 'PUT', req.swift_entity_path, headers) resp = self.make_requests(req, self.app.container_ring, container_node, container_filesystem, container_directory, 'PUT', req.swift_entity_path, headers) resp = self.container_req_for_comp_distribution(req, \ component_number, global_map_version, self.account_name, \ self.container_name, resp) if int(resp.status.split()[0]) in (301, 307): resp = HTTPInternalServerError(request=req) return resp
def GETorHEAD(self, req): """Handler for HTTP GET/HEAD requests.""" account_node, account_filesystem, account_directory, container_count, \ account_component_number, head_status = self.account_info(\ self.account_name, req) if not account_node and not head_status: return HTTPInternalServerError(request=req) if head_status and int(str(head_status).split()[0]) == 503: #TODO need to check why head_status is int or sometimes str self.app.logger.info("account HEAD returning 503 service " \ "unavailable error due to which this request got failed") return HTTPServiceUnavailable(request=req) if head_status and int(str(head_status).split()[0]) == 500: return HTTPInternalServerError(request=req) if not account_node: return HTTPNotFound(request=req) path = get_param(req, 'path') #delimiter = get_param(req, 'delimiter') #prefix = get_param(req, 'prefix') limit = get_param(req, 'limit') if limit and not limit.isdigit(): return HTTPPreconditionFailed(request=req, body='Value of limit must ' 'be a positive integer') if path: return HTTPBadRequest(request=req, body='Unsupported query parameter ' 'path') # part = self.app.container_ring.get_part( # self.account_name, self.container_name) node, filesystem, directory, global_map_version, component_number = \ self.app.container_ring.get_node(self.account_name, \ self.container_name) if not len(node): self.app.logger.error( _('%(msg)s %(method)s %(path)s'), { 'msg': _('ERROR Wrong ring file content'), 'method': 'GET/HEAD', 'path': req.path }) return HTTPInternalServerError(request=req) #TODO Currently same component request is blocked until previous same #component request's map version is not updated, need to check other provision if not self.is_req_blocked(_('Container'), component_number): #return HTTPTemporaryRedirect(request=req, body = 'Component' # 'movement is in progress') # resp = self.GETorHEAD_base( # req, _('Container'), self.app.container_ring, part, # req.swift_entity_path) resp = self.GETorHEAD_base(req, _('Container'), \ self.app.container_ring, node, filesystem, directory, \ req.swift_entity_path, global_map_version, component_number) resp = self.container_req_for_comp_distribution(req, \ component_number, global_map_version, self.account_name, \ self.container_name, resp) if int(resp.status.split()[0]) in (301, 307): resp = HTTPInternalServerError(request=req) if 'swift.authorize' in req.environ: req.acl = resp.headers.get('x-container-read') aresp = req.environ['swift.authorize'](req) if aresp: return aresp if not req.environ.get('swift_owner', False): for key in self.app.swift_owner_headers: if key in resp.headers: del resp.headers[key] return resp
def authorize(self, req): env = req.environ env_identity = self._integral_keystone_identity(env) tenant_id, tenant_name = env_identity['tenant'] user_id, user_name = env_identity['user'] referrers, roles = osd_acl.parse_acl(getattr(req, 'acl', None)) #allow OPTIONS requests to proceed as normal if req.method == 'OPTIONS': return try: part = req.split_path(1, 4, True) version, account, container, obj = part except ValueError: return HTTPNotFound(request=req) user_roles = [r.lower() for r in env_identity.get('roles', [])] # Give unconditional access to a user with the reseller_admin # role. if self.reseller_admin_role in user_roles: msg = 'User %s has reseller admin authorizing' self.logger.debug(msg, tenant_id) req.environ['swift_owner'] = True return # if we are not reseller admin and user is trying to post metadata #X-Account-Meta-Max-Read-Bandwidth or X-Account-Meta-Max-Write-Bandwidth # on account then deny it. if not container and not obj and (req.method == 'POST' or \ req.method == 'PUT') and self.authorize_for_bandwidth_limit(req): msg = 'User %s:%s is not allowed to post this metadata on account' self.logger.debug(msg, tenant_name, user_name) return self.denied_response(req) # If we are not reseller admin and user is trying to delete its own # account then deny it. if not container and not obj and req.method == 'DELETE': # User is not allowed to issue a DELETE on its own account msg = 'User %s:%s is not allowed to delete its own account' self.logger.debug(msg, tenant_name, user_name) return self.denied_response(req) # cross-tenant authorization matched_acl = self._authorize_cross_tenant(user_id, user_name, tenant_id, tenant_name, roles) if matched_acl is not None: log_msg = 'user %s allowed in ACL authorizing.' self.logger.debug(log_msg, matched_acl) return acl_authorized = self._authorize_unconfirmed_identity( req, obj, referrers, roles) if acl_authorized: return # Check if a user tries to access an account that does not match their # token if not self._reseller_check(account, tenant_id): log_msg = 'tenant mismatch: %s != %s' self.logger.debug(log_msg, account, tenant_id) return self.denied_response(req) # Check the roles the user is belonging to. If the user is # part of the role defined in the config variable # operator_roles (like admin) then it will be # promoted as an admin of the account/tenant. for role in self.operator_roles.split(','): role = role.strip() if role in user_roles: log_msg = 'allow user with role %s as account admin' self.logger.debug(log_msg, role) req.environ['swift_owner'] = True return # If user is of the same name of the tenant then make owner of it. if self.is_admin and user_name == tenant_name: self.logger.warning("the is_admin feature has been deprecated " "and will be removed in the future " "update your config file") req.environ['swift_owner'] = True return if acl_authorized is not None: return self.denied_response(req) # Check if we have the role in the userroles and allow it for user_role in user_roles: if user_role in (r.lower() for r in roles): log_msg = 'user %s:%s allowed in ACL: %s authorizing' self.logger.debug(log_msg, tenant_name, user_name, user_role) return if "_member_" in user_roles and env.get('swift.source', '').startswith('S3'): log_msg = 'user %s:%s with _member_ role allowed' self.logger.debug(log_msg, tenant_name, user_name) return return self.denied_response(req)
def handle_get_token(self, req): """ Handles the various `request for token and service end point(s)` calls. There are various formats to support the various auth servers in the past. Examples:: GET <auth-prefix>/v1/<act>/auth X-Auth-User: <act>:<usr> or X-Storage-User: <usr> X-Auth-Key: <key> or X-Storage-Pass: <key> GET <auth-prefix>/auth X-Auth-User: <act>:<usr> or X-Storage-User: <act>:<usr> X-Auth-Key: <key> or X-Storage-Pass: <key> GET <auth-prefix>/v1.0 X-Auth-User: <act>:<usr> or X-Storage-User: <act>:<usr> X-Auth-Key: <key> or X-Storage-Pass: <key> On successful authentication, the response will have X-Auth-Token and X-Storage-Token set to the token to use with Swift and X-Storage-URL set to the URL to the default Swift cluster to use. :param req: The swob.Request to process. :returns: swob.Response, 2xx on success with data set as explained above. """ # Validate the request info try: pathsegs = split_path(req.path_info, 1, 3, True) except ValueError: self.logger.increment('errors') return HTTPNotFound(request=req) if pathsegs[0] == 'v1' and pathsegs[2] == 'auth': account = pathsegs[1] user = req.headers.get('x-storage-user') if not user: user = req.headers.get('x-auth-user') if not user or ':' not in user: self.logger.increment('token_denied') return HTTPUnauthorized(request=req, headers={ 'Www-Authenticate': 'Swift realm="%s"' % account }) account2, user = user.split(':', 1) if account != account2: self.logger.increment('token_denied') return HTTPUnauthorized(request=req, headers={ 'Www-Authenticate': 'Swift realm="%s"' % account }) key = req.headers.get('x-storage-pass') if not key: key = req.headers.get('x-auth-key') elif pathsegs[0] in ('auth', 'v1.0'): user = req.headers.get('x-auth-user') if not user: user = req.headers.get('x-storage-user') if not user or ':' not in user: self.logger.increment('token_denied') return HTTPUnauthorized( request=req, headers={'Www-Authenticate': 'Swift realm="unknown"'}) account, user = user.split(':', 1) key = req.headers.get('x-auth-key') if not key: key = req.headers.get('x-storage-pass') else: return HTTPBadRequest(request=req) if not all((account, user, key)): self.logger.increment('token_denied') realm = account or 'unknown' return HTTPUnauthorized( request=req, headers={'Www-Authenticate': 'Swift realm="%s"' % realm}) # Authenticate user account_user = account + ':' + user if account_user not in self.users: self.logger.increment('token_denied') return HTTPUnauthorized( request=req, headers={'Www-Authenticate': 'Swift realm="%s"' % account}) if self.users[account_user]['key'] != key: self.logger.increment('token_denied') return HTTPUnauthorized( request=req, headers={'Www-Authenticate': 'Swift realm="unknown"'}) account_id = self.users[account_user]['url'].rsplit('/', 1)[-1] # Get memcache client memcache_client = cache_from_env(req.environ) if not memcache_client: raise Exception('Memcache required') # See if a token already exists and hasn't expired token = None memcache_user_key = '%s/user/%s' % (self.reseller_prefix, account_user) candidate_token = memcache_client.get(memcache_user_key) if candidate_token: memcache_token_key = \ '%s/token/%s' % (self.reseller_prefix, candidate_token) cached_auth_data = memcache_client.get(memcache_token_key) if cached_auth_data: expires, old_groups = cached_auth_data old_groups = old_groups.split(',') new_groups = self._get_user_groups(account, account_user, account_id) if expires > time() and \ set(old_groups) == set(new_groups.split(',')): token = candidate_token # Create a new token if one didn't exist if not token: # Generate new token token = '%stk%s' % (self.reseller_prefix, uuid4().hex) expires = time() + self.token_life groups = self._get_user_groups(account, account_user, account_id) # Save token memcache_token_key = '%s/token/%s' % (self.reseller_prefix, token) memcache_client.set(memcache_token_key, (expires, groups), time=float(expires - time())) # Record the token with the user info for future use. memcache_user_key = \ '%s/user/%s' % (self.reseller_prefix, account_user) memcache_client.set(memcache_user_key, token, time=float(expires - time())) resp = Response(request=req, headers={ 'x-auth-token': token, 'x-storage-token': token }) url = self.users[account_user]['url'].replace('$HOST', resp.host_url) if self.storage_url_scheme != 'default': url = self.storage_url_scheme + ':' + url.split(':', 1)[1] resp.headers['x-storage-url'] = url return resp
def authorize(self, req): """ Returns None if the request is authorized to continue or a standard WSGI response callable if not. """ try: _junk, account, container, obj = req.split_path(1, 4, True) except ValueError: self.logger.increment('errors') return HTTPNotFound(request=req) if not account or not account.startswith(self.reseller_prefix): self.logger.debug("Account name: %s doesn't start with " "reseller_prefix: %s." % (account, self.reseller_prefix)) return self.denied_response(req) # At this point, TempAuth is convinced that it is authoritative. # If you are sending an ACL header, it must be syntactically valid # according to TempAuth's rules for ACL syntax. acl_data = req.headers.get('x-account-access-control') if acl_data is not None: error = self.extract_acl_and_report_errors(req) if error: msg = 'X-Account-Access-Control invalid: %s\n\nInput: %s\n' % ( error, acl_data) headers = [('Content-Type', 'text/plain; charset=UTF-8')] return HTTPBadRequest(request=req, headers=headers, body=msg) user_groups = (req.remote_user or '').split(',') account_user = user_groups[1] if len(user_groups) > 1 else None if '.reseller_admin' in user_groups and \ account != self.reseller_prefix and \ account[len(self.reseller_prefix)] != '.': req.environ['swift_owner'] = True self.logger.debug("User %s has reseller admin authorizing." % account_user) return None # if we are not reseller admin and user is trying to post metadata #X-Account-Meta-Max-Read-Bandwidth or X-Account-Meta-Max-Write-Bandwidth # on account then deny it. if not container and not obj and (req.method == 'POST' or \ req.method == 'PUT') and self.authorize_for_bandwidth_limit(req): self.logger.debug('User %s is not allowed to post this metadata' 'on account' % account_user) return self.denied_response(req) if account in user_groups and \ (req.method not in ('DELETE', 'PUT') or container): # If the user is admin for the account and is not trying to do an # account DELETE or PUT... req.environ['swift_owner'] = True self.logger.debug("User %s has admin authorizing." % account_user) return None if (req.environ.get('swift_sync_key') and (req.environ['swift_sync_key'] == req.headers.get( 'x-container-sync-key', None)) and 'x-timestamp' in req.headers): self.logger.debug("Allow request with container sync-key: %s." % req.environ['swift_sync_key']) return None if req.method == 'OPTIONS': #allow OPTIONS requests to proceed as normal self.logger.debug("Allow OPTIONS request.") return None referrers, groups = parse_acl(getattr(req, 'acl', None)) if referrer_allowed(req.referer, referrers): if obj or '.rlistings' in groups: self.logger.debug("Allow authorizing %s via referer ACL." % req.referer) return None for user_group in user_groups: if user_group in groups: self.logger.debug("User %s allowed in ACL: %s authorizing." % (account_user, user_group)) return None # Check for access via X-Account-Access-Control acct_acls = self.account_acls(req) if acct_acls: # At least one account ACL is set in this account's sysmeta data, # so we should see whether this user is authorized by the ACLs. user_group_set = set(user_groups) if user_group_set.intersection(acct_acls['admin']): req.environ['swift_owner'] = True self.logger.debug('User %s allowed by X-Account-Access-Control' ' (admin)' % account_user) return None if (user_group_set.intersection(acct_acls['read-write']) and (container or req.method in ('GET', 'HEAD'))): # The RW ACL allows all operations to containers/objects, but # only GET/HEAD to accounts (and OPTIONS, above) self.logger.debug('User %s allowed by X-Account-Access-Control' ' (read-write)' % account_user) return None if (user_group_set.intersection(acct_acls['read-only']) and req.method in ('GET', 'HEAD')): self.logger.debug('User %s allowed by X-Account-Access-Control' ' (read-only)' % account_user) return None return self.denied_response(req)