cidc_api/resources/users.py
import io
import os
os.environ["TZ"] = "UTC"
from datetime import datetime
from flask import Blueprint, send_file
from flask_cachecontrol import dont_cache
from werkzeug.exceptions import BadRequest
from ..shared.auth import get_current_user, requires_auth
from ..shared.emails import new_user_registration
from ..shared.rest_utils import (
with_lookup,
marshal_response,
unmarshal_request,
use_args_with_pagination,
)
from ..shared.gcloud_client import grant_bigquery_access, revoke_bigquery_access
from ..models import (
Users,
UserSchema,
UserListSchema,
CIDCRole,
IntegrityError,
Permissions,
)
from ..config.settings import ENV
users_bp = Blueprint("users", __name__)
user_schema = UserSchema()
user_list_schema = UserListSchema()
new_user_schema = UserSchema(exclude=("approval_date", "role", "disabled"))
partial_user_schema = UserSchema(partial=True)
@users_bp.route("/self", methods=["GET"])
@requires_auth("self")
@marshal_response(user_schema)
def get_self():
"""Return the current user's information to them."""
return get_current_user()
@users_bp.route("/self", methods=["POST"])
@requires_auth("self")
@unmarshal_request(new_user_schema, "user")
# @marshal_response(user_schema, 201)
def create_self(user):
"""
Allow the current user to create a profile for themself. On success,
send an email to the CIDC mailing list with a registration notification.
"""
return "Data Freeze", 503
# current_user = get_current_user()
# if current_user.email != user.email:
# raise BadRequest(
# f"{current_user.email} can't create a user with email {user.email}"
# )
# try:
# user.insert()
# except IntegrityError as e:
# raise BadRequest(str(e.orig))
# new_user_registration(user.email, send_email=True)
# return user
@users_bp.route("/", methods=["POST"])
@requires_auth("users", [CIDCRole.ADMIN.value])
@unmarshal_request(user_schema, "user")
# @marshal_response(user_schema, 201)
def create_user(user):
"""
Allow admins to create user records.
"""
return "Data Freeze", 503
# try:
# user.insert()
# except IntegrityError as e:
# raise BadRequest(str(e.orig))
# return user
@users_bp.route("/", methods=["GET"])
@requires_auth("users", [CIDCRole.ADMIN.value])
@use_args_with_pagination({}, user_schema)
@marshal_response(user_list_schema)
def list_users(args, pagination_args):
"""
List all users. TODO: pagination support
"""
users = Users.list(**pagination_args)
count = Users.count()
return {"_items": users, "_meta": {"total": count}}
@users_bp.route("/<int:user>", methods=["GET"])
@requires_auth("users_item", [CIDCRole.ADMIN.value])
@with_lookup(Users, "user")
@marshal_response(user_schema)
def get_user(user: Users):
"""Get a single user by their id."""
# this is not user-input due to @with_lookup, so safe to return
return user
@users_bp.route("/<int:user>", methods=["PATCH"])
@requires_auth("users_item", [CIDCRole.ADMIN.value])
@with_lookup(Users, "user", check_etag=True)
@unmarshal_request(partial_user_schema, "user_updates", load_sqla=False)
# @marshal_response(user_schema)
def update_user(user: Users, user_updates: Users):
"""Update a single user's information."""
return "Data Freeze", 503
# If a user is being awarded their first role, add an approval date
# if not user.role and "role" in user_updates:
# user_updates["approval_date"] = datetime.now()
# grant_bigquery_access([user.email])
# # If this user is being re-enabled after being disabled, update their last
# # access date to now so that they aren't disabled again tomorrow and
# # refresh their IAM permissions.
# if user.disabled and user_updates.get("disabled") == False:
# user_updates["_accessed"] = datetime.now()
# # don't grant permissions unless they've be approved
# if user.approval_date:
# Permissions.grant_user_permissions(user)
# grant_bigquery_access([user.email])
# # If this user is being disabled, remove all of their download permissions.
# if not user.disabled and user_updates.get("disabled") == True:
# Permissions.revoke_user_permissions(user)
# revoke_bigquery_access(user.email)
# # this is not user-input due to @with_lookup, so safe to return
# user.update(changes=user_updates)
# return user
@users_bp.route("/data_access_report", methods=["GET"])
@requires_auth("users_data_access_report", [CIDCRole.ADMIN.value])
@dont_cache()
def get_data_access_report():
"""Generate the user data access report."""
buffer = io.BytesIO()
Users.get_data_access_report(buffer)
buffer.seek(0)
filename = f"cidc_{ENV}_data_access_{datetime.now().date()}.xlsx"
return send_file(buffer, as_attachment=True, attachment_filename=filename)