mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-24 00:07:02 -04:00
Compare commits
8 Commits
save_csv
...
11-14-2024
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad50568c10 | ||
|
|
9796d424b5 | ||
|
|
d678659cbe | ||
|
|
49932f6942 | ||
|
|
0091fd0b31 | ||
|
|
683bf893c1 | ||
|
|
a95138f344 | ||
|
|
cc5c4114e0 |
@@ -35,6 +35,8 @@ def index():
|
|||||||
if current_user and current_user.is_authenticated:
|
if current_user and current_user.is_authenticated:
|
||||||
return redirect(url_for("main.choose_account"))
|
return redirect(url_for("main.choose_account"))
|
||||||
|
|
||||||
|
ttl = 24 * 60 * 60
|
||||||
|
|
||||||
# make and store the state
|
# make and store the state
|
||||||
state = generate_token(
|
state = generate_token(
|
||||||
str(request.remote_addr),
|
str(request.remote_addr),
|
||||||
@@ -42,12 +44,12 @@ def index():
|
|||||||
current_app.config["DANGEROUS_SALT"],
|
current_app.config["DANGEROUS_SALT"],
|
||||||
)
|
)
|
||||||
state_key = f"login-state-{unquote(state)}"
|
state_key = f"login-state-{unquote(state)}"
|
||||||
redis_client.set(state_key, state)
|
redis_client.set(state_key, state, ex=ttl)
|
||||||
|
|
||||||
# make and store the nonce
|
# make and store the nonce
|
||||||
nonce = secrets.token_urlsafe()
|
nonce = secrets.token_urlsafe()
|
||||||
nonce_key = f"login-nonce-{unquote(nonce)}"
|
nonce_key = f"login-nonce-{unquote(nonce)}"
|
||||||
redis_client.set(nonce_key, nonce)
|
redis_client.set(nonce_key, nonce, ex=ttl)
|
||||||
|
|
||||||
url = os.getenv("LOGIN_DOT_GOV_INITIAL_SIGNIN_URL")
|
url = os.getenv("LOGIN_DOT_GOV_INITIAL_SIGNIN_URL")
|
||||||
if url is not None:
|
if url is not None:
|
||||||
|
|||||||
@@ -10,12 +10,10 @@ from flask import (
|
|||||||
stream_with_context,
|
stream_with_context,
|
||||||
url_for,
|
url_for,
|
||||||
)
|
)
|
||||||
from flask_login import current_user
|
|
||||||
|
|
||||||
from app import current_service, job_api_client, notification_api_client
|
from app import current_service, job_api_client, notification_api_client
|
||||||
from app.main import main
|
from app.main import main
|
||||||
from app.notify_client.api_key_api_client import KEY_TYPE_TEST
|
from app.notify_client.api_key_api_client import KEY_TYPE_TEST
|
||||||
from app.s3_client.s3_csv_client import delete_report, report_upload
|
|
||||||
from app.utils import (
|
from app.utils import (
|
||||||
DELIVERED_STATUSES,
|
DELIVERED_STATUSES,
|
||||||
FAILURE_STATUSES,
|
FAILURE_STATUSES,
|
||||||
@@ -146,36 +144,6 @@ def download_notifications_csv(service_id):
|
|||||||
file_time = datetime.now().strftime("%Y-%m-%d %I:%M:%S %p")
|
file_time = datetime.now().strftime("%Y-%m-%d %I:%M:%S %p")
|
||||||
file_time = f"{file_time} {get_user_preferred_timezone()}"
|
file_time = f"{file_time} {get_user_preferred_timezone()}"
|
||||||
|
|
||||||
csv = generate_notifications_csv(
|
|
||||||
service_id=service_id,
|
|
||||||
job_id=None,
|
|
||||||
status=filter_args.get("status"),
|
|
||||||
page=request.args.get("page", 1),
|
|
||||||
page_size=10000,
|
|
||||||
format_for_csv=True,
|
|
||||||
template_type=filter_args.get("message_type"),
|
|
||||||
limit_days=service_data_retention_days,
|
|
||||||
)
|
|
||||||
|
|
||||||
# START asynchronous reporting block
|
|
||||||
csv_file = "".join(csv)
|
|
||||||
file_location = f"reports/{service_id}/{current_user.id}/{service_data_retention_days}/report.csv"
|
|
||||||
|
|
||||||
# TODO these are some capabilities we will probably need when
|
|
||||||
# report generation becomes asynchronous.
|
|
||||||
|
|
||||||
# old_content = report_download(file_location)
|
|
||||||
# current_app.logger.info(f"OLD CONTENT IS {old_content}")
|
|
||||||
# reports = get_downloadable_reports(current_user.id, service_id)
|
|
||||||
|
|
||||||
# TODO these are to support asynchronous report generation.
|
|
||||||
# Leaving them commented in so they get exercised.
|
|
||||||
delete_report(file_location)
|
|
||||||
report_upload(file_location, csv_file)
|
|
||||||
# END asynchronous reporting block
|
|
||||||
|
|
||||||
# TODO eventually we want to remove this, when reports become fully asynchronous
|
|
||||||
# The UI should be retrieving the report elsewhere via the download_report() method call
|
|
||||||
return Response(
|
return Response(
|
||||||
stream_with_context(
|
stream_with_context(
|
||||||
generate_notifications_csv(
|
generate_notifications_csv(
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ from app.main.views.verify import activate_user
|
|||||||
from app.models.user import User
|
from app.models.user import User
|
||||||
from app.utils import hide_from_search_engines
|
from app.utils import hide_from_search_engines
|
||||||
from app.utils.login import get_id_token, is_safe_redirect_url
|
from app.utils.login import get_id_token, is_safe_redirect_url
|
||||||
from app.utils.time import is_less_than_days_ago
|
|
||||||
|
# from app.utils.time import is_less_than_days_ago
|
||||||
from app.utils.user import is_gov_user
|
from app.utils.user import is_gov_user
|
||||||
from notifications_utils.url_safe_token import generate_token
|
from notifications_utils.url_safe_token import generate_token
|
||||||
|
|
||||||
@@ -108,11 +109,15 @@ def _do_login_dot_gov(): # $ pragma: no cover
|
|||||||
)
|
)
|
||||||
raise Exception(f"Could not login with login.gov {login_gov_error}")
|
raise Exception(f"Could not login with login.gov {login_gov_error}")
|
||||||
elif code and state:
|
elif code and state:
|
||||||
state_key = f"login-state-{unquote(state)}"
|
verify_key = f"login-verify_email-{unquote(state)}"
|
||||||
stored_state = unquote(redis_client.get(state_key).decode("utf8"))
|
verify_path = bool(redis_client.get(verify_key))
|
||||||
if state != stored_state:
|
|
||||||
current_app.logger.error(f"State Error: {state} != {stored_state}")
|
if not verify_path:
|
||||||
abort(403)
|
state_key = f"login-state-{unquote(state)}"
|
||||||
|
stored_state = unquote(redis_client.get(state_key).decode("utf8"))
|
||||||
|
if state != stored_state:
|
||||||
|
current_app.logger.error(f"State Error: {state} != {stored_state}")
|
||||||
|
abort(403)
|
||||||
|
|
||||||
# activate the user
|
# activate the user
|
||||||
try:
|
try:
|
||||||
@@ -130,12 +135,17 @@ def _do_login_dot_gov(): # $ pragma: no cover
|
|||||||
f"Retrieved user {user['id']} from db #notify-admin-1505"
|
f"Retrieved user {user['id']} from db #notify-admin-1505"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check if the email needs to be revalidated
|
# Temporary disabling of this until we figure out what is happening.
|
||||||
is_fresh_email = is_less_than_days_ago(
|
# # Check if the email needs to be revalidated
|
||||||
user["email_access_validated_at"], 90
|
# is_fresh_email = is_less_than_days_ago(
|
||||||
)
|
# user["email_access_validated_at"], 90
|
||||||
if not is_fresh_email:
|
# )
|
||||||
return verify_email(user, redirect_url)
|
# if not is_fresh_email:
|
||||||
|
# # send email verify
|
||||||
|
# ttl = 24 * 60 * 60
|
||||||
|
# verify_key = f"login-verify_email-{unquote(state)}"
|
||||||
|
# redis_client.set(verify_key, state, ex=ttl)
|
||||||
|
# return verify_email(user, redirect_url)
|
||||||
|
|
||||||
usr = User.from_email_address(user["email_address"])
|
usr = User.from_email_address(user["email_address"])
|
||||||
current_app.logger.info(f"activating user {usr.id} #notify-admin-1505")
|
current_app.logger.info(f"activating user {usr.id} #notify-admin-1505")
|
||||||
@@ -209,17 +219,19 @@ def sign_in(): # pragma: no cover
|
|||||||
return redirect(redirect_url)
|
return redirect(redirect_url)
|
||||||
return redirect(url_for("main.show_accounts_or_dashboard"))
|
return redirect(url_for("main.show_accounts_or_dashboard"))
|
||||||
|
|
||||||
|
ttl = 24 * 60 * 60
|
||||||
|
|
||||||
state = generate_token(
|
state = generate_token(
|
||||||
str(request.remote_addr),
|
str(request.remote_addr),
|
||||||
current_app.config["SECRET_KEY"],
|
current_app.config["SECRET_KEY"],
|
||||||
current_app.config["DANGEROUS_SALT"],
|
current_app.config["DANGEROUS_SALT"],
|
||||||
)
|
)
|
||||||
state_key = f"login-state-{unquote(state)}"
|
state_key = f"login-state-{unquote(state)}"
|
||||||
redis_client.set(state_key, state)
|
redis_client.set(state_key, state, ex=ttl)
|
||||||
|
|
||||||
nonce = secrets.token_urlsafe()
|
nonce = secrets.token_urlsafe()
|
||||||
nonce_key = f"login-nonce-{unquote(nonce)}"
|
nonce_key = f"login-nonce-{unquote(nonce)}"
|
||||||
redis_client.set(nonce_key, nonce)
|
redis_client.set(nonce_key, nonce, ex=ttl)
|
||||||
|
|
||||||
url = os.getenv("LOGIN_DOT_GOV_INITIAL_SIGNIN_URL")
|
url = os.getenv("LOGIN_DOT_GOV_INITIAL_SIGNIN_URL")
|
||||||
# handle unit tests
|
# handle unit tests
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ class InviteApiClient(NotifyAdminAPIClient):
|
|||||||
}
|
}
|
||||||
data = _attach_current_user(data)
|
data = _attach_current_user(data)
|
||||||
|
|
||||||
|
ttl = 24 * 60 * 60
|
||||||
|
|
||||||
# make and store the state
|
# make and store the state
|
||||||
state = generate_token(
|
state = generate_token(
|
||||||
str(request.remote_addr),
|
str(request.remote_addr),
|
||||||
@@ -48,12 +50,12 @@ class InviteApiClient(NotifyAdminAPIClient):
|
|||||||
current_app.config["DANGEROUS_SALT"],
|
current_app.config["DANGEROUS_SALT"],
|
||||||
)
|
)
|
||||||
state_key = f"login-state-{unquote(state)}"
|
state_key = f"login-state-{unquote(state)}"
|
||||||
redis_client.set(state_key, state)
|
redis_client.set(state_key, state, ex=ttl)
|
||||||
|
|
||||||
# make and store the nonce
|
# make and store the nonce
|
||||||
nonce = secrets.token_urlsafe()
|
nonce = secrets.token_urlsafe()
|
||||||
nonce_key = f"login-nonce-{unquote(nonce)}"
|
nonce_key = f"login-nonce-{unquote(nonce)}"
|
||||||
redis_client.set(nonce_key, nonce) # save the nonce to redis.
|
redis_client.set(nonce_key, nonce, ex=ttl) # save the nonce to redis.
|
||||||
|
|
||||||
data["nonce"] = nonce # This is passed to api for the invite url.
|
data["nonce"] = nonce # This is passed to api for the invite url.
|
||||||
data["state"] = state # This is passed to api for the invite url.
|
data["state"] = state # This is passed to api for the invite url.
|
||||||
@@ -64,7 +66,7 @@ class InviteApiClient(NotifyAdminAPIClient):
|
|||||||
invite_data_key = f"invitedata-{unquote(state)}"
|
invite_data_key = f"invitedata-{unquote(state)}"
|
||||||
redis_invite_data = resp["invite"]
|
redis_invite_data = resp["invite"]
|
||||||
redis_invite_data = json.dumps(redis_invite_data)
|
redis_invite_data = json.dumps(redis_invite_data)
|
||||||
redis_client.set(invite_data_key, redis_invite_data)
|
redis_client.set(invite_data_key, redis_invite_data, ex=ttl)
|
||||||
|
|
||||||
return resp_data
|
return resp_data
|
||||||
|
|
||||||
@@ -97,6 +99,8 @@ class InviteApiClient(NotifyAdminAPIClient):
|
|||||||
self.post(url=f"/service/{service_id}/invite/{invited_user_id}", data=data)
|
self.post(url=f"/service/{service_id}/invite/{invited_user_id}", data=data)
|
||||||
|
|
||||||
def resend_invite(self, service_id, invited_user_id):
|
def resend_invite(self, service_id, invited_user_id):
|
||||||
|
ttl = 24 * 60 * 60
|
||||||
|
|
||||||
# make and store the state
|
# make and store the state
|
||||||
state = generate_token(
|
state = generate_token(
|
||||||
str(request.remote_addr),
|
str(request.remote_addr),
|
||||||
@@ -104,12 +108,12 @@ class InviteApiClient(NotifyAdminAPIClient):
|
|||||||
current_app.config["DANGEROUS_SALT"],
|
current_app.config["DANGEROUS_SALT"],
|
||||||
)
|
)
|
||||||
state_key = f"login-state-{unquote(state)}"
|
state_key = f"login-state-{unquote(state)}"
|
||||||
redis_client.set(state_key, state)
|
redis_client.set(state_key, state, ex=ttl)
|
||||||
|
|
||||||
# make and store the nonce
|
# make and store the nonce
|
||||||
nonce = secrets.token_urlsafe()
|
nonce = secrets.token_urlsafe()
|
||||||
nonce_key = f"login-nonce-{unquote(nonce)}"
|
nonce_key = f"login-nonce-{unquote(nonce)}"
|
||||||
redis_client.set(nonce_key, nonce)
|
redis_client.set(nonce_key, nonce, ex=ttl)
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
"nonce": nonce,
|
"nonce": nonce,
|
||||||
@@ -122,7 +126,7 @@ class InviteApiClient(NotifyAdminAPIClient):
|
|||||||
invite_data_key = f"invitedata-{unquote(state)}"
|
invite_data_key = f"invitedata-{unquote(state)}"
|
||||||
redis_invite_data = resp["invite"]
|
redis_invite_data = resp["invite"]
|
||||||
redis_invite_data = json.dumps(redis_invite_data)
|
redis_invite_data = json.dumps(redis_invite_data)
|
||||||
redis_client.set(invite_data_key, redis_invite_data)
|
redis_client.set(invite_data_key, redis_invite_data, ex=ttl)
|
||||||
|
|
||||||
@cache.delete("service-{service_id}")
|
@cache.delete("service-{service_id}")
|
||||||
@cache.delete("user-{invited_user_id}")
|
@cache.delete("user-{invited_user_id}")
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ class UserApiClient(NotifyAdminAPIClient):
|
|||||||
data["next"] = next_string
|
data["next"] = next_string
|
||||||
if code_type == "email":
|
if code_type == "email":
|
||||||
data["email_auth_link_host"] = self.admin_url
|
data["email_auth_link_host"] = self.admin_url
|
||||||
endpoint = "/user/{0}/{1}-code".format(user_id, code_type)
|
endpoint = f"/user/{user_id}/{code_type}-code"
|
||||||
current_app.logger.warn(hilite(f"Sending verify_code {code_type} to {user_id}"))
|
current_app.logger.warn(hilite(f"Sending verify_code {code_type} to {user_id}"))
|
||||||
self.post(endpoint, data=data)
|
self.post(endpoint, data=data)
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
import boto3
|
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
|
||||||
from app.s3_client import (
|
from app.s3_client import (
|
||||||
AWS_CLIENT_CONFIG,
|
|
||||||
get_s3_contents,
|
get_s3_contents,
|
||||||
get_s3_metadata,
|
get_s3_metadata,
|
||||||
get_s3_object,
|
get_s3_object,
|
||||||
@@ -65,70 +63,3 @@ def set_metadata_on_csv_upload(service_id, upload_id, **kwargs):
|
|||||||
|
|
||||||
def get_csv_metadata(service_id, upload_id):
|
def get_csv_metadata(service_id, upload_id):
|
||||||
return get_s3_metadata(get_csv_upload(service_id, upload_id))
|
return get_s3_metadata(get_csv_upload(service_id, upload_id))
|
||||||
|
|
||||||
|
|
||||||
def report_upload(file_location, report_content):
|
|
||||||
bucket_name = current_app.config["CSV_UPLOAD_BUCKET"]["bucket"]
|
|
||||||
access_key = current_app.config["CSV_UPLOAD_BUCKET"]["access_key_id"]
|
|
||||||
secret_key = current_app.config["CSV_UPLOAD_BUCKET"]["secret_access_key"]
|
|
||||||
region = current_app.config["CSV_UPLOAD_BUCKET"]["region"]
|
|
||||||
|
|
||||||
utils_s3upload(
|
|
||||||
filedata=report_content,
|
|
||||||
region=region,
|
|
||||||
bucket_name=bucket_name,
|
|
||||||
file_location=file_location,
|
|
||||||
access_key=access_key,
|
|
||||||
secret_key=secret_key,
|
|
||||||
)
|
|
||||||
current_app.logger.info(f"Succcessfully uploaded report to {file_location}")
|
|
||||||
|
|
||||||
|
|
||||||
def report_download(file_location):
|
|
||||||
current_app.logger.info(f"Downloading report from {file_location}")
|
|
||||||
bucket_name = current_app.config["CSV_UPLOAD_BUCKET"]["bucket"]
|
|
||||||
access_key = current_app.config["CSV_UPLOAD_BUCKET"]["access_key_id"]
|
|
||||||
secret_key = current_app.config["CSV_UPLOAD_BUCKET"]["secret_access_key"]
|
|
||||||
region = current_app.config["CSV_UPLOAD_BUCKET"]["region"]
|
|
||||||
|
|
||||||
return get_s3_contents(
|
|
||||||
get_s3_object(bucket_name, file_location, access_key, secret_key, region)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def delete_report(file_location):
|
|
||||||
current_app.logger.info(f"Deleting report from {file_location}")
|
|
||||||
bucket_name = current_app.config["CSV_UPLOAD_BUCKET"]["bucket"]
|
|
||||||
access_key = current_app.config["CSV_UPLOAD_BUCKET"]["access_key_id"]
|
|
||||||
secret_key = current_app.config["CSV_UPLOAD_BUCKET"]["secret_access_key"]
|
|
||||||
region = current_app.config["CSV_UPLOAD_BUCKET"]["region"]
|
|
||||||
|
|
||||||
obj = get_s3_object(bucket_name, file_location, access_key, secret_key, region)
|
|
||||||
if obj is None:
|
|
||||||
return None
|
|
||||||
return obj.delete()
|
|
||||||
|
|
||||||
|
|
||||||
def get_downloadable_reports(user_id, service_id):
|
|
||||||
prefix = f"reports/{service_id}/{user_id}/"
|
|
||||||
bucket_name = current_app.config["CSV_UPLOAD_BUCKET"]["bucket"]
|
|
||||||
access_key = current_app.config["CSV_UPLOAD_BUCKET"]["access_key_id"]
|
|
||||||
secret_key = current_app.config["CSV_UPLOAD_BUCKET"]["secret_access_key"]
|
|
||||||
region = current_app.config["CSV_UPLOAD_BUCKET"]["region"]
|
|
||||||
|
|
||||||
session = boto3.Session(
|
|
||||||
aws_access_key_id=access_key,
|
|
||||||
aws_secret_access_key=secret_key,
|
|
||||||
region_name=region,
|
|
||||||
)
|
|
||||||
s3 = session.client(
|
|
||||||
"s3",
|
|
||||||
config=AWS_CLIENT_CONFIG,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = s3.list_objects_v2(Bucket=bucket_name, Prefix=prefix)
|
|
||||||
object_keys = []
|
|
||||||
if "Contents" in response:
|
|
||||||
for obj in response["Contents"]:
|
|
||||||
object_keys.append(obj["Key"])
|
|
||||||
return object_keys
|
|
||||||
|
|||||||
Reference in New Issue
Block a user