Auditing
Auditing is a crucial aspect of security and compliance for extensions built into the Q2 Online Banking platform. Auditing allows the tracking of events that occur within the system, providing a way to monitor actions, protect key methods, and ensure accountability.
How are audit events used?
Audit events are used to log actions taken by users or the system within the Q2 Online Banking platform. These events can include user logins, transactions, changes to account settings, and other critical events. By capturing these events, we can maintain a record of activities, which is essential for compliance with regulatory requirements and for investigating potential security incidents. Audit events can be used in connection with Q2 Patrol to trigger additional security measures, such as step-up authentication via Event Driven Validation (EDV). They can be connected to CSR assist policies to provide additional control and protections around access for CSRs assisting end users. Audit events can also be used for reporting and analytics, helping institutions understand user behavior and improve their services. Adding events to important methods can provide additional hooks for Audit Action Extensions and EDV Adapters to build additional functionality around.
Implementing Audit Events
Audit events should be implemented on any of your server methods that perform critical actions or handle sensitive data. Any methods you create that modify user data, perform transactions, or access sensitive information should have audit events associated with them. To do this, you will first need to define the audit actions in your extension’s DB Plan. Once defined, you can use the provided Python decorator to easily add auditing to your server-side methods.
Creating New Audit Events
To create a new Audit Actions for your extension, you will define the action in your extension’s db_plan.py file. This is done by adding an entry to self.audit_actions. Each entry defines a new audit action with the action name, action description, and the category of action.
self.audit_actions = [
db_plan.AuditAction("CustomAuditAction", "Custom Audit action for extension name submit request", "SDK"),
...
]
Decorating Methods for Auditing
The @create_audit decorator simplifies the process of auditing events by allowing you to easily log important actions taken in your extensions. By applying this decorator to your server-side methods, you can ensure that audit events are created automatically whenever those methods are invoked.
from q2_sdk.core.http_handlers.tecton_server_handler import Q2TectonServerRequestHandler, create_audit
class AuthorizedUserHandler(Q2TectonServerRequestHandler):
@create_audit(audit_action_name="CustomAuditAction")
async def submit(self):
"""
This route will be only called after an audit event has been created
"""
When using an audit action name, the decorator creates audit details with basic information such as the extension name and routing key. You can also provide an audit details object to include additional context in the audit event. The audit details parameter accepts a string, dictionary, or a lambda that returns a dictionary. If you use a lambda, it will be executed when the audit event is created, allowing you to include dynamic information from your handler in the audit details.
from q2_sdk.core.http_handlers.tecton_client_handler import Q2TectonClientRequestHandler, create_audit
class AuthorizedUserHandler(Q2TectonClientRequestHandler):
@create_audit(
audit_action_name="CustomAuditAction",
audit_details="Example details"
)
async def string_details(self):
"""
"""
@create_audit(
audit_action_name="CustomAuditAction",
audit_details={"key": "value"}
)
async def dict_details(self):
"""
"""
@create_audit(
audit_action_name="CustomAuditAction",
audit_details=lambda self: {"detailItem": self.form_fields.detail_item, "firstName": self.online_user.first_name}
)
async def lambda_details(self):
"""
"""
You may also pass a error handler method to the decorator to handle cases where Q2 Patrol indicates Event Driven Validation (EDV) is required for your audit event and an exception is thrown during the process.
from q2_sdk.core.http_handlers.tecton_server_handler import Q2TectonServerRequestHandler, create_audit
class AuthorizedUserHandler(Q2TectonServerRequestHandler):
def handle_edv_error(self, error):
template = self.get_template(
"error.html.jinja2",
{
"header": "MyExtension: Error",
"message": f'EDV Failure - {error}',
},
)
html = self.get_tecton_form(
"MyExtension",
custom_template=template,
# Hide the submit button as there is no form on this route.
hide_submit_button=True,
)
return html
@create_audit(audit_action_name="CustomAuditAction", error_handler=handle_edv_error)
async def submit(self):
"""
This route will be only called after an audit event has been created
"""
Warning
The error_handler function must be synchronous — it cannot be defined with async or use await.
If you only want to create audits for CSR Assist sessions:
from q2_sdk.core.http_handlers.tecton_server_handler import Q2TectonServerRequestHandler, create_audit
class AuthorizedUserHandler(Q2TectonServerRequestHandler):
@create_audit(audit_action_name="CustomAuditAction", csr_only=True)
async def submit(self):
"""
This route will be only create an audit_event if called during a CsrAssist session
"""
If you would like to treat audit failures as warnings rather than blocks:
from q2_sdk.core.http_handlers.tecton_server_handler import Q2TectonServerRequestHandler, create_audit
class AuthorizedUserHandler(Q2TectonServerRequestHandler):
@create_audit(audit_action_name="CustomAuditAction", soft_failures=True)
async def submit(self):
"""
This route will log a warning instead of fail if a CsrAssist policy would otherwise block
"""
Note
The soft_failures parameter can be overridden by placing {"_overrides": {"enforce_csr_policy_rights": True}} in the DB_CONFIG. This allows an initial soft launch,
but hardening over time without rebuilding and redeploying the code.
Error Handling
Audit actions can fail for reasons outside your extension’s control — a denied session, insufficient CSR rights, or a user abandoning step-up authentication midway through. Providing an error_handler to @create_audit ensures your extension can handle or log these situations appropriately outside of the default SDK handling. The SDK fires it synchronously in two scenarios:
Audit denial — the action is rejected before any MFA prompt (session flagged as suspect, insufficient CSR assist rights, or an unexpected error during audit record creation):
MFAMessage.DENIED—"Action not allowed in this session."MFAMessage.CSR_INSUFFICIENT—"Insufficent CsrAssist rights for audit action: <name>"MFAMessage.EDV_UNKNOWN—"Unhandled error during audit record creation for: <name>"
If no error_handler is provided for a denial, the SDK renders the platform’s standard action-denied modal.
Client-side and server-side error handling is important to ensure a smooth user experience when an audit event is denied or step-up authentication fails. As shown above, both client-side and server-side extensions allow a custom error handler to be provided to manage these scenarios. If you do not provide a custom error handler, the SDK will provide some default error handling mechanisms.
With server-side extension handling, the SDK and Online Banking platform can manage returning an appropriate error response to the client which will open an modal if the user is blocked or unable to complete the process.
MFA step-up exit or lockout — the audit action triggered MFA and the user cancelled or was locked out:
MFAExitMessage.EXITED—"Validation workflow exited or failed."MFAExitMessage.LOCKED_OUT—"User was locked out during MFA validation."
The error_handler fires for both SSR and CSR extensions on MFA exit.
from q2_sdk.core.http_handlers.tecton_server_handler import Q2TectonServerRequestHandler, create_audit
from q2_sdk.core.http_handlers.tecton_base_handler import MFAExitMessage, MFAMessage
class AuthorizedUserHandler(Q2TectonServerRequestHandler):
def handle_audit_error(self, error):
if error == MFAExitMessage.LOCKED_OUT:
# User was locked out after too many failed MFA attempts.
self.logger.warning("User %s was locked out during MFA.", self.online_user.login_name)
return
elif error == MFAExitMessage.EXITED:
# User voluntarily cancelled the MFA step-up flow.
pass
elif error.startswith(MFAMessage.CSR_INSUFFICIENT):
# CSR operator lacks sufficient assist rights for this action.
self.logger.warning("CSR rights insufficient: %s", error)
elif error == MFAMessage.DENIED:
# Q2 Patrol has flagged the session as suspect.
self.logger.warning("Audit action denied: %s", error)
else:
# Unexpected error during audit record creation.
self.logger.error("Audit error: %s", error)
template = self.get_template(
"error.html.jinja2",
{
"header": "MyExtension: Action Denied",
"message": "This action could not be completed.",
},
)
return self.get_tecton_form(
"MyExtension",
custom_template=template,
hide_submit_button=True,
)
@create_audit(audit_action_name="CustomAuditAction", error_handler=handle_audit_error)
async def submit(self):
"""
This route is only called after the audit record is created and MFA is validated.
"""
Warning
The error_handler function must be synchronous — it cannot be defined with async or use await.
Note
Once a user is locked out, their HQ session is no longer valid.