Use simple string formatting for flash messages.
This addresses an SSTI vulnerability in Flask's `render_template_string` function, which we were using for rendering flash messages. The implementation I'd built was too complicated, so I removed its reliance on Jinja template rendering. Instead, all parts of the flash message should be keys in the translations file. The `flash` wrapper in `atst.utils.flash` is just a thin wrapper over our `translate` function. The `translate` function relies on Python string formatting, which does not evaluate expressions and so isn't vulnerable to SSTI.
This commit is contained in:
parent
7de2f440c6
commit
0731b0519c
@ -1,214 +1,171 @@
|
||||
from flask import flash, render_template_string
|
||||
from flask import flash
|
||||
from atst.utils.localization import translate
|
||||
|
||||
|
||||
MESSAGES = {
|
||||
"portfolio_deleted": {
|
||||
"title_template": "Portfolio has been deleted",
|
||||
"message_template": "Portfolio '{{portfolio_name}}' has been deleted",
|
||||
"category": "success",
|
||||
},
|
||||
"application_created": {
|
||||
"title_template": translate("flash.application.created.title"),
|
||||
"message_template": """
|
||||
{{ "flash.application.created.message" | translate({"application_name": application_name}) }}
|
||||
""",
|
||||
"title": "flash.application.created.title",
|
||||
"message": "flash.application.created.message",
|
||||
"category": "success",
|
||||
},
|
||||
"application_updated": {
|
||||
"title_template": translate("flash.success"),
|
||||
"message_template": """
|
||||
{{ "flash.application.updated" | translate({"application_name": application_name}) }}
|
||||
""",
|
||||
"category": "success",
|
||||
},
|
||||
"application_deleted": {
|
||||
"title_template": translate("flash.success"),
|
||||
"message_template": """
|
||||
{{ "flash.application.deleted" | translate({"application_name": application_name}) }}
|
||||
<a href="#">{{ "common.undo" | translate }}</a>
|
||||
""",
|
||||
"title": "flash.success",
|
||||
"message": "flash.application.updated",
|
||||
"category": "success",
|
||||
},
|
||||
"application_environments_name_error": {
|
||||
"title_template": "",
|
||||
"message_template": """{{ 'flash.application.env_name_error.message' | translate({ 'name': name }) }}""",
|
||||
"title": None,
|
||||
"message": "flash.application.env_name_error.message",
|
||||
"category": "error",
|
||||
},
|
||||
"application_environments_updated": {
|
||||
"title_template": "Application environments updated",
|
||||
"message_template": "Application environments have been updated",
|
||||
"title": "flash.environment.updated.title",
|
||||
"message": "flash.environment.updated.message",
|
||||
"category": "success",
|
||||
},
|
||||
"application_invite_error": {
|
||||
"title_template": "Application invitation error",
|
||||
"message_template": "There was an error processing the invitation for {{ user_name }} from {{ application_name }}",
|
||||
"title": "flash.application_invite.error.title",
|
||||
"message": "flash.application_invite.error.message",
|
||||
"category": "error",
|
||||
},
|
||||
"application_invite_resent": {
|
||||
"title_template": "Application invitation resent",
|
||||
"message_template": "You have successfully resent the invite for {{ user_name }} from {{ application_name }}",
|
||||
"title": "flash.application_invite.resent.title",
|
||||
"message": "flash.application_invite.resent.message",
|
||||
"category": "success",
|
||||
},
|
||||
"application_invite_revoked": {
|
||||
"title_template": "Application invitation revoked",
|
||||
"message_template": "You have successfully revoked the invite for {{ user_name }} from {{ application_name }}",
|
||||
"title": "flash.application_invite.revoked.title",
|
||||
"message": "flash.application_invite.revoked.message",
|
||||
"category": "success",
|
||||
},
|
||||
"application_member_removed": {
|
||||
"title_template": "Team member removed from application",
|
||||
"message_template": "You have successfully deleted {{ user_name }} from {{ application_name }}",
|
||||
"title": "flash.application_member.removed.title",
|
||||
"message": "flash.application_member.removed.message",
|
||||
"category": "success",
|
||||
},
|
||||
"application_member_update_error": {
|
||||
"title_template": "{{ user_name }} could not be updated",
|
||||
"message_template": "An unexpected problem occurred with your request, please try again. If the problem persists, contact an administrator.",
|
||||
"title": "flash.application_member.update_error.title",
|
||||
"message": "flash.application_member.update_error.message",
|
||||
"category": "error",
|
||||
},
|
||||
"application_member_updated": {
|
||||
"title_template": "Team member updated",
|
||||
"message_template": "You have successfully updated the permissions for {{ user_name }}",
|
||||
"title": "flash.application_member.updated.title",
|
||||
"message": "flash.application_member.updated.message",
|
||||
"category": "success",
|
||||
},
|
||||
"application_name_error": {
|
||||
"title_template": "",
|
||||
"message_template": """{{ 'flash.application.name_error.message' | translate({ 'name': name }) }}""",
|
||||
"title": None,
|
||||
"message": "flash.application.name_error.message",
|
||||
"category": "error",
|
||||
},
|
||||
"ccpo_user_added": {
|
||||
"title_template": translate("flash.success"),
|
||||
"message_template": "You have successfully given {{ user_name }} CCPO permissions.",
|
||||
"title": "flash.success",
|
||||
"message": "flash.ccpo_user.added.message",
|
||||
"category": "success",
|
||||
},
|
||||
"ccpo_user_not_found": {
|
||||
"title_template": translate("ccpo.form.user_not_found_title"),
|
||||
"message_template": translate("ccpo.form.user_not_found_text"),
|
||||
"title": "ccpo.form.user_not_found_title",
|
||||
"message": "ccpo.form.user_not_found_text",
|
||||
"category": "info",
|
||||
},
|
||||
"ccpo_user_removed": {
|
||||
"title_template": translate("flash.success"),
|
||||
"message_template": "You have successfully removed {{ user_name }}'s CCPO permissions.",
|
||||
"title": "flash.success",
|
||||
"message": "flash.ccpo_user.removed.message",
|
||||
"category": "success",
|
||||
},
|
||||
"environment_added": {
|
||||
"title_template": translate("flash.success"),
|
||||
"message_template": """
|
||||
{{ "flash.environment_added" | translate({ "env_name": environment_name }) }}
|
||||
""",
|
||||
"title": "flash.success",
|
||||
"message": "flash.environment_added",
|
||||
"category": "success",
|
||||
},
|
||||
"environment_deleted": {
|
||||
"title_template": "{{ environment_name }} deleted",
|
||||
"message_template": 'The environment "{{ environment_name }}" has been deleted',
|
||||
"title": "flash.environment.deleted.title",
|
||||
"message": "flash.environment.deleted.message",
|
||||
"category": "success",
|
||||
},
|
||||
"form_errors": {
|
||||
"title_template": "There were some errors",
|
||||
"message_template": "<p>Please see below.</p>",
|
||||
"title": "flash.form.errors.title",
|
||||
"message": "flash.form.errors.message",
|
||||
"category": "error",
|
||||
},
|
||||
"insufficient_funds": {
|
||||
"title_template": "Insufficient Funds",
|
||||
"message_template": "",
|
||||
"title": "flash.task_order.insufficient_funds.title",
|
||||
"message": "",
|
||||
"category": "warning",
|
||||
},
|
||||
"logged_out": {
|
||||
"title_template": translate("flash.logged_out"),
|
||||
"message_template": """
|
||||
You've been logged out.
|
||||
""",
|
||||
"title": "flash.logged_out.title",
|
||||
"message": "flash.logged_out.message",
|
||||
"category": "info",
|
||||
},
|
||||
"login_next": {
|
||||
"title_template": translate("flash.login_required_title"),
|
||||
"message_template": translate("flash.login_required_message"),
|
||||
"title": "flash.login_required_title",
|
||||
"message": "flash.login_required_message",
|
||||
"category": "warning",
|
||||
},
|
||||
"new_application_member": {
|
||||
"title_template": """{{ "flash.new_application_member.title" | translate({ "user_name": user_name }) }}""",
|
||||
"message_template": """
|
||||
<p>{{ "flash.new_application_member.message" | translate({ "user_name": user_name }) }}</p>
|
||||
""",
|
||||
"title": "flash.new_application_member.title",
|
||||
"message": "flash.new_application_member.message",
|
||||
"category": "success",
|
||||
},
|
||||
"new_portfolio_member": {
|
||||
"title_template": translate("flash.success"),
|
||||
"message_template": """
|
||||
<p>{{ "flash.new_portfolio_member" | translate({ "user_name": user_name }) }}</p>
|
||||
""",
|
||||
"title": "flash.success",
|
||||
"message": "flash.new_portfolio_member",
|
||||
"category": "success",
|
||||
},
|
||||
"portfolio_member_removed": {
|
||||
"title_template": translate("flash.deleted_member"),
|
||||
"message_template": """
|
||||
{{ "flash.delete_member_success" | translate({ "member_name": member_name }) }}
|
||||
""",
|
||||
"title": "flash.deleted_member",
|
||||
"message": "flash.delete_member_success",
|
||||
"category": "success",
|
||||
},
|
||||
"primary_point_of_contact_changed": {
|
||||
"title_template": translate("flash.new_ppoc_title"),
|
||||
"message_template": """{{ "flash.new_ppoc_message" | translate({ "ppoc_name": ppoc_name }) }}""",
|
||||
"title": "flash.new_ppoc_title",
|
||||
"message": "flash.new_ppoc_message",
|
||||
"category": "success",
|
||||
},
|
||||
"resend_portfolio_invitation": {
|
||||
"title_template": "Invitation resent",
|
||||
"message_template": """
|
||||
<p>Successfully sent a new invitation to {{ user_name }}.</p>
|
||||
""",
|
||||
"title": "flash.portfolio_invite.resent.title",
|
||||
"message": "flash.portfolio_invite.resent.message",
|
||||
"category": "success",
|
||||
},
|
||||
"revoked_portfolio_access": {
|
||||
"title_template": "Removed portfolio access",
|
||||
"message_template": """
|
||||
<p>Portfolio access successfully removed from {{ member_name }}.</p>
|
||||
""",
|
||||
"title": "flash.portfolio_member.revoked.title",
|
||||
"message": "flash.portfolio_member.revoked.message",
|
||||
"category": "success",
|
||||
},
|
||||
"session_expired": {
|
||||
"title_template": "Session Expired",
|
||||
"message_template": """
|
||||
Your session expired due to inactivity. Please log in again to continue.
|
||||
""",
|
||||
"title": "flash.session_expired.title",
|
||||
"message": "flash.session_expired.message",
|
||||
"category": "error",
|
||||
},
|
||||
"task_order_draft": {
|
||||
"title_template": translate("task_orders.form.draft_alert_title"),
|
||||
"message_template": translate("task_orders.form.draft_alert_message"),
|
||||
"title": "task_orders.form.draft_alert_title",
|
||||
"message": "task_orders.form.draft_alert_message",
|
||||
"category": "warning",
|
||||
},
|
||||
"task_order_number_error": {
|
||||
"title_template": "",
|
||||
"message_template": """{{ 'flash.task_order_number_error.message' | translate({ 'to_number': to_number }) }}""",
|
||||
"title": None,
|
||||
"message": "flash.task_order_number_error.message",
|
||||
"category": "error",
|
||||
},
|
||||
"task_order_submitted": {
|
||||
"title_template": "Your Task Order has been uploaded successfully.",
|
||||
"message_template": """
|
||||
Your task order form for {{ task_order.portfolio_name }} has been submitted.
|
||||
""",
|
||||
"category": "success",
|
||||
},
|
||||
"update_portfolio_members": {
|
||||
"title_template": "Success!",
|
||||
"message_template": """
|
||||
<p>You have successfully updated access permissions for members of {{ portfolio.name }}.</p>
|
||||
""",
|
||||
"title": "flash.task_order.submitted.title",
|
||||
"message": "flash.task_order.submitted.message",
|
||||
"category": "success",
|
||||
},
|
||||
"updated_application_team_settings": {
|
||||
"title_template": translate("flash.success"),
|
||||
"message_template": """
|
||||
<p>{{ "flash.updated_application_team_settings" | translate({"application_name": application_name}) }}</p>
|
||||
""",
|
||||
"title": "flash.success",
|
||||
"message": "flash.updated_application_team_settings",
|
||||
"category": "success",
|
||||
},
|
||||
"user_must_complete_profile": {
|
||||
"title_template": "You must complete your profile",
|
||||
"message_template": "<p>Before continuing, you must complete your profile</p>",
|
||||
"title": "flash.user.complete_profile.title",
|
||||
"message": "flash.user.complete_profile.message",
|
||||
"category": "info",
|
||||
},
|
||||
"user_updated": {
|
||||
"title_template": "User information updated.",
|
||||
"message_template": "",
|
||||
"title": "flash.user.updated.title",
|
||||
"message": None,
|
||||
"category": "success",
|
||||
},
|
||||
}
|
||||
@ -216,9 +173,11 @@ MESSAGES = {
|
||||
|
||||
def formatted_flash(message_name, **message_args):
|
||||
config = MESSAGES[message_name]
|
||||
title = render_template_string(config["title_template"], **message_args)
|
||||
message = render_template_string(config["message_template"], **message_args)
|
||||
actions = None
|
||||
if "actions" in config:
|
||||
actions = render_template_string(config["actions"], **message_args)
|
||||
|
||||
title = translate(config["title"], message_args) if config["title"] else None
|
||||
message = translate(config["message"], message_args) if config["message"] else None
|
||||
actions = (
|
||||
translate(config["actions"], message_args) if config.get("actions") else None
|
||||
)
|
||||
|
||||
flash({"title": title, "message": message, "actions": actions}, config["category"])
|
||||
|
@ -118,22 +118,83 @@ flash:
|
||||
message: 'The application name {name} has already been used in this portfolio. Please enter a unique name.'
|
||||
env_name_error:
|
||||
message: 'The environment name {name} has already been used in this application. Please enter a unique name.'
|
||||
application_invite:
|
||||
error:
|
||||
title: Application invitation error
|
||||
message: There was an error processing the invitation for {user_name} from {application_name}
|
||||
resent:
|
||||
title: Application invitation resent
|
||||
message: You have successfully resent the invite for {user_name} from {application_name}
|
||||
revoked:
|
||||
title: Application invitation revoked
|
||||
message: You have successfully revoked the invite for {user_name} from {application_name}
|
||||
application_member:
|
||||
removed:
|
||||
title: Team member removed from application
|
||||
message: You have successfully deleted {user_name} from {application_name}
|
||||
update_error:
|
||||
title: "{user_name} could not be updated"
|
||||
message: An unexpected problem occurred with your request, please try again. If the problem persists, contact an administrator.
|
||||
updated:
|
||||
title: Team member updated
|
||||
message: You have successfully updated the permissions for {user_name}
|
||||
ccpo_user:
|
||||
added:
|
||||
message: You have successfully given {user_name} CCPO permissions.
|
||||
removed:
|
||||
message: You have successfully removed {user_name}'s CCPO permissions.
|
||||
delete_member_success: 'You have successfully deleted {member_name} from the portfolio.'
|
||||
deleted_member: Portfolio member deleted
|
||||
environment_added: 'The environment "{env_name}" has been added to the application.'
|
||||
environment_added: 'The environment "{environment_name}" has been added to the application.'
|
||||
environment:
|
||||
updated:
|
||||
title: Application environments updated
|
||||
message: Application environments have been updated
|
||||
deleted:
|
||||
title: "{environment_name} deleted"
|
||||
message: The environment "{environment_name}" has been deleted
|
||||
form:
|
||||
errors:
|
||||
title: There were some errors
|
||||
message: Please see below.
|
||||
login_required_message: After you log in, you will be redirected to your destination page.
|
||||
login_required_title: Log in required
|
||||
logged_out:
|
||||
title: Logged out
|
||||
message: You've been logged out.
|
||||
new_portfolio_member: 'You have successfully invited {user_name} to the portfolio.'
|
||||
new_ppoc_message: 'You have successfully added {ppoc_name} as the primary point of contact. You are no longer the PPoC.'
|
||||
new_ppoc_title: Primary point of contact updated
|
||||
portfolio_member:
|
||||
revoked:
|
||||
title: Removed portfolio access
|
||||
message: Portfolio access successfully removed from {member_name}.
|
||||
portfolio_invite:
|
||||
resent:
|
||||
title: Invitation resent
|
||||
message: Successfully sent a new invitation to {user_name}.
|
||||
session_expired:
|
||||
title: Session Expired
|
||||
message: Your session expired due to inactivity. Please log in again to continue.
|
||||
success: Success!
|
||||
task_order_number_error:
|
||||
message: 'The TO number has already been entered for a JEDI task order #{to_number}. Please double-check the TO number you are entering. If you believe this is in error, please contact support@cloud.mil.'
|
||||
task_order:
|
||||
insufficient_funds:
|
||||
title: Insufficient Funds
|
||||
submitted:
|
||||
title: Your Task Order has been uploaded successfully.
|
||||
message: Your task order form for {task_order.portfolio_name} has been submitted.
|
||||
new_application_member:
|
||||
title: "{user_name}'s invitation has been sent"
|
||||
message: "{user_name}'s access to this Application is pending until they sign in for the first time."
|
||||
updated_application_team_settings: 'You have updated the {application_name} team settings.'
|
||||
logged_out: Logged out
|
||||
user:
|
||||
complete_profile:
|
||||
title: You must complete your profile
|
||||
message: Before continuing, you must complete your profile.
|
||||
updated:
|
||||
title: User information updated.
|
||||
footer:
|
||||
login: 'Last login:'
|
||||
forms:
|
||||
|
Loading…
x
Reference in New Issue
Block a user