Hooks: Flow Control and Automation
Hooks are a powerful SDK feature that enable full workflow customization, including side-effects, intercepts, and redirects.
Overview
Hooks are a core SDK tool that enable you to add logic at any step in your workflow, like automatic approvals/denials, side effects in other systems, and so forth.
Every core Sym event comes with a set of hooks:
- An on-Hook (e.g.
on_request
) that executes before the event - An after-Hook (e.g.
after_escalate
) that executes after the event
In practice, not every hook will be useful for every workflow. Below is a selection of some of the most common implementations.
Implementation
Hooks are all defined inside of a Sym Approval Flow's impl.py
file, fire at specific points in the workflow, and can deliver a broad range of user-defined behaviors.
Hooks prefixed with on_
will fire after a relevant event has been triggered, but before it is processed. For example, an on_request
Hook will trigger after a request form is submitted; but before the get_approvers
Reducer is processed and a channel message is sent.
Hooks prefixed with after_
will fire after a relevant event has finished processing.
Recursion is tightly gated
In theory, a Hook can return its own calling event (e.g.
return ApprovalTemplate.request()
inside ofon_request
). To prevent unintended timeouts due to user behavior, the Sym SDK will process the first recursive call only.In other words, any recursive returns in Hooks should be written with the expectation that they will be processed exactly once, after which either a different outcome should be processed, or the Flow will terminate.
Determining if an event
came from a Hook
event
came from a HookYou may want to split your logic based on the source of an event
. For example, if you want to gate approvals to a specific list of people, but always allow SDK automations to pass through. In such cases, you'll want to inspect event.channel.type
, as in the following example.
@hook
def on_approve(event):
"""
Only let members of the approver safelist approve requests
"""
# Don't check approval permissions if the event came from another hook
if event.channel.type == "sdk":
return
if not has_approve_access(event):
return ApprovalTemplate.ignore(
message="You are not authorized to approve this request."
)
For more information about event
attributes, please see Working With Flow Fields and Data.
Examples of common Hooks
on_prompt
on_prompt
on_prompt
Hooks will fire before a user even sees the data entry form for a Flow. This Hook might be useful if you want to block certain users or user groups from even requesting to run certain Flows.
@hook
def on_prompt(event):
if event.flow.srn.slug == "super_secret_flow" and event.user.email != "[email protected]":
return ApprovalTemplate.ignore(message="You're not allowed to access the super secret Flow!")
on_request
on_request
One of the most useful Hooks,on_request
fires right as a user submits their request. This is the best place to do deep data validation, as well as automate approvals and denials. For more examples, see Automating and Fast-Tracking Approvals
@hook
def on_request(event):
'''
Auto-approve urgent requests for
access by the person on call
'''
if pagerduty.is_on_call(event.user):
return ApprovalTemplate.approve()
after_request
after_request
This Hook is useful if, after a request is made, you want to reflect its existence in another system, e.g. your team's shared bug tracker.
@hook
def after_request(event):
# Send a text to everyone on call when a request is made
for user in pagerduty.users_on_call(escalation_policy_name="123"):
requests.post(
url="http://my-website.com/sms",
body={"message": f"{event.user.email} just made an access request!"}
)
on_approve
on_approve
on_approve
is the most commonly used Hook in Sym's SDK. It fires as a user "Approves" a request. For more information, see Defining Who Can Approve Requests
@hook
def on_approve(event):
if not okta.is_user_in_group(event.user, group_id=event.flow.vars["okta_managers_group"]):
return ApprovalTemplate.ignore(message="Only managers may approve access requests.")
on_deny
on_deny
This Hook is effectively the same as on_approve
-- the two often go hand in hand.
@hook
def on_deny(event):
if okta.is_user_in_group(event.user, group_id=event.flow.vars["okta_interns_group"]):
return ApprovalTemplate.ignore(message="Interns cannot deny access requests.")
after_escalate
after_escalate
after_escalate
is the natural partner to after_request
, in that it can be used to follow up on any side-affecting audit trails that an action has taken place via Sym.
@hook
def after_escalate(event):
requester = event.get_actor("request")
# Send a text to the requester when their access is granted
requests.post(
url="http://my-website.com/sms",
body={
"message": f"Your access to {event.flow.srn.slug} has been granted!",
"user_email": requester.email
}
)
on_revoke
on_revoke
By default, only the requester, approver, or admins may revoke access. This default behavior can be overridden with a custom on_revoke
hook. For example:
@hook
def on_revoke(event):
if not okta.is_user_in_group(event.user, group_id=event.flow.vars["okta_managers_group"]):
return ApprovalTemplate.ignore(message="Only managers may revoke access requests.")
If you want to extend the default behavior without fully overriding it, you can add checks for requestor, approver, and admin roles as follows:
@hook
def on_revoke(event):
requester = event.get_actor("request")
approver = event.get_actor("approve")
if event.user not in {requester, approver} and event.user.role != "admin":
return ApprovalTemplate.ignore(message="Only the requester, approver, or admins may revoke access.")
after_deescalate
after_deescalate
Similar to after_escalate
, this Hook can be useful when paper-trailing requests and escalations in third party systems.
@hook
def after_deescalate(event):
# Send a text to the requester when their access has expired
requests.post(
url="http://my-website.com/sms",
body={
"message": f"Your access to {event.flow.srn.slug} has expired!",
"user_email": event.user.email
}
)
Updated 2 months ago