Пример #1
0
    def get(self):
        search = request.args.get('search')
        resp = OrgBookService.search(search)

        if resp.status_code != requests.codes.ok:
            raise BadGateway(f'OrgBook API responded with {resp.status_code}: {resp.reason}')

        try:
            results = json.loads(resp.text)['results']
        except:
            raise BadGateway('OrgBook API responded with unexpected data.')

        return results
Пример #2
0
 def run(self, payload: dict[str, Any]) -> dict[str, Any]:
     try:
         return self.client.invoke(**self.LAMBDA_CONFIG,
                                   Payload=json.dumps(payload).encode())
     except Exception:
         raise BadGateway(
             "Lambda is not accessible right now. Please try again.")
Пример #3
0
 def get_delivery_estimate(self, product, units, buyer):
     """Returns a shipping cost estimation for an order from shared server. If
     the order can't be delivered returns cost -1"""
     cost = -1
     total = product.price * units
     url = os.environ['SHARED_SERVER_URI'] + '/deliveries/estimate'
     header = {'Authorization': 'Bearer ' + self.auth_token}
     payload = {
         "ammount": total,
         "distance": product.location.distance_to(buyer.location),
         "user": {
             "email": buyer.email,
             "points": buyer.points,
             "deliveries": buyer.metadata.purchases
         }
     }
     response = requests.post(url, headers=header, json=payload)
     if response.status_code == 401:
         raise ExpiredTokenError()
     if response.status_code == 201:
         delivery_estimate = response.json()
         if delivery_estimate['isAbleToDeliver']:
             cost = delivery_estimate['cost']
     else:
         raise BadGateway()
     return cost
Пример #4
0
def lambda_proxy(action):
    """Invoke Lambda function through API Gateway to start/stop EC2 server
         instance

       action: (str) Only valid arguments are 'start_instance' and
         'stop_instance'.

    """

    if action != 'start_instance' and action != 'stop_instance':
        raise ValueError('Invalid argument for ec2_handler: %s' % action)

    # Input to the Lambda function
    payload = {'action': action}
    headers = {'Content-Type': 'application/json'}
    response = requests.post(
        # API Gateway endpoint
        'https://mvht1yr1c8.execute-api.us-east-2.amazonaws.com/prod/{proxy+}',
         data=json.dumps(payload), headers=headers)
    # API Gateway max timeout is very close to EC2 instance startup time.
    if action == 'start_instance' and response.status_code == 504:
        # Try again
        return lambda_proxy(action)

    # Instance state == 'stopping'
    if response.status_code == 502:
        raise BadGateway('Server is shutting down. Try again in a minute.')

    return response.json()['public_ip_address']
Пример #5
0
def send_invite_emails(pending_users):
    '''Send an invite email to every pending user in the list'''
    user_manager = current_app.user_manager
    db_adapter = user_manager.db_adapter
    logger = g.request_logger if hasattr(g, 'request_logger') else LOG

    for user in pending_users:
        logger.info('Sending email invite to: \'%s\'', user.username)
        # generate invite token
        token = user_manager.generate_token(int(user.id))
        invite_link = url_for('user.register', token=token, _external=True)

        email_msg = current_app.email_renderer.create_invitation_message(
            current_user, user, invite_link)
        try:
            # send invite email
            current_app.notification_service.send_email(email_msg)
        except NotificationError:
            error = 'Failed to send invitation email to: \'%s\'' % user.username
            logger.error('Failed to send invitation email to: \'%s\'',
                         user.username)
            raise BadGateway(error)

        # store token to db
        db_adapter.update_object(user, reset_password_token=token)
    db_adapter.commit()
Пример #6
0
    def post(self, party_guid):
        party = Party.find_by_party_guid(party_guid)
        if party is None:
            raise NotFound('Party not found.')

        if PartyOrgBookEntity.find_by_party_guid(party_guid) is not None:
            raise BadRequest(
                'This party is already associated with an OrgBook entity.')

        data = PartyOrgBookEntityListResource.parser.parse_args()
        credential_id = data.get('credential_id')

        if PartyOrgBookEntity.find_by_credential_id(credential_id) is not None:
            raise BadRequest(
                'An OrgBook entity with the provided credential ID already exists.'
            )

        resp = OrgBookService.get_credential(credential_id)
        if resp.status_code != requests.codes.ok:
            raise BadGateway(
                f'OrgBook API responded with {resp.status_code}: {resp.reason}'
            )

        try:
            credential = json.loads(resp.text)
            registration_id = credential['topic']['source_id']
            registration_status = not (credential['inactive'])
            registration_date = credential['effective_date']
            name_id = credential['names'][0]['id']
            name_text = credential['names'][0]['text']
        except:
            raise BadGateway('OrgBook API responded with unexpected data.')

        party_orgbook_entity = PartyOrgBookEntity.create(
            registration_id, registration_status, registration_date, name_id,
            name_text, credential_id, party_guid)
        if not party_orgbook_entity:
            raise InternalServerError(
                'Failed to create the Party OrgBook Entity.')

        party_orgbook_entity.save()

        party.party_name = name_text
        party.save()

        return party_orgbook_entity, 201
Пример #7
0
    def get(self, credential_id):
        resp = OrgBookService.get_credential(credential_id)

        if resp.status_code != requests.codes.ok:
            raise BadGateway(f'OrgBook API responded with {resp.status_code}: {resp.reason}')

        credential = json.loads(resp.text)
        return credential
    def get(self):
        search = request.args.get('search')
        resp = OrgBookService.search(search)

        if resp.status_code != requests.codes.ok:
            message = f'OrgBook API responded with {resp.status_code}: {resp.reason}'
            current_app.logger.error(
                f'SearchResource.get: {message}\nresp.text:\n{resp.text}')
            raise BadGateway(message)

        try:
            results = json.loads(resp.text)['results']
        except:
            message = 'OrgBook API responded with unexpected data.'
            current_app.logger.error(
                f'SearchResource.get: {message}\nresp.text:\n{resp.text}')
            raise BadGateway(message)

        return results
Пример #9
0
def retrieve_yaml_remote_rule(url):
    """ Retrieve a remote rule file content from the git web UI. """
    response = requests_session.request('HEAD', url)
    if response.status_code == 404:
        log.debug(f'Server returned 404 for {url}.')
        return None

    if response.status_code != 200:
        raise BadGateway('Error occurred while retrieving a remote rule file from the repo.')

    # remote rule file found...
    response = requests_session.request('GET', url)
    response.raise_for_status()
    return response.content
Пример #10
0
 def create_payment(self, order):
     """Sends a new payment order to shared server"""
     url = os.environ['SHARED_SERVER_URI'] + '/payments'
     header = {'Authorization': 'Bearer ' + self.auth_token}
     payload = {
         "transaction_id": order.tracking_number,
         "currency": "Pesos",
         "value": order.total,
         "paymentMethod": PaymentInfoSchema().dump(order.payment_info)
     }
     response = requests.post(url, headers=header, json=payload)
     if response.status_code == 401:
         raise ExpiredTokenError()
     if response.status_code != 201:
         raise BadGateway()
Пример #11
0
 def get_payment_methods(self):
     """Returns available payment methods from shared server"""
     url = os.environ['SHARED_SERVER_URI'] + '/payments/methods'
     header = {'Authorization': 'Bearer ' + self.auth_token}
     response = requests.get(url, headers=header)
     if response.status_code == 401:
         raise ExpiredTokenError()
     if response.status_code == 200:
         payment_methods = []
         payment_methods_dict = response.json()
         for payment_method in payment_methods_dict:
             payment_methods.append(
                 PaymentMethodSchema().load(payment_method))
     else:
         raise BadGateway()
     return payment_methods
    def get(self, credential_id):

        # Get the credential data for this credential ID
        resp = OrgBookService.get_credential(credential_id)
        if resp.status_code != requests.codes.ok:
            message = f'OrgBook API (get_credential) responded with {resp.status_code}: {resp.reason}'
            current_app.logger.error(
                f'CredentialResource get_credential: {message}\nresp.text:\n{resp.text}'
            )
            raise BadGateway(message)

        # Get the topic ID from the credential data
        credential_data = json.loads(resp.text)
        topic_id = credential_data['topic']['id']

        # Get the business number data using the topic ID
        resp = OrgBookService.get_business_number(topic_id)
        if resp.status_code != requests.codes.ok:
            message = f'OrgBook API (get_business_number) responded with {resp.status_code}: {resp.reason}'
            current_app.logger.warning(
                f'CredentialResource get_business_number: {message}\nresp.text:\n{resp.text}'
            )

        # Get the business number from the business number data
        business_number = None
        try:
            business_number_data = json.loads(resp.text)
            results = business_number_data['results']
            for result in results:
                for attribute in result['attributes']:
                    if attribute['type'] == 'business_number':
                        business_number = attribute['value']
                        break
                if business_number:
                    break
        except:
            pass

        # Set the business number (if it was found) in the credential data
        if business_number is None:
            message = f'OrgBook API (get_business_number) did not contain the business number'
            current_app.logger.warning(
                f'CredentialResource get_business_number: {message}')
        credential_data['business_number'] = business_number

        return credential_data
Пример #13
0
def get_weather():
    # Auth
    try:
        user = check_api_key()
    except Unauthorized:
        raise

    # Check throttling
    if not check_ratelimit(user.api_key):
        raise TooManyRequests('Maximum of %s request per hour' %
                              config.LIMIT_REQ_PER_HOUR)

    q = request.args.get('q')
    if not q:
        raise BadRequest('q parameter required')

    # We're only supporting ?q=cityname[,countryname]
    # So we'll just remove anything that's not (alphanum or comma),
    # complain if more than one comma
    # and pass the ?q straight through to OWM (Open Weather Map)
    q = re.sub('[^\w,]', '', q)
    if q.count(',') > 1:
        raise BadRequest(
            'q parameter should be of format cityname[,countryname]')

    # Don't talk to OWM if we're running in dev mode
    if config.DEV_MODE:
        return 'cloudy with a chance of meatballs'

    # Request to OWM
    url = util.construct_owm_req_uri(q)
    res = requests.get(url)

    # Grab the first weather item's description
    # (according to doco looks like we only get more than one if specifying geo region)
    # If the format isn't exactly as expected, or other problem talking to OWM
    # (e.g. bad API key) just log this and return 502 bad gateway
    try:
        j = res.json()
        description = j['weather'][0]['description']
    except:
        # TODO log the invalid req/res from OWM for troubleshooting, raise an alert etc
        # Error returned to client is intentionally vague
        raise BadGateway('Problem talking to weather service')

    return description
Пример #14
0
def send_reset_password(email):
    logger = g.request_logger if hasattr(g, 'request_logger') else LOG

    with Transaction() as transaction:
        user_manager = current_app.user_manager
        user = transaction.find_one_by_fields(
            User, case_sensitive=True, search_fields={'username': email})

        if not user:
            logger.warning('User does not exist with email: \'%s\'',
                           user.username)
            raise ItemNotFound('user', {'username': email})

        # Generate reset password token
        token = user_manager.generate_token(int(user.get_id()))
        reset_password_link = url_for('user.reset_password',
                                      token=token,
                                      _external=True)

        # Create reset password email message
        email_message = current_app.email_renderer.create_password_reset_message(
            current_user, user, reset_password_link)
        logger.info('Sending reset-password email to: \'%s\'', user.username)
        try:
            # Send password reset email
            current_app.notification_service.send_email(email_message)
        except NotificationError:
            error = 'Failed to send reset-password email to: \'%s\'' % user.username
            logger.error('Failed to send reset-password email to: \'%s\'',
                         user.username)
            raise BadGateway(error)

        # Store token to db
        user.reset_password_token = token
        user = transaction.add_or_update(user, flush=True)

    # Send forgot_password signal to flask_user to trigger any hooks
    # pylint: disable=W0212
    user_forgot_password.send(current_app._get_current_object(), user=user)
    logger.info(
        'Successfully sent reset password email for user with email: \'%s\'',
        user.username,
    )
Пример #15
0
def get_profile(user, retries=10):
    tries = 0
    status = 0

    session_id = keyring.get_password("instarss", "user")

    while status != 200 and tries < retries:
        if tries:
            time.sleep(1)
        r = requests.get('https://www.instagram.com/' + user + '/',
                         cookies={'sessionid': session_id},
                         allow_redirects=False)
        status = r.status_code
        if r.status_code != 200:
            print(user, status, file=sys.stderr)
        tries += 1
    if status != 200:
        sys.stderr.flush()
        raise BadGateway(description=status)
    return r.content
Пример #16
0
def _requests_fn(fn, url, parse_json=False, **kwargs):
    """Wraps requests.* and adds raise_for_status() and User-Agent."""
    kwargs.setdefault('headers', {}).update(HEADERS)
    resp = fn(url, gateway=True, **kwargs)

    logging.info(f'Got {resp.status_code} headers: {resp.headers}')
    type = content_type(resp)
    if (type and type != 'text/html'
            and (type.startswith('text/') or type.endswith('+json')
                 or type.endswith('/json'))):
        logging.info(resp.text)

    if parse_json:
        try:
            return resp.json()
        except ValueError:
            msg = "Couldn't parse response as JSON"
            logging.info(msg, exc_info=True)
            raise BadGateway(msg)

    return resp
Пример #17
0
    def share_by_email(self, **kwargs):
        shared_image_name = 'share_analysis.png'
        logger = g.request_logger if hasattr(g, 'request_logger') else LOG

        attachments = [("attachment", (attachment['filename'],
                                       attachment['content']))
                       for attachment in kwargs['attachments']]
        base64_image_str = kwargs.get('image_url')
        if base64_image_str:
            base64_image_str = base64_image_str.replace(
                'data:image/png;base64,', '')
            attachments.append((
                "inline",
                (
                    shared_image_name,
                    base64.decodebytes(base64_image_str.encode('utf-8')),
                ),
            ))
        else:
            shared_image_name = ''
        for recipient in kwargs['recipients']:
            msg = current_app.email_renderer.create_share_analysis_email(
                subject=kwargs['subject'],
                recipient=recipient,
                reply_to=kwargs['sender'],
                body=kwargs['message'],
                attachments=attachments,
                query_url=kwargs['query_url'],
                image=shared_image_name,
            )
            try:
                current_app.notification_service.send_email(msg)
            except NotificationError:
                error = 'Failed to send share analysis email to: \'%s\'' % recipient
                logger.error(error)
                raise BadGateway(error)
        message = (SHARE_ANALYSIS_PREVIEW_MESSAGE.format(
            recipient=','.join(kwargs['recipients'])) if kwargs.get(
                'is_preview', False) else SHARE_ANALYSIS_MESSAGE)
        return {'message': message}
Пример #18
0
 def update_payment_status(self, order):
     """Updates the order payment status from shared server"""
     url = os.environ['SHARED_SERVER_URI'] + '/payments/id/' + str(
         order.tracking_number)
     header = {'Authorization': 'Bearer ' + self.auth_token}
     response = requests.get(url, headers=header)
     payment_status = {
         'PENDIENTE': 'PAGO PENDIENTE DE PROCESO',
         'CONFIRMADO': 'PAGO ACEPTADO',
         'CANCELADO': 'PAGO RECHAZADO'
     }
     if response.status_code == 401:
         raise ExpiredTokenError()
     if response.status_code == 200:
         payment_list = response.json()
         if payment_list:
             payment_last_update = payment_list[-1]
         status = payment_last_update['status']
         order.last_status_update = payment_last_update['updateat']
         order.status = payment_status[status]
     else:
         raise BadGateway()
Пример #19
0
 def update_tracking_status(self, order):
     """Returns the shipping order status from shared server"""
     url = os.environ['SHARED_SERVER_URI'] + '/tracking/' + str(
         order.tracking_number)
     header = {'Authorization': 'Bearer ' + self.auth_token}
     response = requests.get(url, headers=header)
     tracking_status = {
         'PENDIENTE': 'PENDIENTE DE ENVIO',
         'EN TRANSITO': 'ENVIO EN PROGRESO',
         'CANCELADO': 'ENVIO CANCELADO',
         'ENTREGADO': 'ENVIO REALIZADO'
     }
     if response.status_code == 401:
         raise ExpiredTokenError()
     if response.status_code == 200:
         tracking_list = response.json()
         if tracking_list:
             tracking_last_update = tracking_list[-1]
         status = tracking_last_update['status']
         order.last_status_update = tracking_last_update['updateat']
         order.status = tracking_status[status]
     else:
         raise BadGateway()
Пример #20
0
def retrieve_scm_from_koji_build(nvr, build, koji_url):
    if not build:
        raise NotFound('Failed to find Koji build for "{}" at "{}"'.format(nvr, koji_url))

    source = None
    try:
        source = build['extra']['source']['original_url']
    except (TypeError, KeyError, AttributeError):
        pass
    finally:
        if not source:
            source = build.get('source')

    if not source:
        raise NoSourceException(
            'Failed to retrieve SCM URL from Koji build "{}" at "{}" '
            '(expected SCM URL in "source" attribute)'.format(nvr, koji_url)
        )

    url = urlparse(source)

    path_components = url.path.rsplit('/', 2)
    if len(path_components) < 3:
        namespace = ""
    else:
        namespace = path_components[-2]

    rev = url.fragment
    if not rev:
        raise BadGateway(
            'Failed to parse SCM URL "{}" from Koji build "{}" at "{}" '
            '(missing URL fragment with SCM revision information)'.format(source, nvr, koji_url)
        )

    pkg_name = url.path.split('/')[-1]
    pkg_name = re.sub(r'\.git$', '', pkg_name)
    return namespace, pkg_name, rev
Пример #21
0
 def _error(resp):
     msg = "Couldn't fetch %s as ActivityStreams 2" % url
     logging.warning(msg)
     err = BadGateway(msg)
     err.requests_response = resp
     raise err
Пример #22
0
        def application(environ, start_response):
            headers = list(EnvironHeaders(environ).items())
            headers[:] = [(k, v) for k, v in headers
                          if not is_hop_by_hop_header(k) and k.lower() not in (
                              "content-length", "host")]
            headers.append(("Connection", "close"))

            if opts["host"] == "<auto>":
                headers.append(("Host", target.ascii_host))
            elif opts["host"] is None:
                headers.append(("Host", environ["HTTP_HOST"]))
            else:
                headers.append(("Host", opts["host"]))

            headers.extend(opts["headers"].items())
            remote_path = path

            if opts["remove_prefix"]:
                remote_path = "%s/%s" % (
                    target.path.rstrip("/"),
                    remote_path[len(prefix):].lstrip("/"),
                )

            content_length = environ.get("CONTENT_LENGTH")
            chunked = False

            if content_length not in ("", None):
                headers.append(("Content-Length", content_length))
            elif content_length is not None:
                headers.append(("Transfer-Encoding", "chunked"))
                chunked = True

            try:
                if target.scheme == "http":
                    con = client.HTTPConnection(target.ascii_host,
                                                target.port or 80,
                                                timeout=self.timeout)
                elif target.scheme == "https":
                    con = client.HTTPSConnection(
                        target.ascii_host,
                        target.port or 443,
                        timeout=self.timeout,
                        context=opts["ssl_context"],
                    )
                else:
                    raise RuntimeError(
                        "Target scheme must be 'http' or 'https', got '{}'.".
                        format(target.scheme))

                con.connect()
                remote_url = url_quote(remote_path)
                querystring = environ["QUERY_STRING"]

                if querystring:
                    remote_url = remote_url + "?" + querystring

                con.putrequest(environ["REQUEST_METHOD"],
                               remote_url,
                               skip_host=True)

                for k, v in headers:
                    if k.lower() == "connection":
                        v = "close"

                    con.putheader(k, v)

                con.endheaders()
                stream = get_input_stream(environ)

                while 1:
                    data = stream.read(self.chunk_size)

                    if not data:
                        break

                    if chunked:
                        con.send(b"%x\r\n%s\r\n" % (len(data), data))
                    else:
                        con.send(data)

                resp = con.getresponse()
            except socket.error:
                from werkzeug.exceptions import BadGateway

                return BadGateway()(environ, start_response)

            start_response(
                "%d %s" % (resp.status, resp.reason),
                [(k.title(), v)
                 for k, v in resp.getheaders() if not is_hop_by_hop_header(k)],
            )

            def read():
                while 1:
                    try:
                        data = resp.read(self.chunk_size)
                    except socket.error:
                        break

                    if not data:
                        break

                    yield data

            return read()
Пример #23
0
    def post(self):
        if request.headers.get('Tus-Resumable') is None:
            raise BadRequest(
                'Received file upload for unsupported file transfer protocol')

        # Validate the file size
        file_size = request.headers.get('Upload-Length')
        if not file_size:
            raise BadRequest('Received file upload of unspecified size')
        file_size = int(file_size)
        max_file_size = Config.MAX_CONTENT_LENGTH
        if file_size > max_file_size:
            raise RequestEntityTooLarge(
                f'The maximum file upload size is {max_file_size/1024/1024}MB.'
            )

        # Validate the file name and file type
        data = self.parser.parse_args()
        filename = data.get('filename') or request.headers.get('Filename')
        if not filename:
            raise BadRequest('File name cannot be empty')
        if filename.endswith(FORBIDDEN_FILETYPES):
            raise BadRequest('File type is forbidden')

        # Create the path string for this file
        document_guid = str(uuid.uuid4())
        base_folder = Config.UPLOADED_DOCUMENT_DEST
        folder = data.get('folder') or request.headers.get('Folder')
        folder = os.path.join(base_folder, folder)
        file_path = os.path.join(folder, document_guid)
        pretty_folder = data.get('pretty_folder') or request.headers.get(
            'Pretty-Folder')
        pretty_path = os.path.join(base_folder, pretty_folder, filename)

        # If the object store is enabled, send the post request through to TUSD to the object store
        object_store_path = None
        if Config.OBJECT_STORE_ENABLED:
            resp = requests.post(url=Config.TUSD_URL,
                                 headers={
                                     key: value
                                     for (key, value) in request.headers
                                     if key != 'Host'
                                 },
                                 data=request.data)
            if resp.status_code != requests.codes.created:
                message = f'Cannot upload file. Object store responded with {resp.status_code} ({resp.reason}): {resp._content}'
                current_app.logger.error(
                    f'POST resp.request:\n{resp.request.__dict__}')
                current_app.logger.error(f'POST resp:\n{resp.__dict__}')
                current_app.logger.error(message)
                raise BadGateway(message)

            object_store_upload_resource = urlparse(
                resp.headers['Location']).path.split('/')[-1]
            object_store_path = Config.S3_PREFIX + object_store_upload_resource.split(
                '+')[0]
            cache.set(OBJECT_STORE_UPLOAD_RESOURCE(document_guid),
                      object_store_upload_resource, TIMEOUT_24_HOURS)
            cache.set(OBJECT_STORE_PATH(document_guid), object_store_path,
                      TIMEOUT_24_HOURS)

        # Else, create an empty file at this path in the file system
        else:
            try:
                if not os.path.exists(folder):
                    os.makedirs(folder)
                with open(file_path, 'wb') as f:
                    f.seek(file_size - 1)
                    f.write(b'\0')
            except IOError as e:
                current_app.logger.error(e)
                raise InternalServerError('Unable to create file')

        # Cache data to be used in future PATCH requests
        cache.set(FILE_UPLOAD_SIZE(document_guid), file_size, TIMEOUT_24_HOURS)
        cache.set(FILE_UPLOAD_OFFSET(document_guid), 0, TIMEOUT_24_HOURS)
        cache.set(FILE_UPLOAD_PATH(document_guid), file_path, TIMEOUT_24_HOURS)

        # Create document record
        document = Document(document_guid=document_guid,
                            full_storage_path=file_path,
                            upload_started_date=datetime.utcnow(),
                            file_display_name=filename,
                            path_display_name=pretty_path,
                            object_store_path=object_store_path)
        document.save()

        # Create and send response
        response = make_response(jsonify(document_manager_guid=document_guid),
                                 201)
        response.headers['Tus-Resumable'] = TUS_API_VERSION
        response.headers['Tus-Version'] = TUS_API_SUPPORTED_VERSIONS
        response.headers[
            'Location'] = f'{Config.DOCUMENT_MANAGER_URL}/documents/{document_guid}'
        response.headers['Upload-Offset'] = 0
        response.headers[
            'Access-Control-Expose-Headers'] = 'Tus-Resumable,Tus-Version,Location,Upload-Offset,Content-Type'
        response.autocorrect_location_header = False
        return response
Пример #24
0
        def application(environ, start_response):
            headers = list(EnvironHeaders(environ).items())
            headers[:] = [(k, v) for k, v in headers
                          if not is_hop_by_hop_header(k) and k.lower() not in (
                              'content-length', 'host')]
            headers.append(('Connection', 'close'))
            if opts['host'] == '<auto>':
                headers.append(('Host', target.ascii_host))
            elif opts['host'] is None:
                headers.append(('Host', environ['HTTP_HOST']))
            else:
                headers.append(('Host', opts['host']))
            headers.extend(opts['headers'].items())

            remote_path = path
            if opts['remove_prefix']:
                remote_path = '%s/%s' % (target.path.rstrip('/'),
                                         remote_path[len(prefix):].lstrip('/'))

            content_length = environ.get('CONTENT_LENGTH')
            chunked = False
            if content_length not in ('', None):
                headers.append(('Content-Length', content_length))
            elif content_length is not None:
                headers.append(('Transfer-Encoding', 'chunked'))
                chunked = True

            try:
                if target.scheme == 'http':
                    con = httplib.HTTPConnection(target.ascii_host,
                                                 target.port or 80,
                                                 timeout=self.timeout)
                elif target.scheme == 'https':
                    con = httplib.HTTPSConnection(target.ascii_host,
                                                  target.port or 443,
                                                  timeout=self.timeout,
                                                  context=opts['ssl_context'])
                con.connect()
                con.putrequest(environ['REQUEST_METHOD'],
                               url_quote(remote_path),
                               skip_host=True)

                for k, v in headers:
                    if k.lower() == 'connection':
                        v = 'close'
                    con.putheader(k, v)
                con.endheaders()

                stream = get_input_stream(environ)
                while 1:
                    data = stream.read(self.chunk_size)
                    if not data:
                        break
                    if chunked:
                        con.send(b'%x\r\n%s\r\n' % (len(data), data))
                    else:
                        con.send(data)

                resp = con.getresponse()
            except socket.error:
                from werkzeug.exceptions import BadGateway
                return BadGateway()(environ, start_response)

            start_response(
                '%d %s' % (resp.status, resp.reason),
                [(k.title(), v)
                 for k, v in resp.getheaders() if not is_hop_by_hop_header(k)])

            def read():
                while 1:
                    try:
                        data = resp.read(self.chunk_size)
                    except socket.error:
                        break
                    if not data:
                        break
                    yield data

            return read()
Пример #25
0
        ),
        (
            RequestTimeout("Request has timed out"),
            (
                {
                    "error": {
                        "title": "Request has timed out",
                        "status": 408,
                        "details": "()",
                    }
                },
                408,
            ),
        ),
        (
            BadGateway("This is a Bad Gateway"),
            (
                {
                    "error": {
                        "title": "This is a Bad Gateway",
                        "status": 502,
                        "details": "()",
                    }
                },
                502,
            ),
        ),
    ],
)
def test_error_handler(app, error, expected):
    resp = generic_error_handler(error)
Пример #26
0
    def patch(self, document_guid):
        # Get and validate the file path (not required if object store is enabled)
        file_path = cache.get(FILE_UPLOAD_PATH(document_guid))
        if not Config.OBJECT_STORE_ENABLED and (
                file_path is None or not os.path.lexists(file_path)):
            raise NotFound('File does not exist')

        # Get and validate the upload offset
        request_offset = int(request.headers.get('Upload-Offset', 0))
        file_offset = cache.get(FILE_UPLOAD_OFFSET(document_guid))
        if request_offset != file_offset:
            raise Conflict(
                'Upload offset in request does not match the file\'s upload offset'
            )

        # Get and validate the content length and the expected new upload offset
        chunk_size = request.headers.get('Content-Length')
        if chunk_size is None:
            raise BadRequest('No Content-Length header in request')
        chunk_size = int(chunk_size)
        new_offset = file_offset + chunk_size
        file_size = cache.get(FILE_UPLOAD_SIZE(document_guid))
        if new_offset > file_size:
            raise RequestEntityTooLarge(
                'The uploaded chunk would put the file above its declared file size'
            )

        # If the object store is enabled, send the patch request through to TUSD to the object store
        if Config.OBJECT_STORE_ENABLED:
            object_store_upload_resource = cache.get(
                OBJECT_STORE_UPLOAD_RESOURCE(document_guid))

            url = f'{Config.TUSD_URL}{object_store_upload_resource}'
            headers = {
                key: value
                for (key, value) in request.headers if key != 'Host'
            }
            resp = requests.patch(url=url, headers=headers, data=request.data)

            if resp.status_code not in [
                    requests.codes.ok, requests.codes.no_content
            ]:
                message = f'Cannot upload file. Object store responded with {resp.status_code} ({resp.reason}): {resp._content}'
                current_app.logger.error(
                    f'PATCH resp.request:\n{resp.request.__dict__}')
                current_app.logger.error(f'PATCH resp:\n{resp.__dict__}')
                current_app.logger.error(message)
                raise BadGateway(message)

        # Else, write the content to the file in the file system
        else:
            try:
                with open(file_path, 'r+b') as f:
                    f.seek(file_offset)
                    f.write(request.data)
            except IOError as e:
                current_app.logger.error(e)
                raise InternalServerError('Unable to write to file')

        # If the file upload is complete, set the upload completion date and delete cached data
        if new_offset == file_size:
            document = Document.find_by_document_guid(document_guid)
            document.upload_completed_date = datetime.utcnow()
            document.save()
            cache.delete(FILE_UPLOAD_SIZE(document_guid))
            cache.delete(FILE_UPLOAD_OFFSET(document_guid))
            cache.delete(FILE_UPLOAD_PATH(document_guid))
            cache.delete(OBJECT_STORE_PATH(document_guid))
            cache.delete(OBJECT_STORE_UPLOAD_RESOURCE(document_guid))

        # Else, the file upload is still in progress, update its upload offset in the cache
        else:
            cache.set(FILE_UPLOAD_OFFSET(document_guid), new_offset,
                      TIMEOUT_24_HOURS)

        response = make_response('', 204)
        response.headers['Tus-Resumable'] = TUS_API_VERSION
        response.headers['Tus-Version'] = TUS_API_SUPPORTED_VERSIONS
        response.headers['Upload-Offset'] = new_offset
        response.headers[
            'Access-Control-Expose-Headers'] = 'Tus-Resumable,Tus-Version,Upload-Offset'
        return response