Skip to content

Commit 437714a

Browse files
authored
Merge pull request #52 from PostHog/tom/supersede
Mark superseded grants in Slack on re-request
2 parents 07a339e + 0ddccf5 commit 437714a

5 files changed

Lines changed: 562 additions & 4 deletions

File tree

src/access_control.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ def execute_decision( # noqa: PLR0913
318318
approver=approver,
319319
requester=requester,
320320
user_account_assignment=account_assignment,
321+
slack_client=slack_client,
321322
thread_ts=thread_ts,
322323
permission_set_name=permission_set.name,
323324
account_name=account.name,
@@ -461,6 +462,7 @@ def execute_decision_on_group_request( # noqa: PLR0913
461462
approver=approver,
462463
requester=requester,
463464
group_assignment=group_assignment,
465+
slack_client=slack_client,
464466
thread_ts=thread_ts,
465467
can_extend_expired_grant=can_extend,
466468
extensions_count=0,

src/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ class Config(BaseSettings):
177177
denied_status: str = ":x: *DENIED*"
178178
timed_out_status: str = ":clock1: *TIMED OUT*"
179179
access_ended_status: str = ":checkered_flag: *SESSION COMPLETE*"
180+
superseded_status: str = ":arrows_counterclockwise: *SUPERSEDED*"
180181

181182
@model_validator(mode="before")
182183
@classmethod

src/main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1132,6 +1132,7 @@ def handle_extend_grant_button_click(body: dict, client: WebClient, context: Bol
11321132
approver=approver,
11331133
requester=requester,
11341134
user_account_assignment=account_assignment,
1135+
slack_client=client,
11351136
thread_ts=thread_ts,
11361137
permission_set_name=payload.permission_set_name,
11371138
account_name=payload.account_name,
@@ -1188,6 +1189,7 @@ def handle_extend_grant_button_click(body: dict, client: WebClient, context: Bol
11881189
approver=approver,
11891190
requester=requester,
11901191
group_assignment=group_assignment,
1192+
slack_client=client,
11911193
thread_ts=thread_ts,
11921194
can_extend_expired_grant=True,
11931195
extensions_count=new_extensions_count,

src/schedule.py

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import botocore.exceptions
77
import jmespath as jp
8+
import slack_sdk
89
from croniter import croniter
910
from mypy_boto3_events import EventBridgeClient
1011
from mypy_boto3_events import type_defs as events_type_defs
@@ -14,6 +15,7 @@
1415

1516
import config
1617
import entities
18+
import slack_helpers
1719
import sso
1820
from events import (
1921
ApproverNotificationEvent,
@@ -159,15 +161,54 @@ def _create_schedule_with_retry(
159161
def get_and_delete_scheduled_revoke_event_if_already_exist(
160162
client: EventBridgeSchedulerClient,
161163
event: sso.UserAccountAssignment | sso.GroupAssignment,
162-
) -> None:
164+
) -> list[str]:
165+
"""Delete any existing scheduled revoke for this assignment.
166+
167+
Returns the thread_ts of each orphaned grant message so the caller can flip its
168+
header to SUPERSEDED. The extend-grant flow reuses the original thread_ts, so the
169+
caller is expected to skip any orphan whose ts matches the new request's thread_ts.
170+
"""
171+
orphaned_thread_ts: list[str] = []
163172
for scheduled_event in get_scheduled_events(client):
164173
logger.debug("Checking if schedule already exist", extra={"scheduled_event": scheduled_event})
165174
if isinstance(scheduled_event, ScheduledRevokeEvent) and scheduled_event.revoke_event.user_account_assignment == event:
166175
logger.info("Schedule already exist, deleting it", extra={"schedule_name": scheduled_event.revoke_event.schedule_name})
167176
delete_schedule(client, scheduled_event.revoke_event.schedule_name)
177+
if scheduled_event.revoke_event.thread_ts:
178+
orphaned_thread_ts.append(scheduled_event.revoke_event.thread_ts)
168179
if isinstance(scheduled_event, ScheduledGroupRevokeEvent) and scheduled_event.revoke_event.group_assignment == event:
169180
logger.info("Schedule already exist, deleting it", extra={"schedule_name": scheduled_event.revoke_event.schedule_name})
170181
delete_schedule(client, scheduled_event.revoke_event.schedule_name)
182+
if scheduled_event.revoke_event.thread_ts:
183+
orphaned_thread_ts.append(scheduled_event.revoke_event.thread_ts)
184+
return orphaned_thread_ts
185+
186+
187+
def mark_superseded(slack_client: slack_sdk.WebClient, thread_ts: str) -> None:
188+
"""Flip an orphaned grant message's header to SUPERSEDED and remove its early-revoke button.
189+
190+
Called when a newer request replaces the schedule for the same assignment, leaving the
191+
old grant message with no revoker to update it on expiry.
192+
"""
193+
message = slack_helpers.get_message_from_timestamp(
194+
channel_id=cfg.slack_channel_id,
195+
message_ts=thread_ts,
196+
slack_client=slack_client,
197+
)
198+
if message is None:
199+
logger.warning("Could not find orphaned grant message to mark superseded", extra={"thread_ts": thread_ts})
200+
return
201+
blocks = slack_helpers.HeaderSectionBlock.set_status(
202+
blocks=message["blocks"],
203+
status_text=cfg.superseded_status,
204+
)
205+
slack_client.chat_update(
206+
channel=cfg.slack_channel_id,
207+
ts=thread_ts,
208+
blocks=blocks,
209+
text="Superseded by newer request",
210+
)
211+
slack_helpers.delete_early_revoke_button(slack_client, cfg.slack_channel_id, thread_ts)
171212

172213

173214
def event_bridge_schedule_after(td: timedelta) -> str:
@@ -181,6 +222,7 @@ def schedule_revoke_event( # noqa: PLR0913
181222
approver: entities.slack.User,
182223
requester: entities.slack.User,
183224
user_account_assignment: sso.UserAccountAssignment,
225+
slack_client: slack_sdk.WebClient,
184226
thread_ts: str | None = None,
185227
permission_set_name: str | None = None,
186228
account_name: str | None = None,
@@ -193,7 +235,12 @@ def schedule_revoke_event( # noqa: PLR0913
193235
Tuple of (CreateScheduleOutput, schedule_name)
194236
"""
195237
logger.info("Scheduling revoke event")
196-
get_and_delete_scheduled_revoke_event_if_already_exist(schedule_client, user_account_assignment)
238+
orphaned_thread_ts = get_and_delete_scheduled_revoke_event_if_already_exist(schedule_client, user_account_assignment)
239+
for orphan_ts in orphaned_thread_ts:
240+
# Extend-grant flow reuses thread_ts — don't flip the message we're about to keep using.
241+
if orphan_ts == thread_ts:
242+
continue
243+
mark_superseded(slack_client, orphan_ts)
197244

198245
def build_input(schedule_name: str) -> str:
199246
revoke_event = RevokeEvent(
@@ -236,6 +283,7 @@ def schedule_group_revoke_event( # noqa: PLR0913
236283
approver: entities.slack.User,
237284
requester: entities.slack.User,
238285
group_assignment: sso.GroupAssignment,
286+
slack_client: slack_sdk.WebClient,
239287
thread_ts: str | None = None,
240288
can_extend_expired_grant: bool = False,
241289
extensions_count: int = 0,
@@ -246,7 +294,11 @@ def schedule_group_revoke_event( # noqa: PLR0913
246294
Tuple of (CreateScheduleOutput, schedule_name)
247295
"""
248296
logger.info("Scheduling revoke event")
249-
get_and_delete_scheduled_revoke_event_if_already_exist(schedule_client, group_assignment)
297+
orphaned_thread_ts = get_and_delete_scheduled_revoke_event_if_already_exist(schedule_client, group_assignment)
298+
for orphan_ts in orphaned_thread_ts:
299+
if orphan_ts == thread_ts:
300+
continue
301+
mark_superseded(slack_client, orphan_ts)
250302

251303
def build_input(schedule_name: str) -> str:
252304
revoke_event = GroupRevokeEvent(

0 commit comments

Comments
 (0)