def _int_del_key(self, trail, request, response): try: name = '/'.join(trail) msg = self._parse_maybe_body(request, name) except Exception as e: raise HTTPError(406, str(e)) key = self._db_key(trail) try: ret = self.root.store.cut(key) except CSStoreDenied: self.logger.exception( "Delete: Permission to perform this operation was denied") raise HTTPError(403) except CSStoreError: self.logger.exception('Delete: Internal Server Error') raise HTTPError(500) except CSStoreUnsupported: self.logger.exception('Delete: Unsupported operation') raise HTTPError(501) if ret is False: raise HTTPError(404) output = msg.reply(None) if output is None: response['code'] = 204 else: response['headers'][ 'Content-Type'] = 'application/json; charset=utf-8' response['output'] = output response['code'] = 200
def _list(self, trail, request, response): try: name = '/'.join(trail) msg = self._parse_query(request, name) except Exception as e: raise HTTPError(406, str(e)) default = request.get('default_namespace', None) basename = self._db_container_key(default, trail) try: keylist = self.root.store.list(basename) self.logger.debug('list %s returned %r', basename, keylist) if keylist is None: raise HTTPError(404) response['headers'][ 'Content-Type'] = 'application/json; charset=utf-8' response['output'] = msg.reply(keylist) except CSStoreDenied: self.logger.exception( "List: Permission to perform this operation was denied") raise HTTPError(403) except CSStoreError: self.logger.exception('List: Internal server error') raise HTTPError(500) except CSStoreUnsupported: self.logger.exception('List: Unsupported operation') raise HTTPError(501)
def _parse_bin_body(self, request, name): body = request.get('body') if body is None: raise HTTPError(400) value = b64encode(bytes(body)).decode('utf-8') payload = {'type': 'simple', 'value': value} return self._parse(request, payload, name)
def _db_container_key(self, default, trail): f = None if len(trail) > 1: f = self._db_key(trail) elif len(trail) == 1 and trail[0] != '': self.logger.debug( "Forbidden action: Wrong container path. Container names must " "end with '/'") raise HTTPError(403) elif default is None: self.logger.debug("Forbidden action: No default namespace") raise HTTPError(403) else: # Use the default namespace f = self._db_key([default, '']) return f
def _db_key(self, trail): if len(trail) < 2: self.logger.debug( "Forbidden action: Operation only permitted within a " "container") raise HTTPError(403) return os.path.join('keys', *trail)
def parse_body(self): length = int(self.headers.get('content-length', 0)) if length > MAX_REQUEST_SIZE: raise HTTPError(413) if length == 0: self.body = None else: self.body = self.rfile.read(length)
def DELETE(self, request, response): trail = request.get('trail', []) if len(trail) == 0: raise HTTPError(405) if trail[-1] == '': self._destroy(trail, request, response) else: self._del_key(trail, request, response)
def _int_set_key(self, trail, request, response): try: name = '/'.join(trail) content_type = request.get('headers', {}).get('Content-Type', '') content_type_value = content_type.split(';')[0].strip() if content_type_value == 'application/octet-stream': msg = self._parse_bin_body(request, name) elif content_type_value == 'application/json': msg = self._parse_body(request, name) else: raise ValueError('Invalid Content-Type') except UnknownMessageType as e: raise HTTPError(406, str(e)) except UnallowedMessage as e: raise HTTPError(406, str(e)) except Exception as e: raise HTTPError(400, str(e)) # must _db_key first as access control is done here for now # otherwise users would e able to probe containers in namespaces # they do not have access to. key = self._db_key(trail) try: default = request.get('default_namespace', None) ok = self._parent_exists(default, trail) if not ok: raise HTTPError(404) ok = self.root.store.set(key, msg.payload) except CSStoreDenied: self.logger.exception( "Set: Permission to perform this operation was denied") raise HTTPError(403) except CSStoreExists: self.logger.exception('Set: Key already exist') raise HTTPError(409) except CSStoreError: self.logger.exception('Set: Internal Server Error') raise HTTPError(500) except CSStoreUnsupported: self.logger.exception('Set: Unsupported operation') raise HTTPError(501) output = msg.reply(None) if output is not None: response['headers'][ 'Content-Type'] = 'application/json; charset=utf-8' response['output'] = output response['code'] = 201
def _create(self, trail, request, response): try: name = '/'.join(trail) msg = self._parse_maybe_body(request, name) except Exception as e: raise HTTPError(406, str(e)) default = request.get('default_namespace', None) basename = self._db_container_key(None, trail) try: if len(trail) > 2: ok = self._parent_exists(default, trail[:-1]) if not ok: raise HTTPError(404) self.root.store.span(basename) except CSStoreDenied: self.logger.exception( "Create: Permission to perform this operation was denied") raise HTTPError(403) except CSStoreExists: self.logger.debug('Create: Key already exists') response['code'] = 200 return except CSStoreError: self.logger.exception('Create: Internal server error') raise HTTPError(500) except CSStoreUnsupported: self.logger.exception('Create: Unsupported operation') raise HTTPError(501) output = msg.reply(None) if output is not None: response['headers'][ 'Content-Type'] = 'application/json; charset=utf-8' response['output'] = output response['code'] = 201
def _parent_exists(self, default, trail): # check that the containers exist basename = self._db_container_key(trail[0], trail[:-1] + ['']) try: keylist = self.root.store.list(basename) except CSStoreError: raise HTTPError(500) self.logger.debug('parent_exists: %s (%s, %r) -> %r', basename, default, trail, keylist) if keylist is not None: return True # create default namespace if it is the only missing piece if len(trail) == 2 and default == trail[0]: container = self._db_container_key(default, '') self.root.store.span(container) return True return False
def _int_get_key(self, trail, request, response): try: name = '/'.join(trail) handler = self._parse_query(request, name) except Exception as e: raise HTTPError(406, str(e)) key = self._db_key(trail) try: output = self.root.store.get(key) if output is None: raise HTTPError(404) elif len(output) == 0: raise HTTPError(406) self._format_reply(request, response, handler, output) except CSStoreDenied: self.logger.exception( "Get: Permission to perform this operation was denied") raise HTTPError(403) except CSStoreError: self.logger.exception('Get: Internal server error') raise HTTPError(500) except CSStoreUnsupported: self.logger.exception('Get: Unsupported operation') raise HTTPError(501)
def pipeline(self, config, request): """ The pipeline() function handles authentication and invocation of the correct consumer based on the server configuration, that is provided at initialization time. When authentication is performed all the authenticators are executed. If any returns False, authentication fails and a 403 error is raised. If none of them positively succeeds and they all return None then also authentication fails and a 403 error is raised. Authentication plugins can add attributes to the request object for use of authorization or other plugins. When authorization is performed and positive result will cause the operation to be accepted and any negative result will cause it to fail. If no authorization plugin returns a positive result a 403 error is returned. Once authentication and authorization are successful the pipeline will parse the path component and find the consumer plugin that handles the provided path walking up the path component by component until a consumer is found. Paths are walked up from the leaf to the root, so if two consumers hang on the same tree, the one closer to the leaf will be used. If there is a trailing path when the conumer is selected then it will be stored in the request dicstionary named 'trail'. The 'trail' is an ordered list of the path components below the consumer entry point. """ path_chain = request['path_chain'] if not path_chain or path_chain[0] != '': # no path or not an absolute path raise HTTPError(400) # auth framework here authers = config.get('authenticators') if authers is None: raise HTTPError(403) valid_once = False for auth in authers: valid = authers[auth].handle(request) if valid is False: raise HTTPError(403) elif valid is True: valid_once = True if valid_once is not True: self.server.auditlog.svc_access(self.__class__.__name__, log.AUDIT_SVC_AUTH_FAIL, request['client_id'], 'No auth') raise HTTPError(403) # auhz framework here authzers = config.get('authorizers') if authzers is None: raise HTTPError(403) authz_ok = None for authz in authzers: valid = authzers[authz].handle(request) if valid is True: authz_ok = True elif valid is False: authz_ok = False break if authz_ok is not True: self.server.auditlog.svc_access(self.__class__.__name__, log.AUDIT_SVC_AUTHZ_FAIL, request['client_id'], path_chain) raise HTTPError(403) # Select consumer trail = [] while path_chain: if path_chain in config['consumers']: con = config['consumers'][path_chain] if len(trail) != 0: request['trail'] = trail return con.handle(request) trail.insert(0, path_chain[-1]) path_chain = path_chain[:-1] raise HTTPError(404)
def handle_one_request(self): if self.request.family == socket.AF_UNIX: # Set a fake client address to make log functions happy self.client_address = ['127.0.0.1', 0] try: if not self.server.config: self.close_connection = 1 return self.raw_requestline = self.rfile.readline(65537) if not self.raw_requestline: self.close_connection = 1 return if len(self.raw_requestline) > 65536: self.requestline = '' self.request_version = '' self.command = '' self.send_error(414) self.wfile.flush() return if not self.parse_request(): self.close_connection = 1 return try: self.parse_body() except HTTPError as e: self.send_error(e.code, e.mesg) self.wfile.flush() return request = { 'creds': self.peer_creds, 'client_cert': self.peer_cert, 'client_id': self.peer_info, 'command': self.command, 'path': self.path, 'path_chain': self.path_chain, 'query': self.query, 'url': self.url, 'version': self.request_version, 'headers': self.headers, 'body': self.body } logger.debug( "REQUEST: %s %s, query: %r, cred: %r, client_id: %s, " "headers: %r, body: %r", request['command'], request['path_chain'], request['query'], request['creds'], request['client_id'], dict(request['headers']), request['body']) try: response = self.pipeline(self.server.config, request) if response is None: raise HTTPError(500) except HTTPError as e: self.send_error(e.code, e.mesg) self.wfile.flush() return except socket.timeout as e: self.log_error("Request timed out: %r", e) self.close_connection = 1 return except Exception as e: # pylint: disable=broad-except self.log_error("Handler failed: %r", e, exc_info=True) self.send_error(500) self.wfile.flush() return self.send_response(response.get('code', 200)) for header, value in six.iteritems(response.get('headers', {})): self.send_header(header, value) self.end_headers() output = response.get('output', None) if hasattr(output, 'read'): shutil.copyfileobj(output, self.wfile) output.close() elif output is not None: self.wfile.write(output) else: self.close_connection = 1 self.wfile.flush() return except socket.timeout as e: self.log_error("Request timed out: %r", e) self.close_connection = 1 return
def _parse_body(self, request, name): body = request.get('body') if body is None: raise HTTPError(400) value = json.loads(bytes(body).decode('utf-8')) return self._parse(request, value, name)
def POST(self, request, response): trail = request.get('trail', []) if len(trail) > 0 and trail[-1] == '': self._create(trail, request, response) else: raise HTTPError(405)
def PUT(self, request, response): trail = request.get('trail', []) if len(trail) == 0 or trail[-1] == '': raise HTTPError(405) else: self._set_key(trail, request, response)