Skip to content

Commit a260275

Browse files
authored
Merge pull request #44 from PostHog/tom/approval-button
Fix request approval button not displayed
2 parents c2dae29 + db219f4 commit a260275

3 files changed

Lines changed: 167 additions & 2 deletions

File tree

src/group.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def approver_group_resolver(group_ids: frozenset[str]) -> set[str]:
7474
},
7575
)
7676

77-
show_buttons = bool(decision.approvers)
77+
show_buttons = bool(decision.approvers) or bool(decision.approver_groups)
7878
slack_response = client.chat_postMessage(
7979
blocks=slack_helpers.build_approval_request_message_blocks(
8080
sso_client=sso_client,

src/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,7 @@ def approver_group_resolver(group_ids: frozenset[str]) -> set[str]:
486486
logger.warning("Failed to describe account, using account ID as fallback", extra={"account_id": request.account_id})
487487
account = entities.aws.Account(id=request.account_id, name=request.account_id)
488488

489-
show_buttons = bool(decision.approvers)
489+
show_buttons = bool(decision.approvers) or bool(decision.approver_groups)
490490
slack_response = client.chat_postMessage(
491491
blocks=slack_helpers.build_approval_request_message_blocks(
492492
sso_client=sso_client,

src/tests/test_main.py

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -736,3 +736,168 @@ def test_second_call_after_refetch_uses_cache(self, import_main):
736736

737737
assert group_ids == {"group-y"}
738738
assert email == "twice@test.com"
739+
740+
741+
class TestShowButtonsForApproverGroups:
742+
"""Approve/Deny buttons must render whenever a decision has approvers OR approver_groups.
743+
744+
Regression: previously `show_buttons = bool(decision.approvers)` ignored approver_groups,
745+
leaving requests that only mentioned groups un-actionable in Slack.
746+
"""
747+
748+
def _build_request(self):
749+
import slack_helpers
750+
751+
return slack_helpers.RequestForAccess(
752+
permission_set_name="TestPermissionSet",
753+
account_id="111111111111",
754+
reason="Testing",
755+
requester_slack_id="U_REQUESTER",
756+
permission_duration=timedelta(hours=1),
757+
)
758+
759+
def _decision(self, reason, approvers=frozenset(), approver_groups=frozenset()):
760+
import access_control
761+
762+
return access_control.AccessRequestDecision(
763+
grant=False,
764+
reason=reason,
765+
based_on_statements=frozenset(),
766+
approvers=approvers,
767+
approver_groups=approver_groups,
768+
)
769+
770+
def _setup_patches(self, main_module, decision):
771+
import access_control
772+
773+
patches = [
774+
patch.object(main_module.access_control, "make_decision_on_access_request", return_value=decision),
775+
patch.object(
776+
main_module.access_control,
777+
"execute_decision",
778+
return_value=access_control.ExecuteDecisionResult(granted=False),
779+
),
780+
patch.object(
781+
main_module.sso,
782+
"get_permission_set",
783+
return_value=entities.aws.PermissionSet(name="TestPermissionSet", arn="arn:sso:ps/test", description=None),
784+
),
785+
patch.object(main_module.sso, "get_identity_store_id", return_value="d-123456"),
786+
patch.object(main_module.sso, "get_user_principal_id_by_email", return_value=("principal-id", None)),
787+
patch.object(
788+
main_module.organizations,
789+
"describe_account",
790+
side_effect=lambda _c, account_id: entities.aws.Account(id=account_id, name=f"Account-{account_id}"),
791+
),
792+
patch.object(main_module.slack_helpers, "build_approval_request_message_blocks", return_value=[]),
793+
patch.object(main_module.slack_helpers, "check_if_user_is_in_channel", return_value=True),
794+
patch.object(
795+
main_module.slack_helpers,
796+
"get_user",
797+
return_value=entities.slack.User(id="U_REQUESTER", email="requester@test.com", real_name="Requester"),
798+
),
799+
patch.object(main_module.slack_helpers, "find_approvers_in_slack", return_value=([], [])),
800+
patch.object(main_module.slack_helpers, "build_approver_group_mentions", return_value="<!subteam^S_GROUP>"),
801+
patch.object(main_module.analytics, "capture"),
802+
patch.object(main_module.schedule, "schedule_discard_buttons_event"),
803+
patch.object(main_module.schedule, "schedule_approver_notification_event"),
804+
]
805+
return patches
806+
807+
def _run(self, main_module):
808+
mock_client = MagicMock()
809+
mock_client.chat_postMessage.return_value = {"ts": "123.456", "message": {"blocks": []}}
810+
main_module._process_single_access_request(
811+
request=self._build_request(),
812+
requester=entities.slack.User(id="U_REQUESTER", email="requester@test.com", real_name="Requester"),
813+
user_group_ids=set(),
814+
client=mock_client,
815+
is_user_in_channel=True,
816+
)
817+
return mock_client
818+
819+
def _show_buttons_kwarg(self, main_module):
820+
build = main_module.slack_helpers.build_approval_request_message_blocks
821+
assert build.called, "build_approval_request_message_blocks was not called"
822+
return build.call_args.kwargs["show_buttons"]
823+
824+
def test_show_buttons_true_when_only_approver_groups(self, import_main):
825+
"""Regression: approver_groups alone must still render buttons."""
826+
import access_control
827+
828+
main = import_main
829+
decision = self._decision(
830+
reason=access_control.DecisionReason.RequiresApproval,
831+
approvers=frozenset(),
832+
approver_groups=frozenset(["S_GROUP"]),
833+
)
834+
patches = self._setup_patches(main, decision)
835+
for p in patches:
836+
p.start()
837+
try:
838+
self._run(main)
839+
assert self._show_buttons_kwarg(main) is True
840+
main.schedule.schedule_discard_buttons_event.assert_called_once()
841+
main.schedule.schedule_approver_notification_event.assert_called_once()
842+
finally:
843+
for p in patches:
844+
p.stop()
845+
846+
def test_show_buttons_true_when_only_individual_approvers(self, import_main):
847+
import access_control
848+
849+
main = import_main
850+
decision = self._decision(
851+
reason=access_control.DecisionReason.RequiresApproval,
852+
approvers=frozenset(["approver@test.com"]),
853+
approver_groups=frozenset(),
854+
)
855+
patches = self._setup_patches(main, decision)
856+
for p in patches:
857+
p.start()
858+
try:
859+
self._run(main)
860+
assert self._show_buttons_kwarg(main) is True
861+
finally:
862+
for p in patches:
863+
p.stop()
864+
865+
def test_show_buttons_true_when_both_set(self, import_main):
866+
import access_control
867+
868+
main = import_main
869+
decision = self._decision(
870+
reason=access_control.DecisionReason.RequiresApproval,
871+
approvers=frozenset(["approver@test.com"]),
872+
approver_groups=frozenset(["S_GROUP"]),
873+
)
874+
patches = self._setup_patches(main, decision)
875+
for p in patches:
876+
p.start()
877+
try:
878+
self._run(main)
879+
assert self._show_buttons_kwarg(main) is True
880+
finally:
881+
for p in patches:
882+
p.stop()
883+
884+
def test_show_buttons_false_when_neither_set(self, import_main):
885+
import access_control
886+
887+
main = import_main
888+
decision = self._decision(
889+
reason=access_control.DecisionReason.NoApprovers,
890+
approvers=frozenset(),
891+
approver_groups=frozenset(),
892+
)
893+
patches = self._setup_patches(main, decision)
894+
for p in patches:
895+
p.start()
896+
try:
897+
self._run(main)
898+
assert self._show_buttons_kwarg(main) is False
899+
main.schedule.schedule_discard_buttons_event.assert_not_called()
900+
main.schedule.schedule_approver_notification_event.assert_not_called()
901+
finally:
902+
for p in patches:
903+
p.stop()

0 commit comments

Comments
 (0)