Defining Who Can Approve Requests
Overview
Out of the box, the Sym SDK provides a single Reducer which defines the basic permissions for who can do what with a given request:
from sym.sdk.annotations import reducer
from sym.sdk.request_permission import PermissionLevel, RequestPermission
@reducer
def get_permissions(event):
return RequestPermission(
webapp_view=PermissionLevel.ADMIN,
approve_deny=PermissionLevel.ADMIN,
# Users can approve their own requests. This is great for testing!
allow_self_approval=True
)
Beyond this, multiple ways to determine approval behavior exist, ranging from customizing this Reducer to full automation based on custom SDK logic.
Concepts
Define permissions declaratively using the get_permissions
Reducer
get_permissions
ReducerThe simplest use case of the get_permissions
Reducer is to grant a permission to one of the built-in Sym User Roles using the PermissionLevel
enum. A permission can be restricted to admins only using PermissionLevel.ADMIN
, restricted to admins and members by using PermissionLevel.MEMBER
, or granted to all users in your Sym organization, including guests, by using PermissionLevel.ALL_USERS
.
The Reducer can also be extended to handle more complex scenarios by calling upon the Sym SDK's various Integrations to define your permissions.
For example, if you want to allow literally all of your users to use your webapp while only granting the ability to approve or deny requests to a list of trusted users which your org maintains in an Okta group, you can do that:
from sym.sdk.annotations import reducer
from sym.sdk.exceptions import OktaError
from sym.sdk.integrations import okta
from sym.sdk.request_permission import RequestPermission, PermissionLevel
from sym.sdk.user import user_ids
@reducer
def get_permissions(event):
try:
approve_deny_permission = user_ids(okta.users_in_group(group_id="00g123456abc"))
except OktaError:
approve_deny_permission = PermissionLevel.ADMIN
return RequestPermission(
webapp_view=PermissionLevel.ALL_USERS,
approve_deny=approve_deny_permission,
allow_self_approval=False,
)
This example also takes advantage of the Sym SDK's exceptions to gracefully degrade in case the Okta API is unavailable.
The get_permissions
Reducer is invoked once for each request, at the time the request is made, and its output is persisted for the lifetime of the request. This makes it well-suited for declarative, static permissions, such as when you want to restrict the ability to approve a request to a known set of users that won't change in the scope of an individual request's lifetime.
For more details on what you can do with the get_permissions
Reducer, including some important notes on implicit permission grants that always apply, please see its documentation.
Gate actions dynamically using Hooks
For more complex scenarios that require more flexibility than a declarative approach, you can use Hooks (more specifically, on_
Hooks). Unlike the get_permissions
Reducer, which is evaluated once per request when the request is made then persisted for the lifetime of the request, Hooks are evaluated just in time for their respective Event—for example, the on_approve
Hook is not called until someone actually attempts to approve a request, and it will be called each time someone attempts to approve a request. By contrast with the get_permissions
Reducer, this makes Hooks useful for making dynamic decisions based on the state of the world in the moment. They're likewise useful for more than just gating actions, but also for powerful, smart automations—see Automating and Fast-Tracking Approvals for more on that.
get_permissions
is evaluated before HooksThe
get_permissions
Reducer takes precedence over Hooks. If theget_permissions
Reducer denies someone the ability to take an action, that action's Hook will not fire. For example, if a user is not granted theapprove_deny
permission and they click the Deny button, theon_deny
Hook will not be invoked and the user will be told they don't have permission to do that.On the other hand, if
approve_deny
is granted to a user and they click the Deny button, theon_deny
Hook will be invoked and it will have a chance to prevent the user from denying the request.
You can block user actions with ApprovalTemplate.ignore()
ApprovalTemplate.ignore()
In addition to the transition methods used to Automate and Fast-Track Approvals, the Sym SDK provides a special method that can be used to simply block user interaction without causing a transition, and send a message back to the acting user.
from sym.sdk.annotations import hook
from sym.sdk.templates import ApprovalTemplate
@hook
def on_approve(event):
return ApprovalTemplate.ignore(message="No one can ever approve this request!")
The on_approve
can be used to check + block approvers
on_approve
can be used to check + block approversAssuming a request has been made successfully and appears in a Slack channel or message, the next step along the happy path is for someone to "approve" the request.
Approving a request will trigger any on_approve
hook to fire, prior to executing the approval itself, and any subsequent escalation.
In the below example, we dynamically change which Okta groups are allowed to approve a request based on whether there is an ongoing PagerDuty incident at the time of the approval attempt.
from sym.sdk.annotations import hook
from sym.sdk.integrations import okta, pagerduty, PagerDutyStatus
from sym.sdk.templates import ApprovalTemplate
@hook
def on_approve(event):
okta_managers_group_id = "00g123456"
okta_engineers_group_id = "00g7890ab"
if pagerduty.has_incident(service_ids=["P9Q1Z1D"], statuses=[PagerDutyStatus.ACKNOWLEDGED]):
if not okta.is_user_in_group(event.user, group_id=okta_managers_group_id) and not okta.is_user_in_group(
event.user, group_id=okta_engineers_group_id
):
return ApprovalTemplate.ignore(
message="Ongoing incident—You must be a manager or engineer to approve this request."
)
else:
if not okta.is_user_in_group(event.user, group_id=okta_managers_group_id):
return ApprovalTemplate.ignore(
message=(
"You must be a manager to approve this request. If there is an ongoing incident, please acknowledge"
" it before attempting to approve."
)
)
Note that for this example to work, both Okta groups of users would need to be granted approve_deny
permissions by the get_permissions
Reducer, either explicitly or by using one of the PermissionLevel
enum elements. The allow_self_approval
permission can also be used to control if members of these groups are allowed to approve their own requests.
on_deny
is a separate event, but works exactly the same way
on_deny
is a separate event, but works exactly the same waySimilar to on_approve
, Sym provides an on_deny
hook that you can use as a checkpoint to ensure only approved people or groups are able to reject requests. This is a less common application, as denying a request tends to have no direct consequence other than creating a need to re-request; but it works exactly as you'd expect:
from sym.sdk.annotations import hook
from sym.sdk.templates import ApprovalTemplate
from sym.sdk.integrations import okta
@hook
def on_deny(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 deny access requests.")
Updated 2 months ago