Merge branch 'main' into 1677-user-story-view-table-of-all-jobs-sent-with-my-service

This commit is contained in:
Beverly Nguyen
2024-07-24 13:44:40 -07:00
58 changed files with 2653 additions and 1548 deletions

View File

@@ -369,11 +369,23 @@ def make_session_permanent():
def create_beta_url(url):
url_created = urlparse(url)
url_list = list(url_created)
url_list[1] = "beta.notify.gov"
url_for_redirect = urlunparse(url_list)
return url_for_redirect
url_created = None
try:
url_created = urlparse(url)
url_list = list(url_created)
url_list[1] = "beta.notify.gov"
url_for_redirect = urlunparse(url_list)
return url_for_redirect
except ValueError:
# This might be happening due to IPv6, see issue # 1395.
# If we see "'RequestContext' object has no attribute 'service'" in the logs
# we can search around that timestamp and find this output, hopefully.
# It may be sufficient to just catch and log, and prevent the stack trace from being in the logs
# but we need to confirm the root cause first.
current_app.logger.error(
f"create_beta_url orig_url: {url} \
url_created = {str(url_created)} url_for_redirect {str(url_for_redirect)}"
)
def redirect_notify_to_beta():
@@ -381,6 +393,7 @@ def redirect_notify_to_beta():
current_app.config["NOTIFY_ENVIRONMENT"] == "production"
and "beta.notify.gov" not in request.url
):
# TODO add debug here to trace what is going on with the URL for the 'RequestContext' error
url_to_beta = create_beta_url(request.url)
return redirect(url_to_beta, 302)

View File

@@ -36,7 +36,7 @@ from notifications_utils.recipients import format_phone_number_human_readable
@socketio.on("fetch_daily_stats")
def handle_fetch_daily_stats():
service_id = session.get('service_id')
service_id = session.get("service_id")
if service_id:
date_range = get_stats_date_range()
daily_stats = service_api_client.get_service_notification_statistics_by_day(
@@ -105,6 +105,8 @@ def service_dashboard(service_id):
".view_job", service_id=current_service.id, job_id=job["id"]
),
"created_at": job["created_at"],
"processing_finished": job["processing_finished"],
"processing_started": job["processing_started"],
"notification_count": job["notification_count"],
"created_by": job["created_by"],
"notifications": aggregate_notifications_by_job.get(job["id"], []),

View File

@@ -312,7 +312,7 @@ def get_status_filters(service, message_type, statistics):
filters = [
# key, label, option
("requested", "total", "sending,delivered,failed"),
("pending", "pending", "pending"),
("pending", "pending", "sending,pending"),
("delivered", "delivered", "delivered"),
("failed", "failed", "failed"),
]

View File

@@ -1053,8 +1053,13 @@ def get_email_reply_to_address_from_session():
def get_sms_sender_from_session():
if session.get("sender_id"):
return current_service.get_sms_sender(session["sender_id"])["sms_sender"]
sender_id = session.get("sender_id")
if sender_id:
sms_sender = current_service.get_sms_sender(session["sender_id"])["sms_sender"]
current_app.logger.info(f"SMS Sender ({sender_id}) #: {sms_sender}")
return sms_sender
else:
current_app.logger.error("No SMS Sender!!!!!!")
def get_spreadsheet_column_headings_from_template(template):

View File

@@ -1,7 +1,7 @@
import os
import requests
from flask import current_app, redirect, url_for
from flask import current_app, redirect, session, url_for
from flask_login import current_user
from app.main import main
@@ -25,12 +25,16 @@ def _sign_out_at_login_dot_gov():
@main.route("/sign-out", methods=(["GET", "POST"]))
def sign_out():
# An AnonymousUser does not have an id
current_app.logger.info("HIT THE REGULAR SIGN OUT")
if current_user.is_authenticated:
# TODO This doesn't work yet, due to problems above.
current_user.deactivate()
session.clear()
current_user.sign_out()
session.permanent = False
login_dot_gov_logout_url = os.getenv("LOGIN_DOT_GOV_LOGOUT_URL")
if login_dot_gov_logout_url:
current_app.config["SESSION_PERMANENT"] = False
return redirect(login_dot_gov_logout_url)
return redirect(url_for("main.index"))

View File

@@ -79,5 +79,4 @@ def activate_user(user_id):
else:
activated_user = user.activate()
activated_user.login()
return redirect(url_for("main.add_service", first="first"))

View File

@@ -24,11 +24,15 @@ from app.utils.user_permissions import (
def _get_service_id_from_view_args():
return str(request.view_args.get("service_id", "")) or None
if request and request.view_args:
return str(request.view_args.get("service_id", ""))
return None
def _get_org_id_from_view_args():
return str(request.view_args.get("org_id", "")) or None
if request and request.view_args:
return str(request.view_args.get("org_id", ""))
return None
class User(JSONModel, UserMixin):
@@ -147,6 +151,13 @@ class User(JSONModel, UserMixin):
else:
return self
def deactivate(self):
if self.is_active:
user_data = user_api_client.deactivate_user(self.id)
return self.__class__(user_data["data"])
else:
return self
def login(self):
login_user(self)
session["user_id"] = self.id
@@ -220,7 +231,9 @@ class User(JSONModel, UserMixin):
if not service_id and not org_id:
# we shouldn't have any pages that require permissions, but don't specify a service or organization.
# use @user_is_platform_admin for platform admin only pages
raise NotImplementedError
# raise NotImplementedError
current_app.logger.warn(f"VIEW ARGS ARE {request.view_args}")
pass
log_msg = f"has_permissions user: {self.id} service: {service_id}"
# platform admins should be able to do most things (except eg send messages, or create api keys)

View File

@@ -1,4 +1,6 @@
from flask import abort, has_request_context, request
import os
from flask import abort, current_app, has_request_context, request
from flask_login import current_user
from notifications_python_client import __version__
from notifications_python_client.base import BaseAPIClient
@@ -54,16 +56,51 @@ class NotifyAdminAPIClient(BaseAPIClient):
):
abort(403)
def check_inactive_user(self, *args):
still_signing_in = False
# TODO clean up and add testing etc.
# We really should be checking for exact matches
# and we only want to check the first arg
for arg in args:
arg = str(arg)
if (
"get-login-gov-user" in arg
or "user/email" in arg
or "/activate" in arg
or "/email-code" in arg
or "/verify/code" in arg
or "/user" in arg
):
still_signing_in = True
# This seems to be a weird edge case that happens intermittently with invites
if str(arg) == "()":
still_signing_in = True
# TODO: Update this once E2E tests are managed by a feature flag or some other main config option.
if os.getenv("NOTIFY_E2E_TEST_EMAIL"):
# allow end-to-end tests to skip check
pass
elif still_signing_in is True:
# we are not full signed in yet
pass
elif not current_user or not current_user.is_active:
current_app.logger.error(f"Unauthorized URL #notify-compliance-46 {args}")
abort(403)
def post(self, *args, **kwargs):
self.check_inactive_service()
self.check_inactive_user(args)
return super().post(*args, **kwargs)
def put(self, *args, **kwargs):
self.check_inactive_service()
self.check_inactive_user()
return super().put(*args, **kwargs)
def delete(self, *args, **kwargs):
self.check_inactive_service()
self.check_inactive_user()
return super().delete(*args, **kwargs)

View File

@@ -217,6 +217,10 @@ class UserApiClient(NotifyAdminAPIClient):
def activate_user(self, user_id):
return self.post("/user/{}/activate".format(user_id), data=None)
@cache.delete("user-{user_id}")
def deactivate_user(self, user_id):
return self.post("/user/{}/deactivate".format(user_id), data=None)
def send_change_email_verification(self, user_id, new_email):
endpoint = "/user/{}/change-email-verification".format(user_id)
data = {"email": new_email}

View File

@@ -58,6 +58,20 @@
</ul>
</div>
</nav>
<section class="usa-identifier__section usa-identifier__section--usagov"
aria-label="Github Repos">
<div class="usa-identifier__container">
<div class="usa-identifier__required-links-item">
Find us on Github:
</div>
<ul>
<li><a href="https://github.com/gsa/notifications-admin" class="usa-identifier__required-link">Notify.gov Admin repo</a></li>
<li><a href="https://github.com/gsa/notifications-api" class="usa-identifier__required-link">Notify.gov API repo</a></li>
</ul>
</div>
</section>
<section class="usa-identifier__section usa-identifier__section--usagov"
aria-label="Government information and services">
<div class="usa-identifier__container">

View File

@@ -14,10 +14,17 @@
{% endif %}
{% if current_service %}
{% set secondaryNavigation = [
{"href": url_for('main.service_settings', service_id=current_service.id), "text": "Settings", "active": secondary_navigation.is_selected('settings')},
{% if current_user.has_permissions('manage_service') %}
{% set secondaryNavigation = [
{"href": url_for('main.service_settings', service_id=current_service.id), "text": "Settings", "active": secondary_navigation.is_selected('settings')},
{"href": url_for('main.sign_out'), "text": "Sign out"}
] %}
{% else %}
{% set secondaryNavigation = [
{"href": url_for('main.sign_out'), "text": "Sign out"}
] %}
] %}
{% endif %}
{% else %}
{% set secondaryNavigation = [{"href": url_for('main.sign_out'), "text": "Sign out"}] %}
{% endif %}
@@ -31,7 +38,7 @@
<div class="logo-img display-flex">
<a href="/">
<span class="usa-sr-only">Notify.gov logo</span>
<img src="{{ (asset_path | default('/static')) + 'images/notify-logo.png' }}" alt="Notify.gov logo" class="usa-flag-logo margin-right-1">
<img src="{{ (asset_path | default('/static')) + 'images/notify-logo.svg' }}" alt="Notify.gov logo" class="usa-flag-logo margin-right-1">
</a>
</div>

View File

@@ -37,7 +37,9 @@
<div class="usa-alert__body">
<h4 class="usa-alert__heading">Your text has been sent</h4>
<p class="usa-alert__text">
{{ job.template_name }} - {{ current_service.name }} was sent on {{ job.created_at|format_datetime_normal }} by {{ job.created_by.name }}
{{ job.template_name }} - {{ current_service.name }} was sent on {% if job.processing_started %}
{{ job.processing_started|format_datetime_table }} {% else %}
{{ job.created_at|format_datetime_table }} {% endif %} by {{ job.created_by.name }}
</p>
</div>
</div>

View File

@@ -41,7 +41,7 @@
<span>Template</span>
</th>
<th scope="col" class="table-field-heading">
<span>Time sent</span>
<span>Job status</span>
</th>
<th scope="col" class="table-field-heading">
<span>Sender</span>
@@ -69,7 +69,8 @@
{{ notification.template.name }}
</td>
<td class="table-field time-sent">
{{ job.created_at | format_datetime_table }}
{{ (job.processing_finished if job.processing_finished else job.processing_started
if job.processing_started else job.created_at)|format_datetime_table }}
</td>
<td class="table-field sender">
{{ notification.created_by.name }}