Пример #1
0
def view(request_id):
    """
    This function is for testing purposes of the view a request back until backend functionality is implemented.

    :return: redirect to view request page
    """
    try:
        current_request = Requests.query.filter_by(id=request_id).one()
        assert current_request.agency.is_active
    except NoResultFound:
        print("Request with id '{}' does not exist.".format(request_id))
        sentry.captureException()
        return abort(404)
    except AssertionError:
        print("Request belongs to inactive agency.")
        sentry.captureException()
        return abort(404)

    holidays = sorted(get_holidays_date_list(
        datetime.utcnow().year,
        (datetime.utcnow() + rd(years=DEFAULT_YEARS_HOLIDAY_LIST)).year)
    )

    active_users = []
    assigned_users = []
    if current_user.is_agency:
        for agency_user in current_request.agency.active_users:
            if not agency_user in current_request.agency.administrators and (agency_user != current_user):
                # populate list of assigned users that can be removed from a request
                if agency_user in current_request.agency_users:
                    assigned_users.append(agency_user)
                # append to list of active users that can be added to a request
                else:
                    active_users.append(agency_user)

    permissions = {
        'acknowledge': permission.ACKNOWLEDGE,
        'deny': permission.DENY,
        'extend': permission.EXTEND,
        'close': permission.CLOSE,
        're_open': permission.RE_OPEN,
        'add_file': permission.ADD_FILE,
        'edit_file_privacy': permission.EDIT_FILE_PRIVACY,
        'delete_file': permission.DELETE_FILE,
        'add_note': permission.ADD_NOTE,
        'edit_note_privacy': permission.EDIT_NOTE_PRIVACY,
        'delete_note': permission.DELETE_NOTE,
        'add_link': permission.ADD_LINK,
        'edit_link_privacy': permission.EDIT_LINK_PRIVACY,
        'delete_link': permission.DELETE_LINK,
        'add_instructions': permission.ADD_OFFLINE_INSTRUCTIONS,
        'edit_instructions_privacy': permission.EDIT_OFFLINE_INSTRUCTIONS_PRIVACY,
        'delete_instructions': permission.DELETE_OFFLINE_INSTRUCTIONS,
        'generate_letter': permission.GENERATE_LETTER,
        'add_user': permission.ADD_USER_TO_REQUEST,
        'edit_user': permission.EDIT_USER_REQUEST_PERMISSIONS,
        'remove_user': permission.REMOVE_USER_FROM_REQUEST,
        'edit_title': permission.EDIT_TITLE,
        'edit_title_privacy': permission.CHANGE_PRIVACY_TITLE,
        'edit_agency_request_summary': permission.EDIT_AGENCY_REQUEST_SUMMARY,
        'edit_agency_request_summary_privacy': permission.CHANGE_PRIVACY_AGENCY_REQUEST_SUMMARY,
        'edit_requester_info': permission.EDIT_REQUESTER_INFO
    }

    # Build permissions dictionary for checking on the front-end.
    for key, val in permissions.items():
        if current_user.is_anonymous or not current_request.user_requests.filter_by(
                user_guid=current_user.guid).first():
            permissions[key] = False
        else:
            permissions[key] = is_allowed(current_user, request_id, val) if not current_user.is_anonymous else False

    # Build dictionary of current permissions for all assigned users.
    assigned_user_permissions = {}
    for u in assigned_users:
        assigned_user_permissions[u.guid] = UserRequests.query.filter_by(
            request_id=request_id, user_guid=u.guid).one().get_permission_choice_indices()

    point_of_contact = get_current_point_of_contact(request_id)
    if point_of_contact:
        current_point_of_contact = {'user_guid': point_of_contact.user_guid}
    else:
        current_point_of_contact = {'user_guid': ''}

    # Determine if the Agency Request Summary should be shown.
    show_agency_request_summary = False

    if current_user in current_request.agency_users \
            or current_request.agency_request_summary \
            and (current_request.requester == current_user
                 and current_request.status == request_status.CLOSED
                 and not current_request.privacy['agency_request_summary']
                 or current_request.status == request_status.CLOSED
                 and current_request.agency_request_summary_release_date
                 and current_request.agency_request_summary_release_date
                 < datetime.utcnow()
                 and not current_request.privacy['agency_request_summary']):
        show_agency_request_summary = True

    # Determine if the title should be shown.
    show_title = (current_user in current_request.agency_users or
                  current_request.requester == current_user or
                  not current_request.privacy['title'])

    # Determine if "Generate Letter" functionality is enabled for the agency.
    if 'letters' in current_request.agency.agency_features:
        generate_letters_enabled = current_request.agency.agency_features['letters']['generate_letters']
    else:
        generate_letters_enabled = False

    # Determine if custom request forms are enabled
    if 'enabled' in current_request.agency.agency_features['custom_request_forms']:
        custom_request_forms_enabled = current_request.agency.agency_features['custom_request_forms']['enabled']
    else:
        custom_request_forms_enabled = False

    # Determine if custom request form panels should be expanded by default
    if 'expand_by_default' in current_request.agency.agency_features['custom_request_forms']:
        expand_by_default = current_request.agency.agency_features['custom_request_forms']['expand_by_default']
    else:
        expand_by_default = False

    # Determine if request description should be hidden when custom forms are enabled
    if 'description_hidden_by_default' in current_request.agency.agency_features['custom_request_forms']:
        description_hidden_by_default = current_request.agency.agency_features['custom_request_forms']['description_hidden_by_default']
    else:
        description_hidden_by_default = False

    return render_template(
        'request/view_request.html',
        request=current_request,
        status=request_status,
        agency_users=current_request.agency_users,
        edit_requester_form=EditRequesterForm(current_request.requester),
        contact_agency_form=ContactAgencyForm(current_request),
        deny_request_form=DenyRequestForm(current_request.agency.ein),
        close_request_form=CloseRequestForm(current_request.agency.ein),
        reopen_request_form=ReopenRequestForm(current_request.agency.ein),
        remove_user_request_form=RemoveUserRequestForm(assigned_users),
        add_user_request_form=AddUserRequestForm(active_users),
        edit_user_request_form=EditUserRequestForm(assigned_users),
        generate_acknowledgment_letter_form=GenerateAcknowledgmentLetterForm(current_request.agency.ein),
        generate_denial_letter_form=GenerateDenialLetterForm(current_request.agency.ein),
        generate_closing_letter_form=GenerateClosingLetterForm(current_request.agency.ein),
        generate_extension_letter_form=GenerateExtensionLetterForm(current_request.agency.ein),
        generate_envelope_form=GenerateEnvelopeForm(current_request.agency_ein, current_request.requester),
        generate_response_letter_form=GenerateResponseLetterForm(current_request.agency.ein),
        assigned_user_permissions=assigned_user_permissions,
        current_point_of_contact=current_point_of_contact,
        holidays=holidays,
        assigned_users=assigned_users,
        active_users=active_users,
        permissions=permissions,
        show_agency_request_summary=show_agency_request_summary,
        show_title=show_title,
        is_requester=(current_request.requester == current_user),
        permissions_length=len(permission.ALL),
        generate_letters_enabled=generate_letters_enabled,
        custom_request_forms_enabled = custom_request_forms_enabled,
        expand_by_default=expand_by_default,
        description_hidden_by_default=description_hidden_by_default
    )
Пример #2
0
def patch(response_id):
    """
    Edit a response's fields and send a notification email.

    Expects a request body containing field names and updated values.
    Ex:
    {
        'privacy': 'release_public',
        'title': 'new title'
        'filename': 'uploaded_file_name.ext'  # REQUIRED for updates to Files metadata
    }
    Ex (for delete):
    {
        'deleted': true,
        'confirmation': string checked against 'DELETE'
            if the strings do not match, the 'deleted' field will not be updated
    }

    :return: on success:
    {
        'old': { original attributes and their values }
        'new': { updated attributes and their values }
    }

    """
    resp = Responses.query.filter_by(id=response_id, deleted=False).one()

    if current_user.is_anonymous or not resp.is_editable:
        return abort(403)

    patch_form = dict(flask_request.form)

    privacy = patch_form.pop('privacy', None)

    if privacy:
        # Check permissions for editing the privacy if required.

        permission_for_edit_type_privacy = {
            Files: permission.EDIT_FILE_PRIVACY,
            Notes: permission.EDIT_NOTE_PRIVACY,
            Instructions: permission.EDIT_OFFLINE_INSTRUCTIONS_PRIVACY,
            Links: permission.EDIT_LINK_PRIVACY
        }

        if not is_allowed(current_user, resp.request_id, permission_for_edit_type_privacy[type(resp)]):
            return abort(403)

    delete = patch_form.pop('deleted', None)

    if delete:
        confirmation = patch_form.pop('confirmation', None)
        if not confirmation:
            return abort(403)

        permission_for_delete_type = {
            Files: permission.DELETE_FILE,
            Notes: permission.DELETE_NOTE,
            Instructions: permission.DELETE_OFFLINE_INSTRUCTIONS,
            Links: permission.DELETE_LINK
        }

        if not is_allowed(current_user, resp.request_id, permission_for_delete_type[type(resp)]):
            return abort(403)

    if patch_form:
        # Mapping of Response types to permission values
        permission_for_type = {
            Files: permission.EDIT_FILE,
            Notes: permission.EDIT_NOTE,
            Instructions: permission.EDIT_OFFLINE_INSTRUCTIONS,
            Links: permission.EDIT_LINK
        }

        # If the current user does not have the permission to edit the response type, return 403
        if not is_allowed(current_user, resp.request_id, permission_for_type[type(resp)]):
            return abort(403)

    editor_for_type = {
        Files: RespFileEditor,
        Notes: RespNoteEditor,
        Instructions: RespInstructionsEditor,
        Links: RespLinkEditor,
    }
    editor = editor_for_type[type(resp)](current_user, resp, flask_request)

    if editor.errors:
        http_response = {"errors": editor.errors}
    else:
        if editor.no_change:  # TODO: unittest
            http_response = {
                "message": "No changes detected."
            }
        else:
            http_response = {
                "old": editor.data_old,
                "new": editor.data_new
            }
    return jsonify(http_response), 200
Пример #3
0
def get_request_responses():
    """
    Returns a set of responses (id, type, and template),
    ordered by date descending, and starting from a specified index.
    Request parameters:
    - start: (int) starting index
    - request_id: FOIL request id
    - with_template: (default: False) include html (rows and modals) for each response
    """
    start = int(flask_request.args['start'])

    current_request = Requests.query.filter_by(id=flask_request.args['request_id']).one()

    if current_user in current_request.agency_users:
        # If the user is an agency user assigned to the request, all responses can be retrieved.
        responses = Responses.query.filter(
            Responses.request_id == current_request.id,
            ~Responses.id.in_([cm.method_id for cm in CommunicationMethods.query.all()]),
            Responses.type != response_type.EMAIL,
            Responses.deleted == False
        ).order_by(
            desc(Responses.date_modified)
        ).all()
    elif current_user == current_request.requester:
        # If the user is the requester, then only responses that are "Release and Private" or "Release and Public"
        # can be retrieved.
        responses = Responses.query.filter(
            Responses.request_id == current_request.id,
            ~Responses.id.in_([cm.method_id for cm in CommunicationMethods.query.all()]),
            Responses.type != response_type.EMAIL,
            Responses.deleted == False,
            Responses.privacy.in_([response_privacy.RELEASE_AND_PRIVATE, response_privacy.RELEASE_AND_PUBLIC])
        ).order_by(
            desc(Responses.date_modified)
        ).all()

    else:
        # If the user is not an agency user assigned to the request or the requester, then only responses that are
        # "Release and Public" whose release date is not in the future can be retrieved.
        responses = Responses.query.filter(
            Responses.request_id == current_request.id,
            ~Responses.id.in_([cm.method_id for cm in CommunicationMethods.query.all()]),
            Responses.type != response_type.EMAIL,
            Responses.deleted == False,
            Responses.privacy.in_([response_privacy.RELEASE_AND_PUBLIC]),
            Responses.release_date.isnot(None),
            Responses.release_date < datetime.utcnow()
        ).order_by(
            desc(Responses.date_modified)
        ).all()

    total = len(responses)
    responses = responses[start: start + RESPONSES_INCREMENT]
    template_path = 'request/responses/'
    response_jsons = []
    row_count = 0
    for response in responses:
        json = {
            'id': response.id,
            'type': response.type
        }
        if eval_request_bool(flask_request.args.get('with_template')):
            row_count += 1
            row = render_template(
                template_path + 'row.html',
                response=response,
                row_num=start + row_count,
                response_type=response_type,
                determination_type=determination_type,
                show_preview=not (response.type == response_type.DETERMINATION and
                                  (response.dtype == determination_type.ACKNOWLEDGMENT or
                                   response.dtype == determination_type.REOPENING))
            )
            modal = render_template(
                template_path + 'modal.html',
                response=response,
                requires_workflow=response.type in response_type.EMAIL_WORKFLOW_TYPES,
                modal_body=render_template(
                    "{}modal_body/{}.html".format(
                        template_path, response.type
                    ),
                    response=response,
                    privacies=[response_privacy.RELEASE_AND_PUBLIC,
                               response_privacy.RELEASE_AND_PRIVATE,
                               response_privacy.PRIVATE],
                    determination_type=determination_type,
                    request_status=request_status,
                    edit_response_privacy_permission=is_allowed(user=current_user,
                                                                request_id=response.request_id,
                                                                permission=get_permission(
                                                                    permission_type='privacy',
                                                                    response_type=type(
                                                                        response))),
                    edit_response_permission=is_allowed(user=current_user,
                                                        request_id=response.request_id,
                                                        permission=get_permission(permission_type='edit',
                                                                                  response_type=type(
                                                                                      response))),
                    delete_response_permission=is_allowed(user=current_user,
                                                          request_id=response.request_id,
                                                          permission=get_permission(permission_type='delete',
                                                                                    response_type=type(response))),
                    is_editable=response.is_editable,
                    current_request=current_request

                ),
                response_type=response_type,
                determination_type=determination_type,
                request_status=request_status,
                edit_response_permission=is_allowed(user=current_user,
                                                    request_id=response.request_id,
                                                    permission=get_permission(permission_type='edit',
                                                                              response_type=type(response))),
                delete_response_permission=is_allowed(user=current_user,
                                                      request_id=response.request_id,
                                                      permission=get_permission(permission_type='delete',
                                                                                response_type=type(response))),
                edit_response_privacy_permission=is_allowed(user=current_user,
                                                            request_id=response.request_id,
                                                            permission=get_permission(
                                                                permission_type='privacy',
                                                                response_type=type(
                                                                    response))),
                is_editable=response.is_editable,
                current_request=current_request
            )
            json['template'] = row + modal

        response_jsons.append(json)

    return jsonify(responses=response_jsons, total=total)
Пример #4
0
def view(request_id):
    """
    This function is for testing purposes of the view a request back until backend functionality is implemented.

    :return: redirect to view request page
    """
    try:
        current_request = Requests.query.filter_by(id=request_id).one()
        assert current_request.agency.is_active
    except NoResultFound:
        print("Request with id '{}' does not exist.".format(request_id))
        sentry.captureException()
        return abort(404)
    except AssertionError:
        print("Request belongs to inactive agency.")
        sentry.captureException()
        return abort(404)

    holidays = sorted(
        get_holidays_date_list(datetime.utcnow().year,
                               (datetime.utcnow() +
                                rd(years=DEFAULT_YEARS_HOLIDAY_LIST)).year))

    active_users = []
    assigned_users = []
    if current_user.is_agency:
        for agency_user in current_request.agency.active_users:
            if not agency_user in current_request.agency.administrators and (
                    agency_user != current_user):
                # populate list of assigned users that can be removed from a request
                if agency_user in current_request.agency_users:
                    assigned_users.append(agency_user)
                # append to list of active users that can be added to a request
                else:
                    active_users.append(agency_user)

    permissions = {
        'acknowledge': permission.ACKNOWLEDGE,
        'deny': permission.DENY,
        'extend': permission.EXTEND,
        'close': permission.CLOSE,
        're_open': permission.RE_OPEN,
        'add_file': permission.ADD_FILE,
        'edit_file_privacy': permission.EDIT_FILE_PRIVACY,
        'delete_file': permission.DELETE_FILE,
        'add_note': permission.ADD_NOTE,
        'edit_note_privacy': permission.EDIT_NOTE_PRIVACY,
        'delete_note': permission.DELETE_NOTE,
        'add_link': permission.ADD_LINK,
        'edit_link_privacy': permission.EDIT_LINK_PRIVACY,
        'delete_link': permission.DELETE_LINK,
        'add_instructions': permission.ADD_OFFLINE_INSTRUCTIONS,
        'edit_instructions_privacy':
        permission.EDIT_OFFLINE_INSTRUCTIONS_PRIVACY,
        'delete_instructions': permission.DELETE_OFFLINE_INSTRUCTIONS,
        'generate_letter': permission.GENERATE_LETTER,
        'add_user': permission.ADD_USER_TO_REQUEST,
        'edit_user': permission.EDIT_USER_REQUEST_PERMISSIONS,
        'remove_user': permission.REMOVE_USER_FROM_REQUEST,
        'edit_title': permission.EDIT_TITLE,
        'edit_title_privacy': permission.CHANGE_PRIVACY_TITLE,
        'edit_agency_request_summary': permission.EDIT_AGENCY_REQUEST_SUMMARY,
        'edit_agency_request_summary_privacy':
        permission.CHANGE_PRIVACY_AGENCY_REQUEST_SUMMARY,
        'edit_requester_info': permission.EDIT_REQUESTER_INFO
    }

    # Build permissions dictionary for checking on the front-end.
    for key, val in permissions.items():
        if current_user.is_anonymous or not current_request.user_requests.filter_by(
                user_guid=current_user.guid,
                auth_user_type=current_user.auth_user_type).first():
            permissions[key] = False
        else:
            permissions[key] = is_allowed(
                current_user, request_id,
                val) if not current_user.is_anonymous else False

    # Build dictionary of current permissions for all assigned users.
    assigned_user_permissions = {}
    for u in assigned_users:
        assigned_user_permissions[u.guid] = UserRequests.query.filter_by(
            request_id=request_id,
            user_guid=u.guid).one().get_permission_choice_indices()

    point_of_contact = get_current_point_of_contact(request_id)
    if point_of_contact:
        current_point_of_contact = {'user_guid': point_of_contact.user_guid}
    else:
        current_point_of_contact = {'user_guid': ''}

    # Determine if the Agency Request Summary should be shown.
    show_agency_request_summary = False

    if current_user in current_request.agency_users \
            or current_request.agency_request_summary \
            and (current_request.requester == current_user
                 and current_request.status == request_status.CLOSED
                 and not current_request.privacy['agency_request_summary']
                 or current_request.status == request_status.CLOSED
                 and current_request.agency_request_summary_release_date
                 and current_request.agency_request_summary_release_date
                 < datetime.utcnow()
                 and not current_request.privacy['agency_request_summary']):
        show_agency_request_summary = True

    # Determine if the title should be shown.
    show_title = (current_user in current_request.agency_users
                  or current_request.requester == current_user
                  or not current_request.privacy['title'])

    # Determine if "Generate Letter" functionality is enabled for the agency.

    if 'letters' in current_request.agency.agency_features:
        generate_letters_enabled = current_request.agency.agency_features[
            'letters']['generate_letters']
    else:
        generate_letters_enabled = False

    return render_template(
        'request/view_request.html',
        request=current_request,
        status=request_status,
        agency_users=current_request.agency_users,
        edit_requester_form=EditRequesterForm(current_request.requester),
        contact_agency_form=ContactAgencyForm(current_request),
        deny_request_form=DenyRequestForm(current_request.agency.ein),
        close_request_form=CloseRequestForm(current_request.agency.ein),
        remove_user_request_form=RemoveUserRequestForm(assigned_users),
        add_user_request_form=AddUserRequestForm(active_users),
        edit_user_request_form=EditUserRequestForm(assigned_users),
        generate_acknowledgment_letter_form=GenerateAcknowledgmentLetterForm(
            current_request.agency.ein),
        generate_denial_letter_form=GenerateDenialLetterForm(
            current_request.agency.ein),
        generate_closing_letter_form=GenerateClosingLetterForm(
            current_request.agency.ein),
        generate_extension_letter_form=GenerateExtensionLetterForm(
            current_request.agency.ein),
        generate_envelope_form=GenerateEnvelopeForm(current_request.agency_ein,
                                                    current_request.requester),
        generate_response_letter_form=GenerateResponseLetterForm(
            current_request.agency.ein),
        assigned_user_permissions=assigned_user_permissions,
        current_point_of_contact=current_point_of_contact,
        holidays=holidays,
        assigned_users=assigned_users,
        active_users=active_users,
        permissions=permissions,
        show_agency_request_summary=show_agency_request_summary,
        show_title=show_title,
        is_requester=(current_request.requester == current_user),
        permissions_length=len(permission.ALL),
        generate_letters_enabled=generate_letters_enabled)
Пример #5
0
def post(request_id):
    """
    Create a new upload.

    Handles chunked files through the Content-Range header.
    For filesize validation and more upload logic, see:
        /static/js/upload/fileupload.js

    Optional request body parameters:
    - update (bool)
        save the uploaded file to the 'updated' directory
        (this indicates the file is meant to replace
        a previously uploaded file)
    - response_id (int)
        the id of a response associated with the file
        this upload is replacing
        - REQUIRED if 'update' is 'true'
        - ignored if 'update' is 'false'

    :returns: {
        "name": file name,
        "size": file size
    }
    """
    files = request.files
    file_ = files[next(files.keys())]
    filename = secure_filename(file_.filename)
    is_update = eval_request_bool(request.form.get('update'))
    agency_ein = Requests.query.filter_by(id=request_id).one().agency.ein
    if is_allowed(user=current_user, request_id=request_id, permission=permission.ADD_FILE) or \
            is_allowed(user=current_user, request_id=request_id, permission=permission.EDIT_FILE):
        response_id = request.form.get('response_id') if is_update else None
        if upload_exists(request_id, filename, response_id):
            response = {
                "files": [{
                    "name": filename,
                    "error": "A file with this name has already "
                             "been uploaded for this request."
                    # TODO: "link": <link-to-existing-file> ? would be nice
                }]
            }
        else:
            upload_path = os.path.join(
                current_app.config['UPLOAD_QUARANTINE_DIRECTORY'],
                request_id)
            if not os.path.exists(upload_path):
                os.mkdir(upload_path)
            filepath = os.path.join(upload_path, filename)
            key = get_upload_key(request_id, filename, is_update)

            try:
                if CONTENT_RANGE_HEADER in request.headers:
                    start, size = parse_content_range(
                        request.headers[CONTENT_RANGE_HEADER])

                    # Only validate mime type on first chunk
                    valid_file_type = True
                    file_type = None
                    if start == 0:
                        valid_file_type, file_type = is_valid_file_type(file_)
                        if current_user.is_agency_active(agency_ein):
                            valid_file_type = True
                        if os.path.exists(filepath):
                            # remove existing file (upload 'restarted' for same file)
                            os.remove(filepath)

                    if valid_file_type:
                        redis.set(key, upload_status.PROCESSING)
                        with open(filepath, 'ab') as fp:
                            fp.seek(start)
                            fp.write(file_.stream.read())
                        # scan if last chunk written
                        if os.path.getsize(filepath) == size:
                            scan_and_complete_upload.delay(request_id, filepath, is_update, response_id)
                else:
                    valid_file_type, file_type = is_valid_file_type(file_)
                    if current_user.is_agency_active(agency_ein):
                        valid_file_type = True
                    if valid_file_type:
                        redis.set(key, upload_status.PROCESSING)
                        file_.save(filepath)
                        scan_and_complete_upload.delay(request_id, filepath, is_update, response_id)

                if not valid_file_type:
                    response = {
                        "files": [{
                            "name": filename,
                            "error": "The file type '{}' is not allowed.".format(
                                file_type)
                        }]
                    }
                else:
                    response = {
                        "files": [{
                            "name": filename,
                            "original_name": file_.filename,
                            "size": os.path.getsize(filepath),
                        }]
                    }
            except Exception as e:
                sentry.captureException()
                redis.set(key, upload_status.ERROR)
                current_app.logger.exception("Upload for file '{}' failed: {}".format(filename, e))
                response = {
                    "files": [{
                        "name": filename,
                        "error": "There was a problem uploading this file."
                    }]
                }

        return jsonify(response), 200
Пример #6
0
def delete(r_id_type, r_id, filecode):
    """
    Removes an uploaded file.

    :param r_id_type: "response" or "request"
    :param r_id: the Response or Request identifier
    :param filecode: the encoded name of the uploaded file
        (base64 without padding)

    Optional request body parameters:
    - quarantined_only (bool)
        only delete the file if it is quarantined
        (beware: takes precedence over 'updated_only')
    - updated_only (bool)
        only delete the file if it is in the 'updated' directory

    :returns:
        On success:
            { "deleted": filename }
        On failure:
            { "error": error message }
    """
    filename = secure_filename(b64decode_lenient(filecode))
    if r_id_type not in ["request", "response"]:
        response = {"error": "Invalid ID type."}
    else:
        try:
            if r_id_type == "response":
                response = Responses.query.filter_by(id=r_id, deleted=False)
                r_id = response.request_id

            path = ''
            quarantined_only = eval_request_bool(request.form.get('quarantined_only'))
            has_add_edit = (is_allowed(user=current_user, request_id=r_id, permission=permission.ADD_FILE) or
                            is_allowed(user=current_user, request_id=r_id, permission=permission.EDIT_FILE))
            if quarantined_only and has_add_edit:
                path = os.path.join(
                    current_app.config['UPLOAD_QUARANTINE_DIRECTORY'],
                    r_id
                )
            elif eval_request_bool(request.form.get('updated_only')) and \
                    is_allowed(user=current_user, request_id=r_id, permission=permission.EDIT_FILE):
                path = os.path.join(
                    current_app.config['UPLOAD_DIRECTORY'],
                    r_id,
                    UPDATED_FILE_DIRNAME
                )
            else:
                path_for_status = {
                    upload_status.PROCESSING: current_app.config['UPLOAD_QUARANTINE_DIRECTORY'],
                    upload_status.SCANNING: current_app.config['UPLOAD_QUARANTINE_DIRECTORY'],
                    upload_status.READY: current_app.config['UPLOAD_DIRECTORY']
                }
                status = redis.get(get_upload_key(r_id, filename))
                if status is not None:
                    dest_path = path_for_status[status.decode("utf-8")]
                    if (dest_path == current_app.config['UPLOAD_QUARANTINE_DIRECTORY'] and has_add_edit) or (
                        dest_path == current_app.config['UPLOAD_DIRECTORY'] and
                            is_allowed(user=current_user, request_id=r_id, permission=permission.ADD_FILE)
                    ):
                        path = os.path.join(
                            dest_path,
                            r_id
                        )
            filepath = os.path.join(path, filename)
            found = False
            if path != '':
                if quarantined_only:
                    if os.path.exists(filepath):
                        os.remove(filepath)
                        found = True
                else:
                    if fu.exists(filepath):
                        fu.remove(filepath)
                        found = True
            if found:
                response = {"deleted": filename}
            else:
                response = {"error": "Upload not found."}
        except Exception as e:
            sentry.captureException()
            current_app.logger.exception("Error on DELETE /upload/: {}".format(e))
            response = {"error": "Failed to delete '{}'".format(filename)}

    return jsonify(response), 200
Пример #7
0
def get_request_responses():
    """
    Returns a set of responses (id, type, and template),
    ordered by date descending, and starting from a specified index.

    Request parameters:
    - start: (int) starting index
    - request_id: FOIL request id
    - with_template: (default: False) include html (rows and modals) for each response
    """
    start = int(flask_request.args['start'])

    current_request = Requests.query.filter_by(
        id=flask_request.args['request_id']).one()

    responses = Responses.query.filter(
        Responses.request_id == current_request.id,
        Responses.type != response_type.EMAIL,
        Responses.deleted == False).order_by(desc(
            Responses.date_modified)).all()[start:start + RESPONSES_INCREMENT]

    template_path = 'request/responses/'
    response_jsons = []
    row_count = 0
    for response in responses:
        # If a user is anonymous or a public user who is not the requester AND the date for Release and Public is in
        # the future, do not generate response row

        if (current_user in response.request.agency_users) or (
                current_user == response.request.requester
                and response.privacy != response_privacy.PRIVATE) or (
                    response.privacy == response_privacy.RELEASE_AND_PUBLIC
                    and response.release_date
                    and response.release_date < datetime.utcnow()):
            json = {'id': response.id, 'type': response.type}
            if eval_request_bool(flask_request.args.get('with_template')):
                row_count += 1
                row = render_template(
                    template_path + 'row.html',
                    response=response,
                    row_num=start + row_count,
                    response_type=response_type,
                    determination_type=determination_type,
                    show_preview=not (
                        response.type == response_type.DETERMINATION and
                        (response.dtype == determination_type.ACKNOWLEDGMENT
                         or response.dtype == determination_type.REOPENING)))
                modal = render_template(
                    template_path + 'modal.html',
                    response=response,
                    requires_workflow=response.type
                    in response_type.EMAIL_WORKFLOW_TYPES,
                    modal_body=render_template(
                        "{}modal_body/{}.html".format(template_path,
                                                      response.type),
                        response=response,
                        privacies=[
                            response_privacy.RELEASE_AND_PUBLIC,
                            response_privacy.RELEASE_AND_PRIVATE,
                            response_privacy.PRIVATE
                        ],
                        determination_type=determination_type,
                        request_status=request_status,
                        edit_response_privacy_permission=is_allowed(
                            user=current_user,
                            request_id=response.request_id,
                            permission=get_permission(
                                permission_type='privacy',
                                response_type=type(response))),
                        edit_response_permission=is_allowed(
                            user=current_user,
                            request_id=response.request_id,
                            permission=get_permission(
                                permission_type='edit',
                                response_type=type(response))),
                        delete_response_permission=is_allowed(
                            user=current_user,
                            request_id=response.request_id,
                            permission=get_permission(
                                permission_type='delete',
                                response_type=type(response))),
                        is_editable=response.is_editable,
                        current_request=current_request),
                    response_type=response_type,
                    determination_type=determination_type,
                    request_status=request_status,
                    edit_response_permission=is_allowed(
                        user=current_user,
                        request_id=response.request_id,
                        permission=get_permission(
                            permission_type='edit',
                            response_type=type(response))),
                    delete_response_permission=is_allowed(
                        user=current_user,
                        request_id=response.request_id,
                        permission=get_permission(
                            permission_type='delete',
                            response_type=type(response))),
                    edit_response_privacy_permission=is_allowed(
                        user=current_user,
                        request_id=response.request_id,
                        permission=get_permission(
                            permission_type='privacy',
                            response_type=type(response))),
                    is_editable=response.is_editable,
                    current_request=current_request)
                json['template'] = row + modal

            response_jsons.append(json)

    return jsonify(responses=response_jsons)
Пример #8
0
def patch(response_id):
    """
    Edit a response's fields and send a notification email.

    Expects a request body containing field names and updated values.
    Ex:
    {
        'privacy': 'release_public',
        'title': 'new title'
        'filename': 'uploaded_file_name.ext'  # REQUIRED for updates to Files metadata
    }
    Ex (for delete):
    {
        'deleted': true,
        'confirmation': string checked against 'DELETE'
            if the strings do not match, the 'deleted' field will not be updated
    }

    :return: on success:
    {
        'old': { original attributes and their values }
        'new': { updated attributes and their values }
    }

    """
    resp = Responses.query.filter_by(id=response_id, deleted=False).one()

    if current_user.is_anonymous or not resp.is_editable:
        return abort(403)

    patch_form = dict(flask_request.form)

    privacy = patch_form.pop('privacy', None)

    if privacy:
        # Check permissions for editing the privacy if required.

        permission_for_edit_type_privacy = {
            Files: permission.EDIT_FILE_PRIVACY,
            Notes: permission.EDIT_NOTE_PRIVACY,
            Instructions: permission.EDIT_OFFLINE_INSTRUCTIONS_PRIVACY,
            Links: permission.EDIT_LINK_PRIVACY
        }

        if not is_allowed(current_user, resp.request_id,
                          permission_for_edit_type_privacy[type(resp)]):
            return abort(403)

    delete = patch_form.pop('deleted', None)

    if delete:
        confirmation = patch_form.pop('confirmation', None)
        if not confirmation:
            return abort(403)

        permission_for_delete_type = {
            Files: permission.DELETE_FILE,
            Notes: permission.DELETE_NOTE,
            Instructions: permission.DELETE_OFFLINE_INSTRUCTIONS,
            Links: permission.DELETE_LINK
        }

        if not is_allowed(current_user, resp.request_id,
                          permission_for_delete_type[type(resp)]):
            return abort(403)

    if patch_form:
        # Mapping of Response types to permission values
        permission_for_type = {
            Files: permission.EDIT_FILE,
            Notes: permission.EDIT_NOTE,
            Instructions: permission.EDIT_OFFLINE_INSTRUCTIONS,
            Links: permission.EDIT_LINK
        }

        # If the current user does not have the permission to edit the response type, return 403
        if not is_allowed(current_user, resp.request_id,
                          permission_for_type[type(resp)]):
            return abort(403)

    editor_for_type = {
        Files: RespFileEditor,
        Notes: RespNoteEditor,
        Instructions: RespInstructionsEditor,
        Links: RespLinkEditor,
    }
    editor = editor_for_type[type(resp)](current_user, resp, flask_request)

    if editor.errors:
        http_response = {"errors": editor.errors}
    else:
        if editor.no_change:  # TODO: unittest
            http_response = {"message": "No changes detected."}
        else:
            http_response = {"old": editor.data_old, "new": editor.data_new}
    return jsonify(http_response), 200