Beispiel #1
0
    def __init__(self):
        super(SearchRequestsForm, self).__init__()
        self.agency_ein.choices = get_agency_choices()
        self.agency_ein.choices.insert(0, ('', 'All'))
        if current_user.is_agency:
            self.agency_ein.default = current_user.default_agency_ein
            user_agencies = sorted([(agencies.ein, agencies.name) for agencies in current_user.agencies
                                    if agencies.ein != current_user.default_agency_ein],
                                   key=lambda x: x[1])
            default_agency = current_user.default_agency

            # set default value of agency select field to agency user's primary agency
            self.agency_ein.default = default_agency.ein
            self.agency_ein.choices.insert(1, self.agency_ein.choices.pop(self.agency_ein.choices.index(
                (default_agency.ein, default_agency.name))
            ))

            # set secondary agencies to be below the primary
            for agency in user_agencies:
                self.agency_ein.choices.insert(2, self.agency_ein.choices.pop(self.agency_ein.choices.index(agency)))

            # get choices for agency user select field
            if current_user.is_agency_admin():
                self.agency_user.choices = get_active_users_as_choices(current_user.default_agency.ein)

            if current_user.is_agency_active() and not current_user.is_agency_admin():
                self.agency_user.choices = [
                    ('', 'All'),
                    (current_user.get_id(), 'My Requests')
                ]
                self.agency_user.default = current_user.get_id()

            # process form for default values
            self.process()
Beispiel #2
0
def get():
    """
    This function handles the retrieval of report data to generate the chart on the frontend.
    Takes in agency_ein or user_guid from the frontend and filters for the number of requests closed and requests
    opened.

    :return: json object({"labels": ["Opened", "Closed"],
                          "values": [150, 135],
                          "active_users": [('', ''), ('o8pj0k', 'John Doe')]}), 200
    """
    agency_ein = request.args.get('agency_ein')
    user_guid = request.args.get('user_guid', '')
    requests_opened = 0
    requests_closed = 0
    active_users = []
    is_visible = False
    results = False
    if agency_ein and user_guid == '':
        if agency_ein == 'all':
            active_requests = Requests.query.with_entities(Requests.status).join(
                Agencies, Requests.agency_ein == Agencies.ein).filter(
                Agencies.is_active).all()
            requests_closed = len([r for r in active_requests if r[0] == request_status.CLOSED])
            requests_opened = len(active_requests) - requests_closed
        else:
            active_requests = Requests.query.with_entities(Requests.status).join(
                Agencies, Requests.agency_ein == Agencies.ein).filter(
                Agencies.ein == agency_ein, Agencies.is_active).all()
            requests_closed = len([r for r in active_requests if r[0] == request_status.CLOSED])
            requests_opened = len(active_requests) - requests_closed
            if not (current_user.is_anonymous or current_user.is_public):
                if (current_user.is_agency and current_user.is_agency_admin(agency_ein)) or current_user.is_super:
                    is_visible = True
                    if current_user.is_agency_admin(agency_ein) or current_user.is_super:
                        active_users = sorted(
                            [(user.guid, user.name)
                             for user in Agencies.query.filter_by(ein=agency_ein).one().active_users],
                            key=lambda x: x[1])
                    elif current_user.is_agency_active(agency_ein):
                        active_users = [(current_user.guid, current_user.name)]
                    if active_users:
                        active_users.insert(0, ('', ''))
                        results = True

    elif user_guid and (current_user.is_agency_active(agency_ein) or
                        current_user.is_agency_admin(agency_ein) or
                        current_user.is_super):
        is_visible = True
        ureqs = UserRequests.query.filter(UserRequests.user_guid == user_guid
                                          ).all()

        requests_closed = len([u for u in ureqs if u.request.status == request_status.CLOSED])
        requests_opened = len([u for u in ureqs if u.request.status != request_status.CLOSED])

    return jsonify({"labels": ["Open", "Closed"],
                    "values": [requests_opened, requests_closed],
                    "active_users": active_users,
                    "is_visible": is_visible,
                    "results": results
                    }), 200
Beispiel #3
0
def get():
    """
    This function handles the retrieval of report data to generate the chart on the frontend.
    Takes in agency_ein or user_guid from the frontend and filters for the number of requests closed and requests
    opened.

    :return: json object({"labels": ["Opened", "Closed"],
                          "values": [150, 135],
                          "active_users": [('', ''), ('o8pj0k', 'John Doe')]}), 200
    """
    agency_ein = request.args.get('agency_ein')
    user_guid = request.args.get('user_guid', '')
    requests_opened = 0
    requests_closed = 0
    active_users = []
    is_visible = False
    results = False
    if agency_ein and user_guid == '':
        if agency_ein == 'all':
            active_requests = Requests.query.with_entities(Requests.status).join(
                Agencies, Requests.agency_ein == Agencies.ein).filter(
                Agencies.is_active).all()
            requests_closed = len([r for r in active_requests if r[0] == request_status.CLOSED])
            requests_opened = len(active_requests) - requests_closed
        else:
            active_requests = Requests.query.with_entities(Requests.status).join(
                Agencies, Requests.agency_ein == Agencies.ein).filter(
                Agencies.ein == agency_ein, Agencies.is_active).all()
            requests_closed = len([r for r in active_requests if r[0] == request_status.CLOSED])
            requests_opened = len(active_requests) - requests_closed
            if not (current_user.is_anonymous or current_user.is_public):
                if (current_user.is_agency and current_user.is_agency_admin(agency_ein)) or current_user.is_super:
                    is_visible = True
                    if current_user.is_agency_admin(agency_ein) or current_user.is_super:
                        active_users = sorted(
                            [(user.guid, user.name)
                             for user in Agencies.query.filter_by(ein=agency_ein).one().active_users],
                            key=lambda x: x[1])
                    elif current_user.is_agency_active(agency_ein):
                        active_users = [(current_user.guid, current_user.name)]
                    if active_users:
                        active_users.insert(0, ('', ''))
                        results = True

    elif user_guid and (current_user.is_agency_active(agency_ein) or
                        current_user.is_agency_admin(agency_ein) or
                        current_user.is_super):
        is_visible = True
        ureqs = UserRequests.query.filter(UserRequests.user_guid == user_guid
                                          ).all()

        requests_closed = len([u for u in ureqs if u.request.status == request_status.CLOSED])
        requests_opened = len([u for u in ureqs if u.request.status != request_status.CLOSED])

    return jsonify({"labels": ["Open", "Closed"],
                    "values": [requests_opened, requests_closed],
                    "active_users": active_users,
                    "is_visible": is_visible,
                    "results": results
                    }), 200
Beispiel #4
0
    def __init__(self):
        super(SearchRequestsForm, self).__init__()
        self.agency_ein.choices = get_agency_choices()
        self.agency_ein.choices.insert(0, ("", "All"))
        if current_user.is_agency:
            self.agency_ein.default = current_user.default_agency_ein
            user_agencies = sorted(
                [(agencies.ein, agencies.name)
                 for agencies in current_user.agencies
                 if agencies.ein != current_user.default_agency_ein],
                key=lambda x: x[1],
            )
            default_agency = current_user.default_agency

            # set default value of agency select field to agency user's primary agency
            self.agency_ein.default = default_agency.ein
            self.agency_ein.choices.insert(
                1,
                self.agency_ein.choices.pop(
                    self.agency_ein.choices.index(
                        (default_agency.ein, default_agency.name))),
            )

            # set secondary agencies to be below the primary
            for agency in user_agencies:
                self.agency_ein.choices.insert(
                    2,
                    self.agency_ein.choices.pop(
                        self.agency_ein.choices.index(agency)),
                )

            # get choices for agency user select field
            if current_user.is_agency_admin():
                self.agency_user.choices = get_active_users_as_choices(
                    current_user.default_agency.ein)

            if current_user.is_agency_active(
            ) and not current_user.is_agency_admin():
                self.agency_user.choices = [
                    ("", "All"),
                    (current_user.get_id(), "My Requests"),
                ]
                self.agency_user.default = current_user.get_id()

            if default_agency.agency_features["custom_request_forms"][
                    "enabled"]:
                self.request_type.choices = [
                    (custom_request_form.form_name,
                     custom_request_form.form_name)
                    for custom_request_form in CustomRequestForms.query.
                    filter_by(agency_ein=default_agency.ein).order_by(
                        asc(CustomRequestForms.category),
                        asc(CustomRequestForms.id)).all()
                ]
                self.request_type.choices.insert(0, ("", "All"))

            # process form for default values
            self.process()
Beispiel #5
0
    def __init__(self, agency_ein=None):
        super(SelectAgencyForm, self).__init__()

        if current_user.is_super:
            # Super Users will always see every agency in the dropdown.
            self.agencies.choices = [
                (agency.ein, '({status}) {agency_name}'.format(
                    status='ACTIVE', agency_name=agency.name)
                 if agency.is_active else '{agency_name}'.format(
                     agency_name=agency.name))
                for agency in Agencies.query.order_by(
                    Agencies.is_active.desc(), Agencies._name.asc()).all()
            ]
        else:
            # Multi-Agency Admin Users will only see the agencies that they administer in the dropdown.
            agency_choices = []
            for agency in current_user.agencies.order_by(
                    Agencies.is_active.desc(), Agencies._name.asc()).all():
                if current_user.is_agency_admin(agency.ein):
                    agency_tuple = (
                        agency.ein, '({status}) {agency_name}'.format(
                            status='ACTIVE', agency_name=agency.name)
                        if agency.is_active else '{agency_name}'.format(
                            agency_name=agency.name))
                    agency_choices.append(agency_tuple)
            self.agencies.choices = agency_choices
        if agency_ein:
            for agency in self.agencies.choices:
                if agency[0] == agency_ein:
                    self.agencies.choices.insert(
                        0,
                        self.agencies.choices.pop(
                            self.agencies.choices.index(agency)))
        self.process()
Beispiel #6
0
def main(agency_ein=None):
    if not current_user.is_anonymous:
        if agency_ein is None:
            agency_ein = current_user.find_admin_agency_ein
        if current_user.is_super:
            agency_form = SelectAgencyForm(agency_ein)
            agency_ein = agency_ein or agency_form.agencies.choices[0][0]
            user_form = ActivateAgencyUserForm(agency_ein)
            active_users = get_agency_active_users(agency_ein)
            agency_is_active = Agencies.query.filter_by(ein=agency_ein).one().is_active
            return render_template("admin/main.html",
                                   agency_ein=agency_ein,
                                   users=active_users,
                                   user_form=user_form,
                                   agency_form=agency_form,
                                   agency_is_active=agency_is_active)
        elif current_user.is_agency_admin(agency_ein) and current_user.is_agency_active(agency_ein):
            form = ActivateAgencyUserForm(agency_ein)
            active_users = get_agency_active_users(agency_ein)
            del active_users[active_users.index(current_user)]
            if len(current_user.agencies.all()) > 1:
                agency_form = SelectAgencyForm(agency_ein)
                return render_template("admin/main.html",
                                       agency_ein=agency_ein,
                                       users=active_users,
                                       agency_form=agency_form,
                                       user_form=form,
                                       multi_agency_admin=True)
            return render_template("admin/main.html",
                                   users=active_users,
                                   agency_ein=agency_ein,
                                   user_form=form)

    return abort(404)
Beispiel #7
0
    def __init__(self):
        super(SearchRequestsForm, self).__init__()
        self.agency_ein.choices = get_agency_choices()
        self.agency_ein.choices.insert(0, ("", "All"))
        if current_user.is_agency:
            self.agency_ein.default = current_user.default_agency_ein
            user_agencies = sorted(
                [(agencies.ein, agencies.name)
                 for agencies in current_user.agencies
                 if agencies.ein != current_user.default_agency_ein],
                key=lambda x: x[1],
            )
            default_agency = current_user.default_agency

            # set default value of agency select field to agency user's primary agency
            self.agency_ein.default = default_agency.ein
            self.agency_ein.choices.insert(
                1,
                self.agency_ein.choices.pop(
                    self.agency_ein.choices.index(
                        (default_agency.ein, default_agency.name))),
            )

            # set secondary agencies to be below the primary
            for agency in user_agencies:
                self.agency_ein.choices.insert(
                    2,
                    self.agency_ein.choices.pop(
                        self.agency_ein.choices.index(agency)),
                )

            # get choices for agency user select field
            if current_user.is_agency_admin():
                self.agency_user.choices = get_active_users_as_choices(
                    current_user.default_agency.ein)

            if current_user.is_agency_active(
            ) and not current_user.is_agency_admin():
                self.agency_user.choices = [
                    ("", "All"),
                    (current_user.get_id(), "My Requests"),
                ]
                self.agency_user.default = current_user.get_id()

            # process form for default values
            self.process()
Beispiel #8
0
def edit(request_id):
    """
    Updates a users permissions on a request and sends notification emails.

    Expects a request body containing the user's guid and updated permissions.
    Ex:
    {
        'user': '******',
        1: true,
        5: false
    }
    :return:
    """
    current_user_request = current_user.user_requests.filter_by(
        request_id=request_id).one()
    current_request = current_user_request.request

    if (current_user.is_agency
            and (current_user.is_super
                 or current_user.is_agency_admin(current_request.agency.ein)
                 or current_user_request.has_permission(
                     permission.EDIT_USER_REQUEST_PERMISSIONS))):
        user_data = flask_request.form
        point_of_contact = True if role_name.POINT_OF_CONTACT in user_data else False

        required_fields = ['user']

        for field in required_fields:
            if not user_data.get(field):
                flash('Uh Oh, it looks like the {} is missing! '
                      'This is probably NOT your fault.'.format(field),
                      category='danger')
                return redirect(url_for('request.view', request_id=request_id))

        try:
            permissions = [int(i) for i in user_data.getlist('permission')]
            edit_user_request(request_id=request_id,
                              user_guid=user_data.get('user'),
                              permissions=permissions,
                              point_of_contact=point_of_contact)
        except UserRequestException as e:
            sentry.captureException()
            flash(e, category='warning')
            return redirect(url_for('request.view', request_id=request_id))
        return 'OK', 200
    return abort(403)
Beispiel #9
0
def edit(request_id):
    """
    Updates a users permissions on a request and sends notification emails.

    Expects a request body containing the user's guid and updated permissions.
    Ex:
    {
        'user': '******',
        1: true,
        5: false
    }
    :return:
    """
    current_user_request = current_user.user_requests.filter_by(request_id=request_id).one()
    current_request = current_user_request.request

    if (
                current_user.is_agency and (
                            current_user.is_super or
                            current_user.is_agency_admin(current_request.agency.ein) or
                        current_user_request.has_permission(permission.EDIT_USER_REQUEST_PERMISSIONS)
            )
    ):
        user_data = flask_request.form
        point_of_contact = True if role_name.POINT_OF_CONTACT in user_data else False

        required_fields = ['user']

        for field in required_fields:
            if not user_data.get(field):
                flash('Uh Oh, it looks like the {} is missing! '
                      'This is probably NOT your fault.'.format(field), category='danger')
                return redirect(url_for('request.view', request_id=request_id))

        try:
            permissions = [int(i) for i in user_data.getlist('permission')]
            edit_user_request(request_id=request_id, user_guid=user_data.get('user'),
                              permissions=permissions, point_of_contact=point_of_contact)
        except UserRequestException as e:
            sentry.captureException()
            flash(e, category='warning')
            return redirect(url_for('request.view', request_id=request_id))
        return 'OK', 200
    return abort(403)
Beispiel #10
0
def delete(request_id):
    """
    Removes a user from a request and send notification emails.

    Expects a request body containing user's guid and confirmation string.
    Ex:
    {
        'user': '******',
        'remove-confirmation-string': 'remove'
    }
    :return:
    """
    agency_ein = Requests.query.filter_by(id=request_id).one().agency.ein

    if (current_user.is_agency and (
                current_user.is_super or (
                        current_user.is_agency_active(agency_ein) and
                        current_user.is_agency_admin(agency_ein)
                )
            )
        ):
        user_data = flask_request.form

        required_fields = ['user',
                           'remove-confirm-string']

        # TODO: Get copy from business, insert sentry issue key in message
        # Error handling to check if retrieved elements exist. Flash error message if elements does not exist.
        for field in required_fields:
            if user_data.get(field) is None:
                flash('Uh Oh, it looks like the {} is missing! '
                      'This is probably NOT your fault.'.format(field), category='danger')
                return redirect(url_for('request.view', request_id=request_id))

        valid_confirmation_string = "REMOVE"
        if user_data['remove-confirm-string'].upper() != valid_confirmation_string:
            flash('Uh Oh, it looks like the confirmation text is incorrect! '
                  'This is probably NOT your fault.', category='danger')
            return redirect(url_for('request.view', request_id=request_id))

        remove_user_request(request_id,
                            user_data['user'])
        return '', 200
    return '', 403
Beispiel #11
0
def delete(request_id):
    """
    Removes a user from a request and send notification emails.

    Expects a request body containing user's guid and confirmation string.
    Ex:
    {
        'user': '******',
        'remove-confirmation-string': 'remove'
    }
    :return:
    """
    agency_ein = Requests.query.filter_by(id=request_id).one().agency.ein

    if (current_user.is_agency
            and (current_user.is_super or
                 (current_user.is_agency_active(agency_ein)
                  and current_user.is_agency_admin(agency_ein)))):
        user_data = flask_request.form

        required_fields = ['user', 'remove-confirm-string']

        # TODO: Get copy from business, insert sentry issue key in message
        # Error handling to check if retrieved elements exist. Flash error message if elements does not exist.
        for field in required_fields:
            if user_data.get(field) is None:
                flash('Uh Oh, it looks like the {} is missing! '
                      'This is probably NOT your fault.'.format(field),
                      category='danger')
                return redirect(url_for('request.view', request_id=request_id))

        valid_confirmation_string = "REMOVE"
        if user_data['remove-confirm-string'].upper(
        ) != valid_confirmation_string:
            flash(
                'Uh Oh, it looks like the confirmation text is incorrect! '
                'This is probably NOT your fault.',
                category='danger')
            return redirect(url_for('request.view', request_id=request_id))

        remove_user_request(request_id, user_data['user'])
        return '', 200
    return '', 403
Beispiel #12
0
def create(request_id):
    """
     Creates a users permissions entry for a request and sends notification emails.

    Expects a request body containing the user's guid and new permissions.
    Ex:
    {
        'user': '******',
        1: true,
        5: false
    }
    :return:
    """
    current_user_request = current_user.user_requests.filter_by(
        request_id=request_id).first()
    current_request = current_user_request.request

    if (current_user.is_agency and
        (current_user.is_super
         or current_user.is_agency_admin(current_request.agency.ein) or
         current_user_request.has_permission(permission.ADD_USER_TO_REQUEST))):
        user_data = flask_request.form

        required_fields = ['user']

        for field in required_fields:
            if not user_data.get(field):
                flash('Uh Oh, it looks like the {} is missing! '
                      'This is probably NOT your fault.'.format(field),
                      category='danger')
                return redirect(url_for('request.view', request_id=request_id))

        try:
            permissions = [int(i) for i in user_data.getlist('permission')]
            add_user_request(request_id=request_id,
                             user_guid=user_data.get('user'),
                             permissions=permissions)
        except UserRequestException as e:
            flash(str(e), category='warning')
            return redirect(url_for('request.view', request_id=request_id))
        return redirect(url_for('request.view', request_id=request_id))
    return abort(403)
Beispiel #13
0
def get_active_users(agency_ein):
    """
    Retrieve the active users for the specified agency.

    :param agency_ein: Agency EIN (String)

    :return: JSON Object({"active_users": [('', 'All'), ('o8pj0k', 'John Doe')],
                          "is_admin": True}), 200
    """
    if current_user.is_agency_admin(agency_ein):
        return jsonify({"active_users": get_active_users_as_choices(agency_ein), "is_admin": True}), 200

    elif current_user.is_agency_active(agency_ein):
        active_users = [
            ('', 'All'),
            (current_user.get_id(), 'My Requests')
        ]
        return jsonify({"active_users": active_users, "is_admin": False}), 200

    else:
        return jsonify({}), 404
Beispiel #14
0
def get_active_users(agency_ein):
    """
    Retrieve the active users for the specified agency.

    :param agency_ein: Agency EIN (String)

    :return: JSON Object({"active_users": [('', 'All'), ('o8pj0k', 'John Doe')],
                          "is_admin": True}), 200
    """
    if current_user.is_agency_admin(agency_ein):
        return jsonify({"active_users": get_active_users_as_choices(agency_ein), "is_admin": True}), 200

    elif current_user.is_agency_active(agency_ein):
        active_users = [
            ('', 'All'),
            (current_user.get_id(), 'My Requests')
        ]
        return jsonify({"active_users": active_users, "is_admin": False}), 200

    else:
        return jsonify({}), 404
Beispiel #15
0
def main(agency_ein=None):
    if not current_user.is_anonymous:
        if agency_ein is None:
            agency_ein = current_user.default_agency_ein
        if current_user.is_super:
            agency_form = SelectAgencyForm(agency_ein)
            agency_ein = agency_ein or agency_form.agencies.choices[0][0]
            user_form = ActivateAgencyUserForm(agency_ein)
            active_users = get_agency_active_users(agency_ein)
            agency_is_active = Agencies.query.filter_by(
                ein=agency_ein).one().is_active
            return render_template("admin/main.html",
                                   agency_ein=agency_ein,
                                   users=active_users,
                                   user_form=user_form,
                                   agency_form=agency_form,
                                   agency_is_active=agency_is_active)
        elif current_user.is_agency_admin(
                agency_ein) and current_user.is_agency_active(agency_ein):
            form = ActivateAgencyUserForm(agency_ein)
            active_users = get_agency_active_users(agency_ein)
            del active_users[active_users.index(current_user)]
            if len(current_user.agencies.all()) > 1:
                agency_form = SelectAgencyForm(agency_ein)
                return render_template("admin/main.html",
                                       agency_ein=agency_ein,
                                       users=active_users,
                                       agency_form=agency_form,
                                       user_form=form,
                                       multi_agency_admin=True)
            return render_template("admin/main.html",
                                   users=active_users,
                                   agency_ein=agency_ein,
                                   user_form=form)

    return abort(404)
Beispiel #16
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
Beispiel #17
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
Beispiel #18
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