Пример #1
0
def to_current(workflow: Workflow,
               stage: Stage,
               ident: str,
               flash: bool = True) -> Response:
    next_stage = workflow.current_stage
    if flash:
        alerts.flash_warning(f'Please {next_stage.label} before proceeding.')
    return to_stage(workflow, next_stage, ident)
Пример #2
0
 def test_safe_flash_message(self, mock_flash):
     """Flash a simple message that is HTML-safe."""
     alerts.flash_warning('The message', safe=True)
     (data, category), kwargs = mock_flash.call_args
     self.assertEqual(data['message'], 'The message')
     self.assertIsInstance(data['message'], Markup)
     self.assertTrue(data['dismissable'])
     self.assertEqual(category, alerts.WARNING)
Пример #3
0
 def test_flash_message_no_dismiss(self, mock_flash):
     """Flash a simple message that can't be dismissed."""
     alerts.flash_warning('The message', dismissable=False)
     (data, category), kwargs = mock_flash.call_args
     self.assertEqual(data['message'], 'The message')
     self.assertIsInstance(data['message'], str)
     self.assertFalse(data['dismissable'])
     self.assertEqual(category, alerts.WARNING)
Пример #4
0
 def test_flash_message(self, mock_flash):
     """Just flash a simple message."""
     alerts.flash_warning('The message')
     (data, category), kwargs = mock_flash.call_args
     self.assertEqual(data['message'], 'The message')
     self.assertIsInstance(data['message'], str)
     self.assertTrue(data['dismissable'])
     self.assertEqual(category, alerts.WARNING)
Пример #5
0
def _new_file(params: MultiDict, pointer: FileStorage, session: Session,
              submission: Submission, rdata: Dict[str, Any], token: str) \
        -> Response:
    """
    Handle a POST request with a new file to add to an existing upload package.

    This occurs in the case that there is already an upload workspace
    associated with the submission. See the :attr:`Submission.source_content`
    attribute, which is set using :class:`SetUploadPackage`.

    Parameters
    ----------
    params : :class:`MultiDict`
        The form data from the request.
    pointer : :class:`FileStorage`
        The file upload stream.
    session : :class:`Session`
        The authenticated session for the request.
    submission : :class:`Submission`
        The submission for which the upload is being made.

    Returns
    -------
    dict
        Response data, to render in template.
    int
        HTTP status code. This should be ``303``, unless something goes wrong.
    dict
        Extra headers to add/update on the response. This should include
        the `Location` header for use in the 303 redirect response.

    """
    submitter, client = user_and_client_from_session(session)
    fm = FileManager.current_session()
    upload_id = submission.source_content.identifier

    # Using a form object provides some extra assurance that this is a legit
    # request; provides CSRF goodies.
    params['file'] = pointer
    form = UploadForm(params)
    rdata['form'] = form

    if not form.validate():
        logger.error('Invalid upload form: %s', form.errors)

        alerts.flash_failure("Something went wrong. Please try again.",
                             title="Whoops")
        # redirect = url_for('ui.file_upload',
        #                    submission_id=submission.submission_id)
        # return {}, status.SEE_OTHER, {'Location': redirect}
        logger.debug('Invalid form data')
        raise BadRequest(rdata)
    ancillary: bool = form.ancillary.data

    try:
        stat = fm.add_file(upload_id, pointer, token, ancillary=ancillary)
    except exceptions.RequestFailed as e:
        try:
            e_data = e.response.json()
        except Exception:
            e_data = None
        if e_data is not None and 'reason' in e_data:
            alerts.flash_failure(
                Markup('There was a problem carrying out your request:'
                       f' {e_data["reason"]}. {PLEASE_CONTACT_SUPPORT}'))
            raise BadRequest(rdata)
        alerts.flash_failure(
            Markup('There was a problem carrying out your request. Please try'
                   f' again. {PLEASE_CONTACT_SUPPORT}'))
        logger.debug('Failed to add file: %s', )
        logger.error(traceback.format_exc())
        raise InternalServerError(rdata) from e

    submission = _update(form, submission, stat, submitter, client, rdata)
    converted_size = tidy_filesize(stat.size)
    if stat.status is Upload.Status.READY:
        alerts.flash_success(
            f'Uploaded {pointer.filename} successfully. Total submission'
            f' package size is {converted_size}',
            title='Upload successful')
    elif stat.status is Upload.Status.READY_WITH_WARNINGS:
        alerts.flash_warning(
            f'Uploaded {pointer.filename} successfully. Total submission'
            f' package size is {converted_size}. See below for warnings.',
            title='Upload complete, with warnings')
    elif stat.status is Upload.Status.ERRORS:
        alerts.flash_warning(
            f'Uploaded {pointer.filename} successfully. Total submission'
            f' package size is {converted_size}. See below for errors.',
            title='Upload complete, with errors')
    status_data = stat.to_dict()
    alerts.flash_hidden(status_data, '_status')
    loc = url_for('ui.file_upload', submission_id=submission.submission_id)
    return {}, status.SEE_OTHER, {'Location': loc}
Пример #6
0
def delete(method: str,
           params: MultiDict,
           session: Session,
           submission_id: int,
           token: Optional[str] = None,
           **kwargs) -> Response:
    """
    Handle a request to delete a file.

    The file will only be deleted if a POST request is made that also contains
    the ``confirmed`` parameter.

    The process can be initiated with a GET request that contains the
    ``path`` (key) for the file to be deleted. For example, a button on
    the upload interface may link to the deletion route with the file path
    as a query parameter. This will generate a deletion confirmation form,
    which can be POSTed to complete the action.

    Parameters
    ----------
    method : str
        ``GET`` or ``POST``
    params : :class:`MultiDict`
        The query or form data from the request.
    session : :class:`Session`
        The authenticated session for the request.
    submission_id : int
        The identifier of the submission for which the deletion is being made.
    token : str
        The original (encrypted) auth token on the request. Used to perform
        subrequests to the file management service.

    Returns
    -------
    dict
        Response data, to render in template.
    int
        HTTP status code. This should be ``200`` or ``303``, unless something
        goes wrong.
    dict
        Extra headers to add/update on the response. This should include
        the `Location` header for use in the 303 redirect response, if
        applicable.

    """
    if token is None:
        logger.debug('Missing auth token')
        raise BadRequest('Missing auth token')

    submission, submission_events = load_submission(submission_id)
    upload_id = submission.source_content.identifier
    submitter, client = user_and_client_from_session(session)

    rdata = {'submission': submission, 'submission_id': submission_id}

    if method == 'GET':
        # The only thing that we want to get from the request params on a GET
        # request is the file path. This way there is no way for a GET request
        # to trigger actual deletion. The user must explicitly indicate via
        # a valid POST that the file should in fact be deleted.
        params = MultiDict({'file_path': params['path']})

    form = DeleteFileForm(params)
    rdata.update({'form': form})

    if method == 'POST':
        if not (form.validate() and form.confirmed.data):
            logger.debug('Invalid form data')
            raise BadRequest(rdata)

        stat: Optional[Upload] = None
        try:
            file_path = form.file_path.data
            stat = FileManager.delete_file(upload_id, file_path, token)
            alerts.flash_success(
                f'File <code>{form.file_path.data}</code> was deleted'
                ' successfully',
                title='Deleted file successfully',
                safe=True)
        except exceptions.RequestForbidden:
            alerts.flash_failure(
                Markup(
                    'There was a problem authorizing your request. Please try'
                    f' again. {PLEASE_CONTACT_SUPPORT}'))
        except exceptions.BadRequest:
            alerts.flash_warning(
                Markup('Something odd happened when processing your request.'
                       f'{PLEASE_CONTACT_SUPPORT}'))
        except exceptions.RequestFailed:
            alerts.flash_failure(
                Markup(
                    'There was a problem carrying out your request. Please try'
                    f' again. {PLEASE_CONTACT_SUPPORT}'))

        if stat is not None:
            command = UpdateUploadPackage(creator=submitter,
                                          checksum=stat.checksum,
                                          uncompressed_size=stat.size,
                                          source_format=stat.source_format)
            if not validate_command(form, command, submission):
                logger.debug('Command validation failed')
                raise BadRequest(rdata)
            commands = [command]
            if submission.submitter_compiled_preview:
                commands.append(UnConfirmCompiledPreview(creator=submitter))
            try:
                submission, _ = save(*commands, submission_id=submission_id)
            except SaveError:
                alerts.flash_failure(
                    Markup(
                        'There was a problem carrying out your request. Please try'
                        f' again. {PLEASE_CONTACT_SUPPORT}'))
        redirect = url_for('ui.file_upload', submission_id=submission_id)
        return {}, status.SEE_OTHER, {'Location': redirect}
    return rdata, status.OK, {}
Пример #7
0
def delete_all(method: str,
               params: MultiDict,
               session: Session,
               submission_id: int,
               token: Optional[str] = None,
               **kwargs) -> Response:
    """
    Handle a request to delete all files in the workspace.

    Parameters
    ----------
    method : str
        ``GET`` or ``POST``
    params : :class:`MultiDict`
        The query or form data from the request.
    session : :class:`Session`
        The authenticated session for the request.
    submission_id : int
        The identifier of the submission for which the deletion is being made.
    token : str
        The original (encrypted) auth token on the request. Used to perform
        subrequests to the file management service.

    Returns
    -------
    dict
        Response data, to render in template.
    int
        HTTP status code. This should be ``200`` or ``303``, unless something
        goes wrong.
    dict
        Extra headers to add/update on the response. This should include
        the `Location` header for use in the 303 redirect response, if
        applicable.

    """
    if token is None:
        logger.debug('Missing auth token')
        raise BadRequest('Missing auth token')

    submission, submission_events = load_submission(submission_id)
    upload_id = submission.source_content.identifier
    submitter, client = user_and_client_from_session(session)

    rdata = {'submission': submission, 'submission_id': submission_id}

    if method == 'GET':
        form = DeleteAllFilesForm()
        rdata.update({'form': form})
        return rdata, status.OK, {}
    elif method == 'POST':
        form = DeleteAllFilesForm(params)
        rdata.update({'form': form})

        if not (form.validate() and form.confirmed.data):
            logger.debug('Invalid form data')
            raise BadRequest(rdata)

        try:
            stat = FileManager.delete_all(upload_id, token)
        except exceptions.RequestForbidden as e:
            alerts.flash_failure(
                Markup(
                    'There was a problem authorizing your request. Please try'
                    f' again. {PLEASE_CONTACT_SUPPORT}'))
            logger.error('Encountered RequestForbidden: %s', e)
        except exceptions.BadRequest as e:
            alerts.flash_warning(
                Markup('Something odd happened when processing your request.'
                       f'{PLEASE_CONTACT_SUPPORT}'))
            logger.error('Encountered BadRequest: %s', e)
        except exceptions.RequestFailed as e:
            alerts.flash_failure(
                Markup(
                    'There was a problem carrying out your request. Please try'
                    f' again. {PLEASE_CONTACT_SUPPORT}'))
            logger.error('Encountered RequestFailed: %s', e)

        command = UpdateUploadPackage(creator=submitter,
                                      client=client,
                                      checksum=stat.checksum,
                                      uncompressed_size=stat.size,
                                      source_format=stat.source_format)
        commands = [command]
        if submission.submitter_compiled_preview:
            commands.append(UnConfirmCompiledPreview(creator=submitter))
        if not validate_command(form, command, submission):
            logger.debug('Command validation failed')
            raise BadRequest(rdata)

        try:
            submission, _ = save(*commands, submission_id=submission_id)
        except SaveError:
            alerts.flash_failure(
                Markup(
                    'There was a problem carrying out your request. Please try'
                    f' again. {PLEASE_CONTACT_SUPPORT}'))

        redirect = url_for('ui.file_upload', submission_id=submission_id)
        return {}, status.SEE_OTHER, {'Location': redirect}
    raise MethodNotAllowed('Method not supported')
Пример #8
0
def cross_list(method: str, params: MultiDict, session: Session,
               submission_id: int, **kwargs) -> Response:

    submitter, client = user_and_client_from_session(session)
    submission, submission_events = load_submission(submission_id)

    form = ClassificationForm(params)
    form.operation._value = lambda: form.operation.data
    form.filter_choices(submission, session)

    # Create a formset to render removal option.
    #
    # We need forms for existing secondaries, to generate removal requests.
    # When the forms in the formset are submitted, they are handled as the
    # primary form in the POST request to this controller.
    formset = ClassificationForm.formset(submission)
    _primary_category = submission.primary_classification.category
    _primary = taxonomy.CATEGORIES[_primary_category]

    response_data = {
        'submission_id': submission_id,
        'submission': submission,
        'submitter': submitter,
        'client': client,
        'form': form,
        'formset': formset,
        'primary': {
            'id': submission.primary_classification.category,
            'name': _primary['name']
        },
    }

    if method == 'POST':
        # Make sure that the user is not attempting to move to a different
        # step.
        #
        # Since the interface provides an "add" button to add cross-list
        # categories, we only want to handle the form data if the user is not
        # attempting to move to a different step.
        action = params.get('action')
        if not action:
            if not form.validate():
                raise BadRequest(response_data)

            if form.operation.data == form.REMOVE:
                command_type = RemoveSecondaryClassification
            else:
                command_type = AddSecondaryClassification
            command = command_type(category=form.category.data,
                                   creator=submitter,
                                   client=client)
            if not validate_command(form, command, submission, 'category'):
                raise BadRequest(response_data)

            try:
                submission, _ = save(command, submission_id=submission_id)
                response_data['submission'] = submission
            except SaveError as e:
                raise InternalServerError(response_data) from e

            # Re-build the formset to reflect changes that we just made, and
            # generate a fresh form for adding another secondary. The POSTed
            # data should now be reflected in the formset.
            response_data['formset'] = ClassificationForm.formset(submission)
            form = ClassificationForm()
            form.operation._value = lambda: form.operation.data
            form.filter_choices(submission, session)
            response_data['form'] = form

            # Warn the user if they have too many secondaries.
            if len(submission.secondary_categories) > 3:
                alerts.flash_warning(
                    Markup(
                        'Adding more than three cross-list classifications will'
                        ' result in a delay in the acceptance of your submission.'
                    ))

        if action in ['previous', 'save_exit', 'next']:
            return response_data, status.SEE_OTHER, {}
    return response_data, status.OK, {}