Ejemplo n.º 1
0
def status():
    """
    Check the status of an upload.

    Request Parameters:
        - request_id
        - filename
        - for_update (bool, optional)

    :returns: {
        "status": upload status
    }
    """
    try:
        status = redis.get(
            get_upload_key(request.args['request_id'],
                           secure_filename(request.args['filename']),
                           eval_request_bool(request.args.get('for_update'))))
        if status is not None:
            response = {"status": status.decode("utf-8")}
        else:
            response = {"error": "Upload status not found."}
        status_code = 200
    except KeyError:
        response = {}
        status_code = 422

    return jsonify(response), status_code
Ejemplo n.º 2
0
def status():
    """
    Check the status of an upload.

    Request Parameters:
        - request_id
        - filename
        - for_update (bool, optional)

    :returns: {
        "status": upload status
    }
    """
    try:
        status = redis.get(
            get_upload_key(
                request.args['request_id'],
                secure_filename(request.args['filename']),
                eval_request_bool(request.args.get('for_update'))
            )
        )
        if status is not None:
            response = {"status": status.decode("utf-8")}
        else:
            response = {"error": "Upload status not found."}
        status_code = 200
    except KeyError:
        sentry.captureException()
        response = {}
        status_code = 422

    return jsonify(response), status_code
Ejemplo n.º 3
0
def oauth_logout():
    timed_out = eval_request_bool(request.args.get('timeout'))
    forced_logout = eval_request_bool(request.args.get('forced_logout'))
    if forced_logout:
        user_session = redis_get_user_session(current_user.session_id)
        if user_session is not None:
            user_session.destroy()
    if timed_out:
        flash("Your session timed out. Please login again", category='info')
    if 'token' in session:
        revoke_and_remove_access_token()
    if current_user.is_anonymous:
        return redirect(url_for("main.index"))
    update_object({'session_id': None}, Users, (current_user.guid, current_user.auth_user_type))
    logout_user()
    session.destroy()
    if forced_logout:
        return redirect(url_for("auth.login"))
    return redirect(url_for("main.index"))
Ejemplo n.º 4
0
def patch(agency_ein):
    """
    Only accessible by Super Users

    Currently only changes:
        is_active
    """
    if not current_user.is_anonymous and current_user.is_super:
        is_active = request.form.get('is_active')
        if is_active is not None and Agencies.query.filter_by(
                ein=agency_ein).first() is not None:
            update_object({'is_active': eval_request_bool(is_active)},
                          Agencies, agency_ein)
            create_object(
                Events(request_id=None,
                       user_guid=current_user.guid,
                       auth_user_type=current_user.auth_user_type,
                       type_=AGENCY_ACTIVATED,
                       new_value={"ein": agency_ein},
                       timestamp=datetime.utcnow()))
            return '', 200
        return '', 400
    return '', 403
Ejemplo n.º 5
0
def new():
    """
    Create a new FOIL request
    sends a confirmation email after the Requests object is created.

    title: request title
    description: request description
    agency: agency selected for the request
    submission: submission method for the request

    :return: redirect to homepage on successful form validation
     if form fields are missing or has improper values, backend error messages (WTForms) will appear
    """
    site_key = current_app.config["RECAPTCHA_SITE_KEY"]

    kiosk_mode = eval_request_bool(
        escape(flask_request.args.get("kiosk_mode", False)))
    category = str(escape(flask_request.args.get("category", None)))
    agency = str(escape(flask_request.args.get("agency", None)))
    title = str(escape(flask_request.args.get("title", None)))

    if current_user.is_public:
        form = PublicUserRequestForm()
        template_suffix = "user.html"
    elif current_user.is_anonymous:
        form = AnonymousRequestForm()
        template_suffix = "anon.html"
    elif current_user.is_agency:
        form = AgencyUserRequestForm()
        template_suffix = "agency.html"
    else:
        raise InvalidUserException(current_user)

    new_request_template = "request/new_request_" + template_suffix

    if flask_request.method == "POST":
        # validate upload with no request id available
        upload_path = None
        if form.request_file.data:
            form.request_file.validate(form)
            upload_path = handle_upload_no_id(form.request_file)
            if form.request_file.errors:
                return render_template(new_request_template,
                                       form=form,
                                       site_key=site_key)

        custom_metadata = json.loads(
            flask_request.form.get("custom-request-forms-data", {}))
        tz_name = (flask_request.form["tz-name"]
                   if flask_request.form["tz-name"] else
                   current_app.config["APP_TIMEZONE"])
        if current_user.is_public:
            request_id = create_request(
                form.request_title.data,
                form.request_description.data,
                form.request_category.data,
                agency_ein=form.request_agency.data,
                upload_path=upload_path,
                tz_name=tz_name,
                custom_metadata=custom_metadata,
            )
        elif current_user.is_agency:
            request_id = create_request(
                form.request_title.data,
                form.request_description.data,
                category=None,
                agency_ein=(form.request_agency.data
                            if form.request_agency.data != "None" else
                            current_user.default_agency_ein),
                submission=form.method_received.data,
                agency_date_submitted_local=form.request_date.data,
                email=form.email.data,
                first_name=form.first_name.data,
                last_name=form.last_name.data,
                user_title=form.user_title.data,
                organization=form.user_organization.data,
                phone=form.phone.data,
                fax=form.fax.data,
                address=get_address(form),
                upload_path=upload_path,
                tz_name=tz_name,
                custom_metadata=custom_metadata,
            )
        else:  # Anonymous User
            request_id = create_request(
                form.request_title.data,
                form.request_description.data,
                form.request_category.data,
                agency_ein=form.request_agency.data,
                email=form.email.data,
                first_name=form.first_name.data,
                last_name=form.last_name.data,
                user_title=form.user_title.data,
                organization=form.user_organization.data,
                phone=form.phone.data,
                fax=form.fax.data,
                address=get_address(form),
                upload_path=upload_path,
                tz_name=tz_name,
                custom_metadata=custom_metadata,
            )

        current_request = Requests.query.filter_by(id=request_id).first()
        requester = current_request.requester

        send_confirmation_email(request=current_request,
                                agency=current_request.agency,
                                user=requester)

        if current_request.agency.is_active:
            if requester.email:
                flashed_message_html = render_template(
                    "request/confirmation_email.html")
                flash(Markup(flashed_message_html), category="success")
            else:
                flashed_message_html = render_template(
                    "request/confirmation_non_email.html")
                flash(Markup(flashed_message_html), category="warning")

            return redirect(url_for("request.view", request_id=request_id))
        else:
            flashed_message_html = render_template(
                "request/non_portal_agency_message.html",
                agency=current_request.agency)
            flash(Markup(flashed_message_html), category="warning")
            return redirect(
                url_for("request.non_portal_agency",
                        agency_name=current_request.agency.name))

    return render_template(
        new_request_template,
        form=form,
        site_key=site_key,
        kiosk_mode=kiosk_mode,
        category=category,
        agency=agency,
        title=title,
    )
Ejemplo n.º 6
0
def update_agency_active_status(agency_ein, is_active):
    """
    Update the active status of an agency.
    :param agency_ein: String identifier for agency (4 characters)
    :param is_active: Boolean value for agency active status (True = Active)
    :return: Boolean value (True if successfully changed active status)
    """
    agency = Agencies.query.filter_by(ein=agency_ein).first()
    is_valid_agency = agency is not None
    activate_agency = eval_request_bool(is_active)

    if is_active is not None and is_valid_agency:
        update_object(
            {'is_active': activate_agency},
            Agencies,
            agency_ein
        )
        if activate_agency:
            create_object(
                Events(
                    request_id=None,
                    user_guid=current_user.guid,
                    type_=AGENCY_ACTIVATED,
                    previous_value={"ein": agency_ein, "is_active": "False"},
                    new_value={"ein": agency_ein, "is_active": "True"},
                    timestamp=datetime.utcnow()
                )
            )
            # create request documents
            for request in agency.requests:
                request.es_create()

            return True
        else:
            create_object(
                Events(
                    request_id=None,
                    user_guid=current_user.guid,
                    type_=AGENCY_DEACTIVATED,
                    previous_value={"ein": agency_ein, "is_active": "True"},
                    new_value={"ein": agency_ein, "is_active": "False"},
                    timestamp=datetime.utcnow()
                )
            )
            # remove requests from index
            for request in agency.requests:
                request.es_delete()
            # deactivate agency users
            for user in agency.active_users:
                update_object(
                    {"is_agency_active": "False",
                     "is_agency_admin": "False"},
                    AgencyUsers,
                    (user.guid, agency_ein)
                )
                create_object(
                    Events(
                        request_id=None,
                        user_guid=current_user.guid,
                        type_=AGENCY_USER_DEACTIVATED,
                        previous_value={"user_guid": user.guid,
                                        "ein": agency_ein,
                                        "is_active": "True"},
                        new_value={"user_guid": user.guid,
                                   "ein": agency_ein,
                                   "is_active": "False"},
                        timestamp=datetime.utcnow()
                    )
                )

            return True
    return False
Ejemplo n.º 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 == response.request.requester
                or current_user in response.request.agency_users) or (
                    response.privacy != response_privacy.PRIVATE
                    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)
Ejemplo n.º 8
0
def requests_doc(doc_type):
    """
    Converts and sends the a search result-set as a
    file of the specified document type.
    - Filtering on set size is ignored; all results are returned.
    - Currently only supports CSVs.

    Document name format: "FOIL_requests_results_<timestamp:MM_DD_YYYY_at_HH_mm_pp>"

    Request parameters are identical to those of /search/requests.

    :param doc_type: document type ('csv' only)
    """
    if current_user.is_agency and doc_type.lower() == 'csv':
        try:
            agency_ein = request.args.get('agency_ein', '')
        except ValueError:
            sentry.captureException()
            agency_ein = None

        tz_name = request.args.get('tz_name', current_app.config['APP_TIMEZONE'])

        start = 0
        buffer = StringIO()  # csvwriter cannot accept BytesIO
        writer = csv.writer(buffer)
        writer.writerow(["FOIL ID",
                         "Agency",
                         "Title",
                         "Description",
                         "Agency Description",
                         "Current Status",
                         "Date Created",
                         "Date Received",
                         "Date Due",
                         "Date Closed",
                         "Requester Name",
                         "Requester Email",
                         "Requester Title",
                         "Requester Organization",
                         "Requester Phone Number",
                         "Requester Fax Number",
                         "Requester Address 1",
                         "Requester Address 2",
                         "Requester City",
                         "Requester State",
                         "Requester Zipcode",
                         "Assigned User Emails"])
        results = search_requests(
            query=request.args.get('query'),
            foil_id=eval_request_bool(request.args.get('foil_id')),
            title=eval_request_bool(request.args.get('title')),
            agency_request_summary=eval_request_bool(request.args.get('agency_request_summary')),
            description=eval_request_bool(request.args.get('description')) if not current_user.is_anonymous else False,
            requester_name=eval_request_bool(request.args.get('requester_name')) if current_user.is_agency else False,
            date_rec_from=request.args.get('date_rec_from'),
            date_rec_to=request.args.get('date_rec_to'),
            date_due_from=request.args.get('date_due_from'),
            date_due_to=request.args.get('date_due_to'),
            date_closed_from=request.args.get('date_closed_from'),
            date_closed_to=request.args.get('date_closed_to'),
            agency_ein=agency_ein,
            agency_user_guid=request.args.get('agency_user'),
            open_=eval_request_bool(request.args.get('open')),
            closed=eval_request_bool(request.args.get('closed')),
            in_progress=eval_request_bool(request.args.get('in_progress')) if current_user.is_agency else False,
            due_soon=eval_request_bool(request.args.get('due_soon')) if current_user.is_agency else False,
            overdue=eval_request_bool(request.args.get('overdue')) if current_user.is_agency else False,
            start=start,
            sort_date_received=request.args.get('sort_date_submitted'),
            sort_date_due=request.args.get('sort_date_due'),
            sort_title=request.args.get('sort_title'),
            tz_name=request.args.get('tz_name', current_app.config['APP_TIMEZONE']),
            for_csv=True
        )
        ids = [result["_id"] for result in results]
        all_requests = Requests.query.filter(Requests.id.in_(ids)).options(
            joinedload(Requests.agency_users)).options(joinedload(Requests.requester)).options(
            joinedload(Requests.agency)).all()
        for req in all_requests:
            writer.writerow([
                req.id,
                req.agency.name,
                req.title,
                req.description,
                req.agency_request_summary,
                req.status,
                req.date_created,
                req.date_submitted,
                req.due_date,
                req.date_closed,
                req.requester.name,
                req.requester.email,
                req.requester.title,
                req.requester.organization,
                req.requester.phone_number,
                req.requester.fax_number,
                req.requester.mailing_address.get('address_one'),
                req.requester.mailing_address.get('address_two'),
                req.requester.mailing_address.get('city'),
                req.requester.mailing_address.get('state'),
                req.requester.mailing_address.get('zip'),
                ", ".join(u.email for u in req.agency_users)])
        dt = datetime.utcnow()
        timestamp = utc_to_local(dt, tz_name) if tz_name is not None else dt
        return send_file(
            BytesIO(buffer.getvalue().encode('UTF-8')),  # convert to bytes
            attachment_filename="FOIL_requests_results_{}.csv".format(
                timestamp.strftime("%m_%d_%Y_at_%I_%M_%p")),
            as_attachment=True
        )
    return '', 400
Ejemplo n.º 9
0
def get_request_events():
    """
    Returns a set of events (id, type, and template),
    ordered by date descending, and starting from a specific index.

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

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

    events = Events.query.filter(
        Events.request_id == current_request.id,
        Events.type.in_(event_type.FOR_REQUEST_HISTORY)
    ).order_by(
        desc(Events.timestamp)
    ).all()
    total = len(events)
    events = events[start: start + EVENTS_INCREMENT]

    template_path = 'request/events/'
    event_jsons = []

    types_with_modal = [
        event_type.FILE_EDITED,
        event_type.INSTRUCTIONS_EDITED,
        event_type.LINK_EDITED,
        event_type.NOTE_EDITED,
        event_type.REQ_AGENCY_REQ_SUM_EDITED,
        event_type.REQ_AGENCY_REQ_SUM_PRIVACY_EDITED,
        event_type.REQ_TITLE_EDITED,
        event_type.REQ_TITLE_PRIVACY_EDITED,
        event_type.REQUESTER_INFO_EDITED,
        event_type.USER_PERM_CHANGED,
    ]

    for i, event in enumerate(events):
        json = {
            'id': event.id,
            'type': event.type
        }

        if eval_request_bool(flask_request.args.get('with_template')):
            has_modal = event.type in types_with_modal
            row = render_template(
                template_path + 'row.html',
                event=event,
                row_num=start + i + 1,
                has_modal=has_modal
            )
            if has_modal:
                if event.type == event_type.USER_PERM_CHANGED:
                    previous_permissions = set([
                        p.label for p in get_permissions_as_list(event.previous_value['permissions'])
                    ])
                    new_permissions = set([
                        p.label for p in get_permissions_as_list(event.new_value['permissions'])
                    ])
                    modal = render_template(
                        template_path + 'modal.html',
                        event=event,
                        modal_body=render_template(
                            "{}modal_body/{}.html".format(
                                template_path, event.type.lower()
                            ),
                            event=event,
                            permissions_granted=list(new_permissions - previous_permissions),
                            permissions_revoked=list(previous_permissions - new_permissions),
                        ),
                    )
                else:
                    modal = render_template(
                        template_path + 'modal.html',
                        modal_body=render_template(
                            "{}modal_body/{}.html".format(
                                template_path, event.type.lower()
                            ),
                            event=event
                        ),
                        event=event,
                    )
            else:
                modal = ""
            json['template'] = row + modal

        event_jsons.append(json)

    return jsonify(events=event_jsons, total=total)
Ejemplo n.º 10
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)
Ejemplo n.º 11
0
def get_request_events():
    """
    Returns a set of events (id, type, and template),
    ordered by date descending, and starting from a specific index.

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

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

    events = Events.query.filter(
        Events.request_id == current_request.id,
        Events.type.in_(event_type.FOR_REQUEST_HISTORY)).order_by(
            desc(Events.timestamp)).all()[start:start + EVENTS_INCREMENT]

    template_path = 'request/events/'
    event_jsons = []

    types_with_modal = [
        event_type.FILE_EDITED,
        event_type.INSTRUCTIONS_EDITED,
        event_type.LINK_EDITED,
        event_type.NOTE_EDITED,
        event_type.REQ_AGENCY_REQ_SUM_EDITED,
        event_type.REQ_AGENCY_REQ_SUM_PRIVACY_EDITED,
        event_type.REQ_TITLE_EDITED,
        event_type.REQ_TITLE_PRIVACY_EDITED,
        event_type.REQUESTER_INFO_EDITED,
        event_type.USER_PERM_CHANGED,
    ]

    for i, event in enumerate(events):
        json = {'id': event.id, 'type': event.type}

        if eval_request_bool(flask_request.args.get('with_template')):
            has_modal = event.type in types_with_modal
            row = render_template(template_path + 'row.html',
                                  event=event,
                                  row_num=start + i + 1,
                                  has_modal=has_modal)
            if has_modal:
                if event.type == event_type.USER_PERM_CHANGED:
                    previous_permissions = set([
                        p.label for p in get_permissions_as_list(
                            event.previous_value['permissions'])
                    ])
                    new_permissions = set([
                        p.label for p in get_permissions_as_list(
                            event.new_value['permissions'])
                    ])
                    modal = render_template(
                        template_path + 'modal.html',
                        event=event,
                        modal_body=render_template(
                            "{}modal_body/{}.html".format(
                                template_path, event.type.lower()),
                            event=event,
                            permissions_granted=list(new_permissions -
                                                     previous_permissions),
                            permissions_revoked=list(previous_permissions -
                                                     new_permissions),
                        ),
                    )
                else:
                    modal = render_template(
                        template_path + 'modal.html',
                        modal_body=render_template(
                            "{}modal_body/{}.html".format(
                                template_path, event.type.lower()),
                            event=event),
                        event=event,
                    )
            else:
                modal = ""
            json['template'] = row + modal

        event_jsons.append(json)

    return jsonify(events=event_jsons)
Ejemplo n.º 12
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
Ejemplo n.º 13
0
def requests_doc(doc_type):
    """
    Converts and sends the a search result-set as a
    file of the specified document type.
    - Filtering on set size is ignored; all results are returned.
    - Currently only supports CSVs.

    Document name format: "FOIL_requests_results_<timestamp:MM_DD_YYYY_at_HH_mm_pp>"

    Request parameters are identical to those of /search/requests.

    :param doc_type: document type ('csv' only)
    """
    if current_user.is_agency and doc_type.lower() == 'csv':
        try:
            agency_ein = request.args.get('agency_ein', '')
        except ValueError:
            agency_ein = None

        tz_name = request.args.get('tz_name')

        start = 0
        buffer = StringIO()  # csvwriter cannot accept BytesIO
        writer = csv.writer(buffer)
        writer.writerow([
            "FOIL ID", "Agency", "Title", "Description", "Agency Description",
            "Current Status", "Date Created", "Date Received", "Date Due",
            "Date Closed", "Requester Name", "Requester Email",
            "Requester Title", "Requester Organization",
            "Requester Phone Number", "Requester Fax Number",
            "Requester Address 1", "Requester Address 2", "Requester City",
            "Requester State", "Requester Zipcode", "Assigned User Emails"
        ])
        while True:
            results = search_requests(
                request.args.get('query'),
                eval_request_bool(request.args.get('foil_id')),
                eval_request_bool(request.args.get('title')),
                eval_request_bool(request.args.get('agency_request_summary')),
                eval_request_bool(request.args.get('description')),
                eval_request_bool(request.args.get('requester_name')),
                request.args.get('date_rec_from'),
                request.args.get('date_rec_to'),
                request.args.get('date_due_from'),
                request.args.get('date_due_to'),
                request.args.get('date_closed_from'),
                request.args.get('date_closed_to'),
                agency_ein,
                eval_request_bool(request.args.get('open')),
                eval_request_bool(request.args.get('closed')),
                eval_request_bool(request.args.get('in_progress')),
                eval_request_bool(request.args.get('due_soon')),
                eval_request_bool(request.args.get('overdue')),
                ALL_RESULTS_CHUNKSIZE,
                start,
                request.args.get('sort_date_submitted'),
                request.args.get('sort_date_due'),
                request.args.get('sort_title'),
                tz_name,
                for_csv=True)
            total = results["hits"]["total"]
            if total != 0:
                convert_dates(results, tz_name=tz_name)
                for result in results["hits"]["hits"]:
                    r = Requests.query.filter_by(id=result["_id"]).one()
                    mailing_address = (r.requester.mailing_address
                                       if r.requester.mailing_address
                                       is not None else {})
                    date_closed = result['_source'].get('date_closed', '')
                    date_closed = date_closed if str(date_closed) != str(
                        list()) else ''
                    writer.writerow([
                        result["_id"], result["_source"]["agency_name"],
                        result["_source"]["title"],
                        result["_source"]["description"],
                        result["_source"]["agency_request_summary"], r.status,
                        result["_source"]["date_created"],
                        result["_source"]["date_submitted"],
                        result["_source"]["date_due"], date_closed,
                        result["_source"]["requester_name"], r.requester.email,
                        r.requester.title, r.requester.organization,
                        r.requester.phone_number, r.requester.fax_number,
                        mailing_address.get('address_one'),
                        mailing_address.get('address_two'),
                        mailing_address.get('city'),
                        mailing_address.get('state'),
                        mailing_address.get('zip'),
                        ", ".join(u.email for u in r.agency_users)
                    ])
            start += ALL_RESULTS_CHUNKSIZE
            if start > total:
                break
        if total != 0:
            dt = datetime.utcnow()
            timestamp = utc_to_local(dt,
                                     tz_name) if tz_name is not None else dt
            return send_file(
                BytesIO(buffer.getvalue().encode('UTF-8')),  # convert to bytes
                attachment_filename="FOIL_requests_results_{}.csv".format(
                    timestamp.strftime("%m_%d_%Y_at_%I_%M_%p")),
                as_attachment=True)
    return '', 400
Ejemplo n.º 14
0
def patch(user_id):
    """
    Request Parameters:
    - title
    - organization
    - email
    - phone_number
    - fax_number
    - mailing_address
    - is_super
    - is_agency_active
    - is_agency_admin
    (Mailing Address)
    - zip
    - city
    - state
    - address_one
    - address_two

    Restrictions:
    - Anonymous Users
        - cannot access this endpoint
    - Agency Administrators
        - cannot change their agency status
        - can only update the agency status of users within their agency
        - cannot change any super user status
    - Super Users
        - cannot change their super user status
    - Agency Users
        - cannot change any user except for themselves or
          *anonymous* requesters for requests they are assigned to
        - cannot change super user or agency status
    - Public Users
        - can only update themselves
        - cannot change super user or agency status

    """

    # Anonymous users cannot access endpoint
    if current_user.is_anonymous:
        return jsonify({'error': 'Anonymous users cannot access this endpoint'}), 403

    # Public users cannot access endpoint
    if current_user.is_public:
        return jsonify({'error': 'Public users cannot access this endpoint'}), 403

    # Unauthenticated users cannot access endpoint
    if not current_user.is_authenticated:
        return jsonify({'error': 'User must be authenticated to access endpoint'}), 403

    # Retrieve the user
    user_ = Users.query.filter_by(guid=user_id).one_or_none()

    # If the user does not exist, return 404 - Not Found
    if not user_:
        return jsonify({'error': 'Specified user does not exist.'}), 404

    # Gather Form details
    is_agency_admin = eval_request_bool(request.form.get('is_agency_admin')) if request.form.get('is_agency_admin',
                                                                                                 None) else None
    is_agency_active = eval_request_bool(request.form.get('is_agency_active')) if request.form.get('is_agency_active',
                                                                                                   None) else None
    is_super = eval_request_bool(request.form.get('is_super')) if request.form.get('is_super', None) else None

    agency_ein = request.form.get('agency_ein', None)

    # Checks that apply if user is changing their own profile
    changing_self = current_user == user_

    # Agency User Restrictions (applies to Admins and Regular Users)
    if user_.is_agency:
        # Endpoint can only be used for a specific agency
        if not agency_ein:
            return jsonify({'error': 'agency_ein must be provided to modify an agency user'}), 400

        # Agency must exist and be active to modify users
        agency = Agencies.query.filter_by(ein=agency_ein).one_or_none()
        if not agency and agency.is_active:
            return jsonify({'error': 'Agency must exist in the database and be active'}), 400

        if not current_user.is_super:
            # Current user must belong to agency specified by agency_ein
            current_user_is_agency_admin = current_user.is_agency_admin(agency.ein)

            if not current_user_is_agency_admin:
                return jsonify({'error': 'Current user must belong to agency specified by agency_ein'}), 400

            user_in_agency = AgencyUsers.query.filter(AgencyUsers.user_guid == user_.guid, AgencyUsers.agency_ein == agency_ein).one_or_none()

            if user_in_agency is None:
                return jsonify({'error': 'User to be modified must belong to agency specified by agency_ein'}), 400

        # Non-Agency Admins cannot access endpoint to modify other agency_users
        if not current_user.is_super and not current_user.is_agency_admin(agency_ein):
            return jsonify({'error': 'User must be agency admin to modify users'}), 403

        # Cannot modify super status when changing agency active or agency admin status
        if (is_agency_admin or is_agency_active) and is_super:
            return jsonify({
                'error': 'Cannot modify super status when changing agency active or agency admin status'}), 400

        if changing_self:
            # Super users cannot change their own is_super value
            if current_user.is_super and is_super:
                return jsonify({'error': 'Super users cannot change their own `super` status'}), 400

            if is_agency_admin:
                return jsonify({'error': 'Agency Administrators cannot change their administrator permissions'}), 400

    elif user_.is_public:
        if current_user != user_:
            return jsonify({'error': 'Public user attributes cannot be modified by agency users.'}), 400

    elif user_.is_anonymous_requester:
        ur = current_user.user_requests.filter_by(request_id=user_.anonymous_request.id).one_or_none()
        if not ur:
            return jsonify({
                'error': 'Agency users can only modify anonymous requesters for requests where they are assigned.'}), 403

        if not ur.has_permission(permission.EDIT_REQUESTER_INFO):
            return jsonify({'error': 'Current user does not have EDIT_REQUESTER_INFO permission'}), 403

    # Gather User Fields

    user_editable_fields = user_attrs.UserEditableFieldsDict(
        email=request.form.get('email'),
        phone_number=request.form.get('phone'),
        fax_number=request.form.get('fax'),
        title=request.form.get('title'),
        organization=request.form.get('organization'),
        address_one=request.form.get('address_one'),
        address_two=request.form.get('address_two'),
        zip=request.form.get('zipcode'),
        city=request.form.get('city'),
    )
    status_field_val = user_attrs.UserStatusDict(
        is_agency_admin=request.form.get('is_agency_admin'),
        is_agency_active=request.form.get('is_agency_active'),
        is_super=request.form.get('is_super')
    )
    if changing_self:
        if not user_editable_fields.is_valid:
            return jsonify({"error": "Missing contact information."}), 400
    else:
        if user_.is_agency and not status_field_val.is_valid:
            return jsonify({"error": "User status values invalid"}), 400

    # Status Values for Events
    old_status = {}
    new_status = {}

    # User Attributes for Events
    old_user_attrs = {'_mailing_address': {}}
    new_user_attrs = {'_mailing_address': {}}

    for key, value in status_field_val.items():
        if value is not None:
            if key == 'is_agency_admin':
                cur_val = user_.is_agency_admin(agency_ein)
            elif key == 'is_agency_active':
                cur_val = user_.is_agency_active(agency_ein)
            else:
                cur_val = getattr(user_, key)
            new_val = eval_request_bool(status_field_val[key])
            if cur_val != new_val:
                old_status[key] = cur_val
                new_status[key] = new_val

    for key, value in user_editable_fields.items():
        # Address is a dictionary and needs to be handled separately
        if key == 'address':
            for address_key, address_value in value.items():
                cur_val = (user_.mailing_address.get(address_key)
                           if user_.mailing_address else None)
                new_val = address_value
                if cur_val != new_val:
                    old_user_attrs['_mailing_address'][address_key] = cur_val
                    new_user_attrs['_mailing_address'][address_key] = new_val
            continue
        if value is not None:
            cur_val = getattr(user_, key)
            new_val = user_editable_fields[key]
            if cur_val != new_val:
                old_user_attrs[key] = cur_val
                new_user_attrs[key] = new_val

    # Update the Users object if new_user_attrs is not None (empty dict)
    if new_user_attrs and new_user_attrs.get('_mailing_address') and old_user_attrs:
        update_object(
            new_user_attrs,
            Users,
            user_id
        )
        # GUID is added to the 'new' value in events to identify the user that was changed
        new_user_attrs['user_guid'] = user_.guid

        # create event(s)
        event_kwargs = {
            'request_id': user_.anonymous_request.id if user_.is_anonymous_requester else None,
            'response_id': None,
            'user_guid': current_user.guid,
            'timestamp': datetime.utcnow()
        }

        create_object(Events(
            type_=(event_type.REQUESTER_INFO_EDITED
                   if user_.is_anonymous_requester
                   else event_type.USER_INFO_EDITED),
            previous_value=old_user_attrs,
            new_value=new_user_attrs,
            **event_kwargs
        ))

    if new_status:
        redis_key = "{current_user_guid}-{update_user_guid}-{agency_ein}-{timestamp}".format(
            current_user_guid=current_user.guid, update_user_guid=user_.guid, agency_ein=agency_ein,
            timestamp=datetime.now())
        old_status['user_guid'] = user_.guid
        new_status['user_guid'] = user_.guid

        old_status['agency_ein'] = agency_ein
        new_status['agency_ein'] = agency_ein

        # Update agency active status and create associated event. Occurs first because a user can be
        # activated / deactivated with admin status set to True.
        if is_agency_active is not None:
            update_object(
                new_status,
                AgencyUsers,
                (user_.guid, agency_ein)
            )
            event_kwargs = {
                'request_id': user_.anonymous_request.id if user_.is_anonymous_requester else None,
                'response_id': None,
                'user_guid': current_user.guid,
                'timestamp': datetime.utcnow()
            }
            if is_agency_active:
                create_object(Events(
                    type_=event_type.AGENCY_USER_ACTIVATED,
                    previous_value=old_status,
                    new_value=new_status,
                    **event_kwargs
                ))
                if is_agency_admin is not None and is_agency_admin:
                    make_user_admin.apply_async(args=(user_.guid, current_user.guid, agency_ein), task_id=redis_key)
                    return jsonify({'status': 'success', 'message': 'Update task has been scheduled.'}), 200
            else:
                create_object(Events(
                    type_=event_type.AGENCY_USER_DEACTIVATED,
                    previous_value=old_status,
                    new_value=new_status,
                    **event_kwargs
                ))
                remove_user_permissions.apply_async(
                    args=(user_.guid, current_user.guid, agency_ein, event_type.AGENCY_USER_DEACTIVATED), task_id=redis_key)
                return jsonify({'status': 'success', 'message': 'Update task has been scheduled.'}), 200
            return jsonify({'status': 'success', 'message': 'Agency user successfully updated'}), 200

        # Update agency admin status and create associated event.
        elif is_agency_admin is not None:
            new_status['agency_ein'] = agency_ein
            update_object(
                new_status,
                AgencyUsers,
                (user_.guid, agency_ein)
            )
            event_kwargs = {
                'request_id': user_.anonymous_request.id if user_.is_anonymous_requester else None,
                'response_id': None,
                'user_guid': current_user.guid,
                'timestamp': datetime.utcnow()
            }
            if is_agency_admin:
                create_object(Events(
                    type_=event_type.USER_MADE_AGENCY_ADMIN,
                    previous_value=old_status,
                    new_value=new_status,
                    **event_kwargs
                ))
                make_user_admin.apply_async(args=(user_.guid, current_user.guid, agency_ein), task_id=redis_key)
                return jsonify({'status': 'success', 'message': 'Update task has been scheduled.'}), 200
            else:
                create_object(Events(
                    type_=event_type.USER_MADE_AGENCY_USER,
                    previous_value=old_status,
                    new_value=new_status,
                    **event_kwargs
                ))
                remove_user_permissions.apply_async(
                    args=(user_.guid, current_user.guid, agency_ein, event_type.USER_MADE_AGENCY_USER), task_id=redis_key)
                return jsonify({'status': 'success', 'message': 'Update task has been scheduled.'}), 200

        # Update user super status and create associated event.
        elif is_super is not None:
            new_status['agency_ein'] = agency_ein
            update_object(
                new_status,
                AgencyUsers,
                (user_.guid, agency_ein)
            )
            event_kwargs = {
                'request_id': user_.anonymous_request.id if user_.is_anonymous_requester else None,
                'response_id': None,
                'user_guid': current_user.guid,
                'timestamp': datetime.utcnow()
            }
            if is_super:
                create_object(Events(
                    type_=event_type.USER_MADE_SUPER_USER,
                    previous_value=old_status,
                    new_value=new_status,
                    **event_kwargs
                ))
            else:
                create_object(Events(
                    type_=event_type.USER_REMOVED_FROM_SUPER,
                    previous_value=old_status,
                    new_value=new_status,
                    **event_kwargs
                ))
            return jsonify({'status': 'success', 'message': 'Agency user successfully updated'}), 200
    # Always returns 200 so that we can use the data from the response in the client side javascript
    return jsonify({'status': 'Not Modified', 'message': 'No changes detected'}), 200
Ejemplo n.º 15
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:
                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
Ejemplo n.º 16
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
Ejemplo n.º 17
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:
            current_app.logger.exception(
                "Error on DELETE /upload/: {}".format(e))
            response = {"error": "Failed to delete '{}'".format(filename)}

    return jsonify(response), 200
Ejemplo n.º 18
0
def update_agency_active_status(agency_ein, is_active):
    """
    Update the active status of an agency.
    :param agency_ein: String identifier for agency (4 characters)
    :param is_active: Boolean value for agency active status (True = Active)
    :return: Boolean value (True if successfully changed active status)
    """
    agency = Agencies.query.filter_by(ein=agency_ein).first()
    is_valid_agency = agency is not None
    activate_agency = eval_request_bool(is_active)

    if is_active is not None and is_valid_agency:
        update_object({'is_active': activate_agency}, Agencies, agency_ein)
        if activate_agency:
            create_object(
                Events(request_id=None,
                       user_guid=current_user.guid,
                       type_=AGENCY_ACTIVATED,
                       previous_value={
                           "ein": agency_ein,
                           "is_active": "False"
                       },
                       new_value={
                           "ein": agency_ein,
                           "is_active": "True"
                       },
                       timestamp=datetime.utcnow()))
            # create request documents
            for request in agency.requests:
                request.es_create()

            return True
        else:
            create_object(
                Events(request_id=None,
                       user_guid=current_user.guid,
                       type_=AGENCY_DEACTIVATED,
                       previous_value={
                           "ein": agency_ein,
                           "is_active": "True"
                       },
                       new_value={
                           "ein": agency_ein,
                           "is_active": "False"
                       },
                       timestamp=datetime.utcnow()))
            # remove requests from index
            for request in agency.requests:
                request.es_delete()
            # deactivate agency users
            for user in agency.active_users:
                update_object(
                    {
                        "is_agency_active": "False",
                        "is_agency_admin": "False"
                    }, AgencyUsers, (user.guid, agency_ein))
                create_object(
                    Events(request_id=None,
                           user_guid=current_user.guid,
                           type_=AGENCY_USER_DEACTIVATED,
                           previous_value={
                               "user_guid": user.guid,
                               "ein": agency_ein,
                               "is_active": "True"
                           },
                           new_value={
                               "user_guid": user.guid,
                               "ein": agency_ein,
                               "is_active": "False"
                           },
                           timestamp=datetime.utcnow()))

            return True
    return False
Ejemplo n.º 19
0
def patch(user_id):
    """
    Request Parameters:
    - title
    - organization
    - email
    - phone_number
    - fax_number
    - mailing_address
    - is_super
    - is_agency_active
    - is_agency_admin
    (Mailing Address)
    - zip
    - city
    - state
    - address_one
    - address_two

    Restrictions:
    - Anonymous Users
        - cannot access this endpoint
    - Agency Administrators
        - cannot change their agency status
        - can only update the agency status of users within their agency
        - cannot change any super user status
    - Super Users
        - cannot change their super user status
    - Agency Users
        - cannot change any user except for themselves or
          *anonymous* requesters for requests they are assigned to
        - cannot change super user or agency status
    - Public Users
        - can only update themselves
        - cannot change super user or agency status

    """
    if not current_user.is_anonymous:
        # attempt to parse user_id and find user
        try:
            guid, auth_type = user_id.split(USER_ID_DELIMITER)
            user_ = Users.query.filter_by(guid=guid,
                                          auth_user_type=auth_type).one()
        except (ValueError, NoResultFound, MultipleResultsFound):
            return jsonify({}), 404

        agency_ein = request.form.get('agency_ein', None)
        if agency_ein is None and not (auth_type
                                       == user_type_auth.ANONYMOUS_USER
                                       or 'is_super' in request.form):
            return jsonify({}), 404

        updating_self = current_user == user_
        current_user_is_agency_user = (
            current_user.is_agency and not current_user.is_super
            and not current_user.is_agency_admin(agency_ein)
            and current_user.is_agency_active(agency_ein))
        current_user_is_agency_admin = (
            current_user.is_agency and not current_user.is_super
            and current_user.is_agency_admin(agency_ein)
            and current_user.is_agency_active(agency_ein))
        same_agency = agency_ein in [
            agency.ein for agency in current_user.agencies.all()
        ]
        associated_anonymous_requester = (
            user_.is_anonymous_requester
            and current_user.user_requests.filter_by(
                request_id=user_.anonymous_request.id).first() is None)

        is_agency_admin = request.form.get('is_agency_admin')
        is_agency_active = request.form.get('is_agency_active')
        is_super = request.form.get('is_super')

        changing_status = any((is_agency_active, is_agency_admin, is_super))

        rform_copy = dict(request.form)
        try:
            rform_copy.pop('is_agency_admin')
            rform_copy.pop('is_agency_active')
            rform_copy.pop('agency_ein')
            changing_more_than_agency_status = len(rform_copy) != 0
        except KeyError:
            changing_more_than_agency_status = False

        # VALIDATE
        if ((
                updating_self and
            (
                # super user attempting to change their own super status
                (current_user.is_super and is_super is not None) or
                # agency admin or public user attempting to change their own agency/super status
                (changing_status and
                 (current_user_is_agency_admin or current_user.is_public)))) or
            (not updating_self and (
                # public user attempting to change another user
                current_user.is_public or
                # agency user attempting to change a agency/super status
                (current_user_is_agency_user and changing_status) or
                # agency user attempting to change a user that is not an anonymous requester
                # for a request they are assigned to
                (current_user_is_agency_user and
                 (not user_.is_anonymous_requester
                  or not associated_anonymous_requester)) or
                # agency admin attempting to change another user that is not in the same agency or
                # attempting to change more than just the agency status of a user
                (current_user_is_agency_admin
                 and not (associated_anonymous_requester
                          or user_.is_anonymous_requester) and
                 (not same_agency or changing_more_than_agency_status)) or
                # agency admin attempting to change an anonymous requester for a request
                # they are not assigned to
                (current_user_is_agency_admin
                 and associated_anonymous_requester)))):
            return jsonify({}), 403

        # UPDATE
        user_fields = [
            'email', 'phone_number', 'fax_number', 'title', 'organization'
        ]
        status_fields = ['is_agency_admin', 'is_agency_active', 'is_super']
        address_fields = ['zip', 'city', 'state', 'address_one', 'address_two']

        user_field_val = {
            'email': request.form.get('email'),
            'phone_number': request.form.get('phone'),
            'fax_number': request.form.get('fax'),
            'title': request.form.get('title'),
            'organization': request.form.get('organization'),
        }
        status_field_val = {
            'is_agency_admin': request.form.get('is_agency_admin'),
            'is_agency_active': request.form.get('is_agency_active'),
            'is_super': request.form.get('is_super')
        }
        address_field_val = {
            'address_one': request.form.get('address_one'),
            'address_two': request.form.get('address_two'),
            'zip': request.form.get('zipcode'),
            'city': request.form.get('city'),
            'state': request.form.get('state')
        }

        # check if missing contact information
        if (user_field_val['email'] == ''
                and user_field_val['phone_number'] == ''
                and user_field_val['fax_number'] == '' and
            (address_field_val['city'] == '' or address_field_val['zip'] == ''
             or address_field_val['state'] == ''
             or address_field_val['address_one'] == '')):
            return jsonify({"error": "Missing contact information."}), 400

        old = {}
        old_address = {}
        new = {}
        new_address = {}

        for field in status_fields:
            if status_field_val[field] is not None:
                if field == 'is_agency_admin':
                    cur_val = user_.is_agency_admin(agency_ein)
                elif field == 'is_agency_active':
                    cur_val = user_.is_agency_active(agency_ein)
                else:
                    cur_val = getattr(user_, field)
                new_val = eval_request_bool(status_field_val[field])
                if cur_val != new_val:
                    old[field] = cur_val
                    new[field] = new_val

        for field in user_fields:
            val = user_field_val[field]
            if val is not None:
                if val == '':
                    user_field_val[
                        field] = None  # null in db, not empty string
                cur_val = getattr(user_, field)
                new_val = user_field_val[field]
                if cur_val != new_val:
                    old[field] = cur_val
                    new[field] = new_val

        for field in address_fields:
            val = address_field_val[field]
            if val is not None:
                if val == '':
                    address_field_val[field] = None
                cur_val = (user_.mailing_address.get(field)
                           if user_.mailing_address else None)
                new_val = address_field_val[field]
                if cur_val != new_val:
                    old_address[field] = cur_val
                    new_address[field] = new_val

        if new or new_address:
            # in spite of not changing, the guid and auth type of
            # the user being updated is added to Events.new_value
            # in order to identify this user
            new['user_guid'] = user_.guid
            new['auth_user_type'] = user_.auth_user_type

            if new_address:
                new['mailing_address'] = new_address
            if old_address:
                old['mailing_address'] = old_address

            if ('is_agency_admin' in new) or ('is_agency_active' in new):
                new['agency_ein'] = agency_ein
                update_object(new, AgencyUsers, (guid, auth_type, agency_ein))
            else:
                update_object(new, Users, (guid, auth_type))

            # create event(s)
            event_kwargs = {
                'request_id':
                user_.anonymous_request.id
                if user_.is_anonymous_requester else None,
                'response_id':
                None,
                'user_guid':
                current_user.guid,
                'auth_user_type':
                current_user.auth_user_type,
                'timestamp':
                datetime.utcnow()
            }

            if changing_status:
                new_statuses = {}
                old_statuses = {}
                for field in status_fields:
                    if new.get(field) is not None:
                        new_statuses[field] = new.pop(field)
                        old_statuses[field] = old.pop(field)

                # TODO: a better way to store user identifiers (than in the value columns)
                new_statuses['user_guid'] = user_.guid
                new_statuses['auth_user_type'] = user_.auth_user_type
                new_statuses['agency_ein'] = agency_ein

                is_agency_active = new_statuses.get('is_agency_active')
                is_agency_admin = new_statuses.get('is_agency_admin')

                if is_agency_active is not None and not is_agency_active:
                    # remove ALL UserRequests
                    for user_request in user_.user_requests.all():
                        create_user_request_event(event_type.USER_REMOVED,
                                                  user_request)
                        delete_object(user_request)
                elif is_agency_admin is not None:

                    def set_permissions_and_create_event(user_req, perms):
                        """
                        Set permissions for a user request and create a
                        'user_permissions_changed' Event.

                        :param user_req: user request
                        :param perms: permissions to set for user request
                        """
                        old_permissions = user_req.permissions
                        user_request.set_permissions(perms)
                        create_user_request_event(event_type.USER_PERM_CHANGED,
                                                  user_req, old_permissions)

                    if is_agency_admin:
                        permissions = Roles.query.filter_by(
                            name=role_name.AGENCY_ADMIN).one().permissions
                        # create UserRequests for ALL existing requests under user's agency where user is not assigned
                        # for where the user *is* assigned, only change the permissions
                        for req in user_.agencies.filter_by(
                                ein=agency_ein).one().requests:
                            user_request = UserRequests.query.filter_by(
                                request_id=req.id,
                                user_guid=user_.guid,
                                auth_user_type=user_.auth_user_type).first()
                            if user_request is None:
                                user_request = UserRequests(
                                    user_guid=user_.guid,
                                    auth_user_type=user_.auth_user_type,
                                    request_id=req.id,
                                    request_user_type=user_type_request.AGENCY,
                                    permissions=permissions)
                                create_object(user_request)
                                create_user_request_event(
                                    event_type.USER_ADDED, user_request)
                            else:
                                set_permissions_and_create_event(
                                    user_request, permissions)

                    else:
                        # update ALL UserRequests (strip user of permissions)
                        for user_request in user_.user_requests.all():
                            set_permissions_and_create_event(
                                user_request, permission.NONE)

                # TODO: single email detailing user changes?

                create_object(
                    Events(type_=event_type.USER_STATUS_CHANGED,
                           previous_value=old_statuses,
                           new_value=new_statuses,
                           **event_kwargs))

            if old:  # something besides status changed ('new' holds user guid and auth type)
                create_object(
                    Events(type_=(event_type.REQUESTER_INFO_EDITED
                                  if user_.is_anonymous_requester else
                                  event_type.USER_INFO_EDITED),
                           previous_value=old,
                           new_value=new,
                           **event_kwargs))
            return jsonify({}), 200
        else:
            return jsonify({"message": "No changes detected."}), 200

    return jsonify({}), 403
Ejemplo n.º 20
0
def requests():
    """
    For request parameters, see app.search.utils.search_requests

    All Users can search by:
    - FOIL ID

    Anonymous Users can search by:
    - Title (public only)
    - Agency Description (public only)

    Public Users can search by:
    - Title (public only OR public and private if user is requester)
    - Agency Description (public only)
    - Description (if user is requester)

    Agency Users can search by:
    - Title
    - Agency Description
    - Description
    - Requester Name

    All Users can filter by:
    - Status, Open (anything not Closed if not agency user)
    - Status, Closed
    - Date Submitted
    - Agency

    Only Agency Users can filter by:
    - Status, In Progress
    - Status, Due Soon
    - Status, Overdue
    - Date Due

    """
    try:
        agency_ein = request.args.get('agency_ein', '')
    except ValueError:
        agency_ein = None

    try:
        size = int(request.args.get('size', DEFAULT_HITS_SIZE))
    except ValueError:
        size = DEFAULT_HITS_SIZE

    try:
        start = int(request.args.get('start'), 0)
    except ValueError:
        start = 0

    query = request.args.get('query')

    # Determine if searching for FOIL ID
    foil_id = eval_request_bool(request.args.get('foil_id')) or re.match(
        r'^(FOIL-|foil-|)\d{4}-\d{3}-\d{5}$', query)

    results = search_requests(
        query, foil_id, eval_request_bool(request.args.get('title')),
        eval_request_bool(request.args.get('agency_request_summary')),
        eval_request_bool(request.args.get('description'))
        if not current_user.is_anonymous else False,
        eval_request_bool(request.args.get('requester_name')) if
        current_user.is_agency else False, request.args.get('date_rec_from'),
        request.args.get('date_rec_to'), request.args.get('date_due_from'),
        request.args.get('date_due_to'), request.args.get('date_closed_from'),
        request.args.get('date_closed_to'), agency_ein,
        eval_request_bool(request.args.get('open')),
        eval_request_bool(request.args.get('closed')),
        eval_request_bool(request.args.get('in_progress'))
        if current_user.is_agency else False,
        eval_request_bool(request.args.get('due_soon'))
        if current_user.is_agency else False,
        eval_request_bool(request.args.get('overdue'))
        if current_user.is_agency else False, size, start,
        request.args.get('sort_date_submitted'),
        request.args.get('sort_date_due'), request.args.get('sort_title'),
        request.args.get('tz_name')
        # eval_request_bool(request.args.get('by_phrase')),
        # eval_request_bool(request.args.get('highlight')),
    )

    # format results
    total = results["hits"]["total"]
    formatted_results = None
    if total != 0:
        convert_dates(results)
        formatted_results = render_template("request/result_row.html",
                                            requests=results["hits"]["hits"])
        # query=query)  # only for testing
    return jsonify({
        "count": len(results["hits"]["hits"]),
        "total": total,
        "results": formatted_results
    }), 200
Ejemplo n.º 21
0
def requests():
    """
    For request parameters, see app.search.utils.search_requests

    All Users can search by:
    - FOIL ID

    Anonymous Users can search by:
    - Title (public only)
    - Agency Request Summary (public only)

    Public Users can search by:
    - Title (public only OR public and private if user is requester)
    - Agency Request Summary (public only)
    - Description (if user is requester)

    Agency Users can search by:
    - Title
    - Agency Request Summary
    - Description
    - Requester Name

    All Users can filter by:
    - Status, Open (anything not Closed if not agency user)
    - Status, Closed
    - Date Submitted
    - Agency

    Only Agency Users can filter by:
    - Status, In Progress
    - Status, Due Soon
    - Status, Overdue
    - Date Due

    """
    try:
        agency_ein = request.args.get("agency_ein", "")
    except ValueError:
        sentry.captureException()
        agency_ein = None

    try:
        size = int(request.args.get("size", DEFAULT_HITS_SIZE))
    except ValueError:
        sentry.captureException()
        size = DEFAULT_HITS_SIZE

    try:
        start = int(request.args.get("start"), 0)
    except ValueError:
        sentry.captureException()
        start = 0

    query = request.args.get("query")

    # Determine if searching for FOIL ID
    foil_id = eval_request_bool(request.args.get("foil_id")) or re.match(
        r"^(FOIL-|foil-|)\d{4}-\d{3}-\d{5}$", query
    )

    results = search_requests(
        query=query,
        foil_id=foil_id,
        title=eval_request_bool(request.args.get("title")),
        agency_request_summary=eval_request_bool(
            request.args.get("agency_request_summary")
        ),
        description=eval_request_bool(request.args.get("description"))
        if not current_user.is_anonymous
        else False,
        requester_name=eval_request_bool(request.args.get("requester_name"))
        if current_user.is_agency
        else False,
        date_rec_from=request.args.get("date_rec_from"),
        date_rec_to=request.args.get("date_rec_to"),
        date_due_from=request.args.get("date_due_from"),
        date_due_to=request.args.get("date_due_to"),
        date_closed_from=request.args.get("date_closed_from"),
        date_closed_to=request.args.get("date_closed_to"),
        agency_ein=agency_ein,
        agency_user_guid=request.args.get("agency_user"),
        request_type=request.args.get("request_type"),
        open_=eval_request_bool(request.args.get("open")),
        closed=eval_request_bool(request.args.get("closed")),
        in_progress=eval_request_bool(request.args.get("in_progress"))
        if current_user.is_agency
        else False,
        due_soon=eval_request_bool(request.args.get("due_soon"))
        if current_user.is_agency
        else False,
        overdue=eval_request_bool(request.args.get("overdue"))
        if current_user.is_agency
        else False,
        size=size,
        start=start,
        sort_date_received=request.args.get("sort_date_submitted"),
        sort_date_due=request.args.get("sort_date_due"),
        sort_title=request.args.get("sort_title"),
        tz_name=request.args.get("tz_name", current_app.config["APP_TIMEZONE"]),
    )

    # format results
    total = results["hits"]["total"]
    formatted_results = None
    if total != 0:
        convert_dates(results)
        formatted_results = render_template(
            "request/result_row.html",
            requests=results["hits"]["hits"],
            today=datetime.utcnow(),
        )
        # query=query)  # only for testing
    return (
        jsonify(
            {
                "count": len(results["hits"]["hits"]),
                "total": total,
                "results": formatted_results,
            }
        ),
        200,
    )
Ejemplo n.º 22
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()[start:start +
                                                RESPONSES_INCREMENT]
    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()[start:start +
                                                RESPONSES_INCREMENT]

    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()[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)
Ejemplo n.º 23
0
def requests_doc(doc_type):
    """
    Converts and sends the a search result-set as a
    file of the specified document type.
    - Filtering on set size is ignored; all results are returned.
    - Currently only supports CSVs.
    - CSV only includes requests belonging to that user's agency

    Document name format: "FOIL_requests_results_<timestamp:MM_DD_YYYY_at_HH_mm_pp>"

    Request parameters are identical to those of /search/requests.

    :param doc_type: document type ('csv' only)
    """
    if current_user.is_agency and doc_type.lower() == 'csv':
        try:
            agency_ein = request.args.get('agency_ein', '')
        except ValueError:
            sentry.captureException()
            agency_ein = None

        tz_name = request.args.get('tz_name', current_app.config['APP_TIMEZONE'])

        start = 0
        buffer = StringIO()  # csvwriter cannot accept BytesIO
        writer = csv.writer(buffer)
        writer.writerow(["FOIL ID",
                         "Agency",
                         "Title",
                         "Description",
                         "Agency Request Summary",
                         "Current Status",
                         "Date Created",
                         "Date Received",
                         "Date Due",
                         "Date Closed",
                         "Requester Name",
                         "Requester Email",
                         "Requester Title",
                         "Requester Organization",
                         "Requester Phone Number",
                         "Requester Fax Number",
                         "Requester Address 1",
                         "Requester Address 2",
                         "Requester City",
                         "Requester State",
                         "Requester Zipcode",
                         "Assigned User Emails"])
        results = search_requests(
            query=request.args.get('query'),
            foil_id=eval_request_bool(request.args.get('foil_id')),
            title=eval_request_bool(request.args.get('title')),
            agency_request_summary=eval_request_bool(request.args.get('agency_request_summary')),
            description=eval_request_bool(request.args.get('description')) if not current_user.is_anonymous else False,
            requester_name=eval_request_bool(request.args.get('requester_name')) if current_user.is_agency else False,
            date_rec_from=request.args.get('date_rec_from'),
            date_rec_to=request.args.get('date_rec_to'),
            date_due_from=request.args.get('date_due_from'),
            date_due_to=request.args.get('date_due_to'),
            date_closed_from=request.args.get('date_closed_from'),
            date_closed_to=request.args.get('date_closed_to'),
            agency_ein=agency_ein,
            agency_user_guid=request.args.get('agency_user'),
            open_=eval_request_bool(request.args.get('open')),
            closed=eval_request_bool(request.args.get('closed')),
            in_progress=eval_request_bool(request.args.get('in_progress')) if current_user.is_agency else False,
            due_soon=eval_request_bool(request.args.get('due_soon')) if current_user.is_agency else False,
            overdue=eval_request_bool(request.args.get('overdue')) if current_user.is_agency else False,
            start=start,
            sort_date_received=request.args.get('sort_date_submitted'),
            sort_date_due=request.args.get('sort_date_due'),
            sort_title=request.args.get('sort_title'),
            tz_name=request.args.get('tz_name', current_app.config['APP_TIMEZONE']),
            for_csv=True
        )
        ids = [result["_id"] for result in results]
        all_requests = Requests.query.filter(Requests.id.in_(ids)).options(
            joinedload(Requests.agency_users)).options(joinedload(Requests.requester)).options(
            joinedload(Requests.agency)).all()
        user_agencies = current_user.get_agencies
        for req in all_requests:
            if req.agency_ein in user_agencies:
                writer.writerow([
                    req.id,
                    req.agency.name,
                    req.title,
                    req.description,
                    req.agency_request_summary,
                    req.status,
                    req.date_created,
                    req.date_submitted,
                    req.due_date,
                    req.date_closed,
                    req.requester.name,
                    req.requester.email,
                    req.requester.title,
                    req.requester.organization,
                    req.requester.phone_number,
                    req.requester.fax_number,
                    req.requester.mailing_address.get('address_one'),
                    req.requester.mailing_address.get('address_two'),
                    req.requester.mailing_address.get('city'),
                    req.requester.mailing_address.get('state'),
                    req.requester.mailing_address.get('zip'),
                    ", ".join(u.email for u in req.agency_users)])
        dt = datetime.utcnow()
        timestamp = utc_to_local(dt, tz_name) if tz_name is not None else dt
        return send_file(
            BytesIO(buffer.getvalue().encode('UTF-8')),  # convert to bytes
            attachment_filename="FOIL_requests_results_{}.csv".format(
                timestamp.strftime("%m_%d_%Y_at_%I_%M_%p")),
            as_attachment=True
        )
    return '', 400
Ejemplo n.º 24
0
def requests():
    """
    For request parameters, see app.search.utils.search_requests

    All Users can search by:
    - FOIL ID

    Anonymous Users can search by:
    - Title (public only)
    - Agency Request Summary (public only)

    Public Users can search by:
    - Title (public only OR public and private if user is requester)
    - Agency Request Summary (public only)
    - Description (if user is requester)

    Agency Users can search by:
    - Title
    - Agency Request Summary
    - Description
    - Requester Name

    All Users can filter by:
    - Status, Open (anything not Closed if not agency user)
    - Status, Closed
    - Date Submitted
    - Agency

    Only Agency Users can filter by:
    - Status, In Progress
    - Status, Due Soon
    - Status, Overdue
    - Date Due

    """
    try:
        agency_ein = request.args.get('agency_ein', '')
    except ValueError:
        sentry.captureException()
        agency_ein = None

    try:
        size = int(request.args.get('size', DEFAULT_HITS_SIZE))
    except ValueError:
        sentry.captureException()
        size = DEFAULT_HITS_SIZE

    try:
        start = int(request.args.get('start'), 0)
    except ValueError:
        sentry.captureException()
        start = 0

    query = request.args.get('query')

    # Determine if searching for FOIL ID
    foil_id = eval_request_bool(request.args.get('foil_id')) or re.match(r'^(FOIL-|foil-|)\d{4}-\d{3}-\d{5}$', query)

    results = search_requests(
        query=query,
        foil_id=foil_id,
        title=eval_request_bool(request.args.get('title')),
        agency_request_summary=eval_request_bool(request.args.get('agency_request_summary')),
        description=eval_request_bool(request.args.get('description')) if not current_user.is_anonymous else False,
        requester_name=eval_request_bool(request.args.get('requester_name')) if current_user.is_agency else False,
        date_rec_from=request.args.get('date_rec_from'),
        date_rec_to=request.args.get('date_rec_to'),
        date_due_from=request.args.get('date_due_from'),
        date_due_to=request.args.get('date_due_to'),
        date_closed_from=request.args.get('date_closed_from'),
        date_closed_to=request.args.get('date_closed_to'),
        agency_ein=agency_ein,
        agency_user_guid=request.args.get('agency_user'),
        open_=eval_request_bool(request.args.get('open')),
        closed=eval_request_bool(request.args.get('closed')),
        in_progress=eval_request_bool(request.args.get('in_progress')) if current_user.is_agency else False,
        due_soon=eval_request_bool(request.args.get('due_soon')) if current_user.is_agency else False,
        overdue=eval_request_bool(request.args.get('overdue')) if current_user.is_agency else False,
        size=size,
        start=start,
        sort_date_received=request.args.get('sort_date_submitted'),
        sort_date_due=request.args.get('sort_date_due'),
        sort_title=request.args.get('sort_title'),
        tz_name=request.args.get('tz_name', current_app.config['APP_TIMEZONE'])
    )

    # format results
    total = results["hits"]["total"]
    formatted_results = None
    if total != 0:
        convert_dates(results)
        formatted_results = render_template("request/result_row.html",
                                            requests=results["hits"]["hits"])
        # query=query)  # only for testing
    return jsonify({
        "count": len(results["hits"]["hits"]),
        "total": total,
        "results": formatted_results
    }), 200
Ejemplo n.º 25
0
def patch(user_id):
    """
    Request Parameters:
    - title
    - organization
    - email
    - phone_number
    - fax_number
    - mailing_address
    - is_super
    - is_agency_active
    - is_agency_admin
    (Mailing Address)
    - zip
    - city
    - state
    - address_one
    - address_two

    Restrictions:
    - Anonymous Users
        - cannot access this endpoint
    - Agency Administrators
        - cannot change their agency status
        - can only update the agency status of users within their agency
        - cannot change any super user status
    - Super Users
        - cannot change their super user status
    - Agency Users
        - cannot change any user except for themselves or
          *anonymous* requesters for requests they are assigned to
        - cannot change super user or agency status
    - Public Users
        - can only update themselves
        - cannot change super user or agency status

    """

    # Anonymous users cannot access endpoint
    if current_user.is_anonymous:
        return jsonify(
            {'error': 'Anonymous users cannot access this endpoint'}), 403

    # Public users cannot access endpoint
    if current_user.is_public:
        return jsonify({'error':
                        'Public users cannot access this endpoint'}), 403

    # Unauthenticated users cannot access endpoint
    if not current_user.is_authenticated:
        return jsonify(
            {'error': 'User must be authenticated to access endpoint'}), 403

    # Retrieve the user
    user_ = Users.query.filter_by(guid=user_id).one_or_none()

    # If the user does not exist, return 404 - Not Found
    if not user_:
        return jsonify({'error': 'Specified user does not exist.'}), 404

    # Gather Form details
    is_agency_admin = eval_request_bool(
        request.form.get('is_agency_admin')) if request.form.get(
            'is_agency_admin', None) else None
    is_agency_active = eval_request_bool(
        request.form.get('is_agency_active')) if request.form.get(
            'is_agency_active', None) else None
    is_super = eval_request_bool(
        request.form.get('is_super')) if request.form.get('is_super',
                                                          None) else None

    agency_ein = request.form.get('agency_ein', None)

    # Checks that apply if user is changing their own profile
    changing_self = current_user == user_

    # Agency User Restrictions (applies to Admins and Regular Users)
    if user_.is_agency:
        # Endpoint can only be used for a specific agency
        if not agency_ein:
            return jsonify({
                'error':
                'agency_ein must be provided to modify an agency user'
            }), 400

        # Agency must exist and be active to modify users
        agency = Agencies.query.filter_by(ein=agency_ein).one_or_none()
        if not agency and agency.is_active:
            return jsonify(
                {'error':
                 'Agency must exist in the database and be active'}), 400

        if not current_user.is_super:
            # Current user must belong to agency specified by agency_ein
            current_user_is_agency_admin = current_user.is_agency_admin(
                agency.ein)

            if not current_user_is_agency_admin:
                return jsonify({
                    'error':
                    'Current user must belong to agency specified by agency_ein'
                }), 400

            user_in_agency = AgencyUsers.query.filter(
                AgencyUsers.user_guid == user_.guid,
                AgencyUsers.agency_ein == agency_ein).one_or_none()

            if user_in_agency is None:
                return jsonify({
                    'error':
                    'User to be modified must belong to agency specified by agency_ein'
                }), 400

        # Non-Agency Admins cannot access endpoint to modify other agency_users
        if not current_user.is_super and not current_user.is_agency_admin(
                agency_ein):
            return jsonify(
                {'error': 'User must be agency admin to modify users'}), 403

        # Cannot modify super status when changing agency active or agency admin status
        if (is_agency_admin or is_agency_active) and is_super:
            return jsonify({
                'error':
                'Cannot modify super status when changing agency active or agency admin status'
            }), 400

        if changing_self:
            # Super users cannot change their own is_super value
            if current_user.is_super and is_super:
                return jsonify({
                    'error':
                    'Super users cannot change their own `super` status'
                }), 400

            if is_agency_admin:
                return jsonify({
                    'error':
                    'Agency Administrators cannot change their administrator permissions'
                }), 400

    elif user_.is_public:
        if current_user != user_:
            return jsonify({
                'error':
                'Public user attributes cannot be modified by agency users.'
            }), 400

    elif user_.is_anonymous_requester:
        ur = current_user.user_requests.filter_by(
            request_id=user_.anonymous_request.id).one_or_none()
        if not ur:
            return jsonify({
                'error':
                'Agency users can only modify anonymous requesters for requests where they are assigned.'
            }), 403

        if not ur.has_permission(permission.EDIT_REQUESTER_INFO):
            return jsonify({
                'error':
                'Current user does not have EDIT_REQUESTER_INFO permission'
            }), 403

    # Gather User Fields

    user_editable_fields = user_attrs.UserEditableFieldsDict(
        email=request.form.get('email'),
        phone_number=request.form.get('phone'),
        fax_number=request.form.get('fax'),
        title=request.form.get('title'),
        organization=request.form.get('organization'),
        address_one=request.form.get('address_one'),
        address_two=request.form.get('address_two'),
        zip=request.form.get('zipcode'),
        city=request.form.get('city'),
        state=request.form.get('state'))
    status_field_val = user_attrs.UserStatusDict(
        is_agency_admin=request.form.get('is_agency_admin'),
        is_agency_active=request.form.get('is_agency_active'),
        is_super=request.form.get('is_super'))
    if changing_self:
        if not user_editable_fields.is_valid:
            return jsonify({"error": "Missing contact information."}), 400
    else:
        if user_.is_agency and not status_field_val.is_valid:
            return jsonify({"error": "User status values invalid"}), 400

    # Status Values for Events
    old_status = {}
    new_status = {}

    # User Attributes for Events
    old_user_attrs = defaultdict(dict)
    new_user_attrs = defaultdict(dict)

    for key, value in status_field_val.items():
        if value is not None:
            if key == 'is_agency_admin':
                cur_val = user_.is_agency_admin(agency_ein)
            elif key == 'is_agency_active':
                cur_val = user_.is_agency_active(agency_ein)
            else:
                cur_val = getattr(user_, key)
            new_val = eval_request_bool(status_field_val[key])
            if cur_val != new_val:
                old_status[key] = cur_val
                new_status[key] = new_val

    for key, value in user_editable_fields.items():
        # Address is a dictionary and needs to be handled separately
        if key == 'address':
            for address_key, address_value in value.items():
                cur_val = (user_.mailing_address.get(address_key)
                           if user_.mailing_address else None)
                new_val = address_value
                if cur_val != new_val:
                    old_user_attrs['_mailing_address'][address_key] = cur_val
                    new_user_attrs['_mailing_address'][address_key] = new_val
            continue
        if value is not None:
            cur_val = getattr(user_, key)
            new_val = user_editable_fields[key]
            if cur_val != new_val:
                old_user_attrs[key] = cur_val
                new_user_attrs[key] = new_val

    # Update the Users object if new_user_attrs is not None (empty dict)
    if new_user_attrs and old_user_attrs:
        update_object(new_user_attrs, Users, user_id)
        # GUID is added to the 'new' value in events to identify the user that was changed
        new_user_attrs['user_guid'] = user_.guid

        # create event(s)
        event_kwargs = {
            'request_id':
            user_.anonymous_request.id
            if user_.is_anonymous_requester else None,
            'response_id':
            None,
            'user_guid':
            current_user.guid,
            'timestamp':
            datetime.utcnow()
        }

        create_object(
            Events(type_=(event_type.REQUESTER_INFO_EDITED
                          if user_.is_anonymous_requester else
                          event_type.USER_INFO_EDITED),
                   previous_value=old_user_attrs,
                   new_value=new_user_attrs,
                   **event_kwargs))

    if new_status:
        redis_key = "{current_user_guid}-{update_user_guid}-{agency_ein}-{timestamp}".format(
            current_user_guid=current_user.guid,
            update_user_guid=user_.guid,
            agency_ein=agency_ein,
            timestamp=datetime.now())
        old_status['user_guid'] = user_.guid
        new_status['user_guid'] = user_.guid

        old_status['agency_ein'] = agency_ein
        new_status['agency_ein'] = agency_ein

        # Update agency active status and create associated event. Occurs first because a user can be
        # activated / deactivated with admin status set to True.
        if is_agency_active is not None:
            update_object(new_status, AgencyUsers, (user_.guid, agency_ein))
            event_kwargs = {
                'request_id':
                user_.anonymous_request.id
                if user_.is_anonymous_requester else None,
                'response_id':
                None,
                'user_guid':
                current_user.guid,
                'timestamp':
                datetime.utcnow()
            }
            if is_agency_active:
                create_object(
                    Events(type_=event_type.AGENCY_USER_ACTIVATED,
                           previous_value=old_status,
                           new_value=new_status,
                           **event_kwargs))
                if is_agency_admin is not None and is_agency_admin:
                    make_user_admin.apply_async(args=(user_.guid,
                                                      current_user.guid,
                                                      agency_ein),
                                                task_id=redis_key)
                    return jsonify({
                        'status': 'success',
                        'message': 'Update task has been scheduled.'
                    }), 200
            else:
                create_object(
                    Events(type_=event_type.AGENCY_USER_DEACTIVATED,
                           previous_value=old_status,
                           new_value=new_status,
                           **event_kwargs))
                remove_user_permissions.apply_async(
                    args=(user_.guid, current_user.guid, agency_ein,
                          event_type.AGENCY_USER_DEACTIVATED),
                    task_id=redis_key)
                return jsonify({
                    'status': 'success',
                    'message': 'Update task has been scheduled.'
                }), 200
            return jsonify({
                'status': 'success',
                'message': 'Agency user successfully updated'
            }), 200

        # Update agency admin status and create associated event.
        elif is_agency_admin is not None:
            new_status['agency_ein'] = agency_ein
            update_object(new_status, AgencyUsers, (user_.guid, agency_ein))
            event_kwargs = {
                'request_id':
                user_.anonymous_request.id
                if user_.is_anonymous_requester else None,
                'response_id':
                None,
                'user_guid':
                current_user.guid,
                'timestamp':
                datetime.utcnow()
            }
            if is_agency_admin:
                create_object(
                    Events(type_=event_type.USER_MADE_AGENCY_ADMIN,
                           previous_value=old_status,
                           new_value=new_status,
                           **event_kwargs))
                make_user_admin.apply_async(args=(user_.guid,
                                                  current_user.guid,
                                                  agency_ein),
                                            task_id=redis_key)
                return jsonify({
                    'status': 'success',
                    'message': 'Update task has been scheduled.'
                }), 200
            else:
                create_object(
                    Events(type_=event_type.USER_MADE_AGENCY_USER,
                           previous_value=old_status,
                           new_value=new_status,
                           **event_kwargs))
                remove_user_permissions.apply_async(
                    args=(user_.guid, current_user.guid, agency_ein,
                          event_type.USER_MADE_AGENCY_USER),
                    task_id=redis_key)
                return jsonify({
                    'status': 'success',
                    'message': 'Update task has been scheduled.'
                }), 200

        # Update user super status and create associated event.
        elif is_super is not None:
            new_status['agency_ein'] = agency_ein
            update_object(new_status, AgencyUsers, (user_.guid, agency_ein))
            event_kwargs = {
                'request_id':
                user_.anonymous_request.id
                if user_.is_anonymous_requester else None,
                'response_id':
                None,
                'user_guid':
                current_user.guid,
                'timestamp':
                datetime.utcnow()
            }
            if is_super:
                create_object(
                    Events(type_=event_type.USER_MADE_SUPER_USER,
                           previous_value=old_status,
                           new_value=new_status,
                           **event_kwargs))
            else:
                create_object(
                    Events(type_=event_type.USER_REMOVED_FROM_SUPER,
                           previous_value=old_status,
                           new_value=new_status,
                           **event_kwargs))
            return jsonify({
                'status': 'success',
                'message': 'Agency user successfully updated'
            }), 200
    # Always returns 200 so that we can use the data from the response in the client side javascript
    return jsonify({
        'status': 'Not Modified',
        'message': 'No changes detected'
    }), 200