merge from main

This commit is contained in:
Kenneth Kehl
2023-09-19 10:44:19 -07:00
40 changed files with 4813 additions and 130 deletions

View File

@@ -30,9 +30,11 @@ from app.models import (
SMS_TYPE,
)
from app.notifications.process_notifications import persist_notification
from app.notifications.validators import check_service_over_total_message_limit
from app.serialised_models import SerialisedService, SerialisedTemplate
from app.service.utils import service_allowed_to_send_to
from app.utils import DATETIME_FORMAT
from app.v2.errors import TotalRequestsError
@notify_celery.task(name="process-job")
@@ -64,6 +66,9 @@ def process_job(job_id, sender_id=None):
)
return
if __total_sending_limits_for_job_exceeded(service, job, job_id):
return
recipient_csv, template, sender_id = get_recipient_csv_and_template_and_sender_id(
job
)
@@ -145,6 +150,25 @@ def process_row(row, template, job, service, sender_id=None):
return notification_id
def __total_sending_limits_for_job_exceeded(service, job, job_id):
try:
total_sent = check_service_over_total_message_limit(KEY_TYPE_NORMAL, service)
if total_sent + job.notification_count > service.total_message_limit:
raise TotalRequestsError(service.total_message_limit)
else:
return False
except TotalRequestsError:
job.job_status = "sending limits exceeded"
job.processing_finished = datetime.utcnow()
dao_update_job(job)
current_app.logger.error(
"Job {} size {} error. Total sending limits {} exceeded".format(
job_id, job.notification_count, service.message_limit
)
)
return True
@notify_celery.task(bind=True, name="save-sms", max_retries=5, default_retry_delay=300)
def save_sms(self, service_id, notification_id, encrypted_notification, sender_id=None):
notification = encryption.decrypt(encrypted_notification)

View File

@@ -772,12 +772,23 @@ def create_user_jwt(token):
def _update_template(id, name, template_type, content, subject):
template = Template.query.filter_by(id=id).first()
if not template:
template = Template(id=id)
template.service_id = "d6aa2c68-a2d9-4437-ab19-3ae8eb202553"
template.created_by_id = "6af522d0-2915-4e52-83a3-3690455a5fe6"
db.session.add(template)
template.name = name
template.template_type = template_type
template.content = "\n".join(content)
template.subject = subject
history = TemplateHistory.query.filter_by(id=id).first()
if not history:
history = TemplateHistory(id=id)
history.service_id = "d6aa2c68-a2d9-4437-ab19-3ae8eb202553"
history.created_by_id = "6af522d0-2915-4e52-83a3-3690455a5fe6"
history.version = 1
db.session.add(history)
history.name = name
history.template_type = template_type
history.content = "\n".join(content)
@@ -792,3 +803,27 @@ def update_templates():
data = json.load(f)
for d in data:
_update_template(d["id"], d["name"], d["type"], d["content"], d["subject"])
@notify_command(name="create-new-service")
@click.option("-n", "--name", required=True, prompt=True)
@click.option("-l", "--message_limit", required=False, default=40000)
@click.option("-r", "--restricted", required=False, default=False)
@click.option("-e", "--email_from", required=True)
@click.option("-c", "--created_by_id", required=True)
def create_new_service(name, message_limit, restricted, email_from, created_by_id):
data = {
"name": name,
"message_limit": message_limit,
"restricted": restricted,
"email_from": email_from,
"created_by_id": created_by_id,
}
service = Service(**data)
try:
db.session.add(service)
db.session.commit()
except IntegrityError:
print("duplicate service", service.name)
db.session.rollback()

View File

@@ -282,6 +282,8 @@ class Config(object):
FREE_SMS_TIER_FRAGMENT_COUNT = 250000
TOTAL_MESSAGE_LIMIT = 250000
DAILY_MESSAGE_LIMIT = notifications_utils.DAILY_MESSAGE_LIMIT
HIGH_VOLUME_SERVICE = json.loads(getenv("HIGH_VOLUME_SERVICE", "[]"))

View File

@@ -4,20 +4,20 @@
"name": "Example text message template",
"type": "sms",
"subject": "",
"content": ["Hi, Im trying out U.S. Notify. Today is ((day of week)) and my favorite color is ((color))."]
"content": ["Hi, Im trying out Notify.gov. Today is ((day of week)) and my favorite color is ((color))."]
},
{
"id": "4f46df42-f795-4cc4-83bb-65ca312f49cc",
"name": "Notify invitation email",
"type": "email",
"subject": "((user_name)) has invited you to collaborate on ((service_name)) on U.S. Notify",
"content": ["((user_name)) has invited you to collaborate on ((service_name)) on U.S. Notify.",
"subject": "((user_name)) has invited you to collaborate on ((service_name)) on Notify.gov",
"content": ["((user_name)) has invited you to collaborate on ((service_name)) on Notify.gov.",
"",
"",
"U.S. Notify makes it easy to keep people updated by helping you send text messages and emails.",
"Notify.gov makes it easy to keep people updated by helping you send text messages and emails.",
"",
"",
"Click this link to create an account on U.S. Notify:",
"Click this link to create an account on Notify.gov:",
"",
"((url))",
"",
@@ -29,17 +29,17 @@
"name": "Notify SMS verify code",
"type": "sms",
"subject": "",
"content": ["((verify_code)) is your U.S. Notify authentication code"]
"content": ["((verify_code)) is your Notify.gov authentication code"]
},
{
"id": "474e9242-823b-4f99-813d-ed392e7f1201",
"name": "Notify password reset email",
"type": "email",
"subject": "Reset your U.S. Notify password",
"subject": "Reset your Notify.gov password",
"content": ["Hi ((user_name)),",
"",
"",
"We received a request to reset your password on U.S. Notify.",
"We received a request to reset your password on Notify.gov.",
"",
"",
"If you didnt request this email, you can ignore it your password has not been changed.",
@@ -51,11 +51,11 @@
"id": "299726d2-dba6-42b8-8209-30e1d66ea164",
"name": "Notify email verify code",
"type": "email",
"subject": "Sign in to U.S. Notify",
"subject": "Sign in to Notify.gov",
"content": ["Hi ((name)),",
"",
"",
"To sign in to U.S. Notify please open this link:",
"To sign in to Notify.gov please open this link:",
"",
"",
"((url))"]
@@ -64,21 +64,21 @@
"id": "ece42649-22a8-4d06-b87f-d52d5d3f0a27",
"name": "Notify email verification code",
"type": "email",
"subject": "Confirm U.S. Notify registration",
"subject": "Confirm Notify.gov registration",
"content": ["Hi ((name)),",
"",
"",
"To complete your registration for U.S. Notify please click the link below",
"To complete your registration for Notify.gov please click the link below",
"",
"",
"((url))"]
},
{
"id": "0880fbb1-a0c6-46f0-9a8e-36c986381ceb",
"name": "Your U.S. Notify account",
"name": "Your Notify.gov account",
"type": "email",
"subject": "Your U.S. Notify account",
"content": ["You already have a U.S. Notify account with this email address.",
"subject": "Your Notify.gov account",
"content": ["You already have a Notify.gov account with this email address.",
"",
"",
"Sign in here: ((signin_url))",
@@ -89,20 +89,20 @@
"",
"",
"",
"If you didnt try to register for a U.S. Notify account recently, please let us know here: ((feedback_url))"]
"If you didnt try to register for a Notify.gov account recently, please let us know here: ((feedback_url))"]
},
{
"id": "eb4d9930-87ab-4aef-9bce-786762687884",
"name": "Confirm new email address",
"type": "email",
"subject": "Confirm your email address for U.S. Notify",
"subject": "Confirm your email address for Notify.gov",
"content": ["Hi ((name)),","","","Click this link to confirm your new email address:",
"",
"",
"((url))",
"",
"",
"If you didnt try to change the email address for your U.S. Notify account, let us know here:",
"If you didnt try to change the email address for your Notify.gov account, let us know here:",
"",
"",
"((feedback_url))"]
@@ -111,11 +111,11 @@
"id": "618185c6-3636-49cd-b7d2-6f6f5eb3bdde",
"name": "Automated \"Youre now live\" message",
"type": "email",
"subject": "((service name)) is now live on U.S. Notify",
"subject": "((service name)) is now live on Notify.gov",
"content": ["Hi ((name)),",
"",
"",
"((service name)) is now live on U.S. Notify.",
"((service name)) is now live on Notify.gov.",
"",
"",
"You can send up to ((message limit)) messages per day.",
@@ -158,87 +158,87 @@
"",
"Thanks",
"",
"U.S. Notify team"]
"Notify.gov team"]
},
{
"id": "203566f0-d835-47c5-aa06-932439c86573",
"name": "Notify organization invitation email",
"type": "email",
"subject": "((user_name)) has invited you to collaborate on ((organization_name)) on U.S. Notify",
"content": ["((user_name)) has invited you to collaborate on ((organization_name)) on U.S. Notify.","","","U.S. Notify makes it easy to keep people updated by helping you send text messages and emails.","","","Open this link to create an account on U.S. Notify:","","((url))","","","This invitation will stop working at midnight tomorrow. This is to keep ((organization_name)) secure."]
"subject": "((user_name)) has invited you to collaborate on ((organization_name)) on Notify.gov",
"content": ["((user_name)) has invited you to collaborate on ((organization_name)) on Notify.gov.","","","Notify.gov makes it easy to keep people updated by helping you send text messages and emails.","","","Open this link to create an account on Notify.gov:","","((url))","","","This invitation will stop working at midnight tomorrow. This is to keep ((organization_name)) secure."]
},
{
"id": "c73f1d71-4049-46d5-a647-d013bdeca3f0",
"name": "Email address changed by service manager",
"type": "email",
"subject": "Your U.S. Notify email address has changed",
"content": ["Dear ((name)),","","","((servicemanagername)) changed your Notify account email address to:","","","((email address))","","","Youll need to use this email address next time you sign in.","","","Thanks","","","U.S. Notify team"]
"subject": "Your Notify.gov email address has changed",
"content": ["Dear ((name)),","","","((servicemanagername)) changed your Notify account email address to:","","","((email address))","","","Youll need to use this email address next time you sign in.","","","Thanks","","","Notify.gov team"]
},
{
"id": "8a31520f-4751-4789-8ea1-fe54496725eb",
"name": "Phone number changed by service manager",
"type": "sms",
"subject": "",
"content": ["Your mobile number was changed by ((servicemanagername)). Next time you sign in, your U.S. Notify authentication code will be sent to this phone."]
"content": ["Your mobile number was changed by ((servicemanagername)). Next time you sign in, your Notify.gov authentication code will be sent to this phone."]
},
{
"id": "a42f1d17-9404-46d5-a647-d013bdfca3e1",
"name": "Verify email reply-to address for a service",
"type": "email",
"subject": "Your U.S. Notify reply-to email address",
"content": ["Hi,","","","This address has been provided as a reply-to email address for a U.S. Notify account.","","Any replies from users to emails they receive through U.S. Notify will come back to this email address.","","","This is just a quick check to make sure the address is valid.","","","No need to reply.","","","Thanks","","","U.S. Notify team"]
"subject": "Your Notify.gov reply-to email address",
"content": ["Hi,","","","This address has been provided as a reply-to email address for a Notify.gov account.","","Any replies from users to emails they receive through Notify.gov will come back to this email address.","","","This is just a quick check to make sure the address is valid.","","","No need to reply.","","","Thanks","","","Notify.gov team"]
},
{
"id": "4fd2e43c-309b-4e50-8fb8-1955852d9d71",
"name": "MOU Signed By Receipt",
"type": "email",
"subject": "Youve accepted the U.S. Notify data sharing and financial agreement",
"subject": "Youve accepted the Notify.gov data sharing and financial agreement",
"content": [
"Hi ((signed_by_name)),",
"",
"((org_name)) has accepted the U.S. Notify data sharing and financial agreement. ",
"((org_name)) has accepted the Notify.gov data sharing and financial agreement. ",
"",
"If you need another copy of the agreement you can download it here: ((mou_link))",
"",
"",
"Thanks,",
"U.S. Notify team"
"Notify.gov team"
]
},
{
"id": "c20206d5-bf03-4002-9a90-37d5032d9e84",
"name": "MOU Signed On Behalf Of Receipt - Signed by",
"type": "email",
"subject": "Youve accepted the U.S. Notify data sharing and financial agreement",
"subject": "Youve accepted the Notify.gov data sharing and financial agreement",
"content": [
"Hi ((signed_by_name)),",
"",
"((org_name)) has accepted the U.S. Notify data sharing and financial agreement. Weve emailed ((on_behalf_of_name)) to let them know too.",
"((org_name)) has accepted the Notify.gov data sharing and financial agreement. Weve emailed ((on_behalf_of_name)) to let them know too.",
"",
"If you need another copy of the agreement you can download it here: ((mou_link))",
"",
"",
"Thanks,",
"U.S. Notify team"
"Notify.gov team"
]
},
{
"id": "522b6657-5ca5-4368-a294-6b527703bd0b",
"name": "MOU Signed On Behalf Of Receipt - On Behalf Of",
"type": "email",
"subject": "((org_name)) has accepted the U.S. Notify data sharing and financial agreement",
"subject": "((org_name)) has accepted the Notify.gov data sharing and financial agreement",
"content": [
"Hi ((on_behalf_of_name)),",
"",
"((signed_by_name)) has accepted the U.S. Notify data sharing and financial agreement on your behalf, for ((org_name)).",
"((signed_by_name)) has accepted the Notify.gov data sharing and financial agreement on your behalf, for ((org_name)).",
"",
"U.S. Notify lets teams in the public sector send text messages and emails. Its built and run by a team in the TTS Public Benefits Studio (part of GSA).",
"Notify.gov lets teams in the public sector send text messages and emails. Its built and run by a team in the TTS Public Benefits Studio (part of GSA).",
"",
"If you need another copy of the agreement you can download it here: ((mou_link))",
"",
"",
"Thanks,",
"U.S. Notify team"
"Notify.gov team"
]
},
{
@@ -268,7 +268,7 @@
"",
"Thanks",
"",
"U.S. Notify team"
"Notify.gov team"
]
}
]

View File

@@ -487,6 +487,9 @@ class Service(db.Model, Versioned):
db.Boolean, index=False, unique=False, nullable=False, default=True
)
message_limit = db.Column(db.BigInteger, index=False, unique=False, nullable=False)
total_message_limit = db.Column(
db.BigInteger, index=False, unique=False, nullable=False
)
restricted = db.Column(db.Boolean, index=False, unique=False, nullable=False)
email_from = db.Column(db.Text, index=False, unique=True, nullable=False)
created_by_id = db.Column(

View File

@@ -81,7 +81,7 @@ def persist_notification(
reply_to_text=None,
billable_units=None,
document_download_count=None,
updated_at=None
updated_at=None,
):
current_app.logger.info("Persisting notification")
@@ -150,7 +150,7 @@ def persist_notification(
current_app.logger.info("Redis total limit cache key does exist")
redis_store.incr(total_key)
current_app.logger.info(
"Redis total limit cache key has been incremented"
f"Redis total limit cache key has been incremented to {redis_store.get(total_key)}"
)
current_app.logger.info(
"{} {} created at {}".format(

View File

@@ -3,6 +3,7 @@ from notifications_utils import SMS_CHAR_COUNT_LIMIT
from notifications_utils.clients.redis import (
daily_total_cache_key,
rate_limit_cache_key,
total_limit_cache_key,
)
from notifications_utils.recipients import (
get_international_phone_info,
@@ -44,6 +45,27 @@ def check_service_over_api_rate_limit(service, api_key):
raise RateLimitError(rate_limit, interval, api_key.key_type)
def check_service_over_total_message_limit(key_type, service):
if key_type == KEY_TYPE_TEST or not current_app.config["REDIS_ENABLED"]:
return 0
cache_key = total_limit_cache_key(service.id)
service_stats = redis_store.get(cache_key)
if service_stats is None:
# first message of the day, set the cache to 0 and the expiry to 24 hours
service_stats = 0
redis_store.set(cache_key, service_stats, ex=86400)
return service_stats
if int(service_stats) >= service.total_message_limit:
current_app.logger.warning(
"service {} has been rate limited for total use sent {} limit {}".format(
service.id, int(service_stats), service.total_message_limit
)
)
raise TotalRequestsError(service.total_message_limit)
return int(service_stats)
def check_application_over_retention_limit(key_type, service):
if key_type == KEY_TYPE_TEST or not current_app.config["REDIS_ENABLED"]:
return 0

View File

@@ -327,6 +327,7 @@ class DetailedServiceSchema(BaseSchema):
"inbound_sms",
"jobs",
"message_limit",
"total_message_limit",
"permissions",
"rate_limit",
"reply_to_email_addresses",
@@ -735,6 +736,7 @@ class ServiceHistorySchema(ma.Schema):
updated_at = FlexibleDateTime()
active = fields.Boolean()
message_limit = fields.Integer()
total_message_limit = fields.Integer()
restricted = fields.Boolean()
email_from = fields.String()
created_by_id = fields.UUID()

View File

@@ -79,6 +79,7 @@ class SerialisedService(SerialisedModel):
"contact_link",
"email_from",
"message_limit",
"total_message_limit",
"permissions",
"rate_limit",
"restricted",

View File

@@ -216,6 +216,8 @@ def create_service():
raise InvalidRequest(errors, status_code=400)
data.pop("service_domain", None)
data["total_message_limit"] = current_app.config["TOTAL_MESSAGE_LIMIT"]
# validate json with marshmallow
service_schema.load(data)

View File

@@ -12,6 +12,7 @@ from app.notifications.process_notifications import (
send_notification_to_queue,
)
from app.notifications.validators import (
check_service_over_total_message_limit,
validate_and_format_recipient,
validate_template,
)
@@ -44,6 +45,8 @@ def send_one_off_notification(service_id, post_data):
validate_template(template.id, personalisation, service, template.template_type)
check_service_over_total_message_limit(KEY_TYPE_NORMAL, service)
validate_and_format_recipient(
send_to=post_data["to"],
key_type=KEY_TYPE_NORMAL,