Fix overflow interactions

Signed-off-by: Émile Ré <nemile.re@gmail.com>
This commit is contained in:
Émile Ré
2025-12-10 19:15:56 +01:00
parent 4cc65520d9
commit b140933cb6
4 changed files with 223 additions and 87 deletions

View File

@@ -667,7 +667,7 @@ WHERE
return nil
}
func RejectByDocumentIDs(
func RejectOrRevokeByDocumentIDs(
ctx context.Context,
conn pg.Conn,
scope Scoper,
@@ -676,7 +676,11 @@ func RejectByDocumentIDs(
) error {
q := `
UPDATE trust_center_document_accesses
SET status = 'REJECTED'::trust_center_document_access_status
SET
status = CASE
WHEN status = 'GRANTED'::trust_center_document_access_status THEN 'REVOKED'::trust_center_document_access_status
ELSE 'REJECTED'::trust_center_document_access_status
END
WHERE
%s
AND trust_center_access_id = @trust_center_access_id
@@ -733,7 +737,7 @@ WHERE
return nil
}
func RejectByReportIDs(
func RejectOrRevokeByReportIDs(
ctx context.Context,
conn pg.Conn,
scope Scoper,
@@ -742,7 +746,11 @@ func RejectByReportIDs(
) error {
q := `
UPDATE trust_center_document_accesses
SET status = 'REJECTED'::trust_center_document_access_status
SET
status = CASE
WHEN status = 'GRANTED'::trust_center_document_access_status THEN 'REVOKED'::trust_center_document_access_status
ELSE 'REJECTED'::trust_center_document_access_status
END
WHERE
%s
AND trust_center_access_id = @trust_center_access_id
@@ -1044,7 +1052,7 @@ ON CONFLICT DO NOTHING
"trust_center_document_access_entity_type": TrustCenterDocumentAccessEntityType,
"trust_center_access_id": trustCenterAccessID,
"report_ids": reportIDs,
"requested": status,
"status": status,
"created_at": createdAt,
"updated_at": createdAt,
}
@@ -1141,7 +1149,7 @@ WHERE
return nil
}
func RejectByTrustCenterFileIDs(
func RejectOrRevokeByTrustCenterFileIDs(
ctx context.Context,
conn pg.Conn,
scope Scoper,
@@ -1150,7 +1158,11 @@ func RejectByTrustCenterFileIDs(
) error {
q := `
UPDATE trust_center_document_accesses
SET status = 'REJECTED'::trust_center_document_access_status
SET
status = CASE
WHEN status = 'GRANTED'::trust_center_document_access_status THEN 'REVOKED'::trust_center_document_access_status
ELSE 'REJECTED'::trust_center_document_access_status
END
WHERE
%s
AND trust_center_access_id = @trust_center_access_id

View File

@@ -17,7 +17,6 @@ package trust_v1
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
@@ -25,6 +24,7 @@ import (
"go.gearno.de/kit/httpserver"
"go.gearno.de/kit/log"
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/slack"
"go.probo.inc/probo/pkg/trust"
@@ -34,8 +34,11 @@ type (
SlackInteractivePayload struct {
ResponseURL string `json:"response_url"`
Actions []struct {
ActionID string `json:"action_id"`
Value string `json:"value"`
ActionID string `json:"action_id"`
Value string `json:"value"`
SelectedOption struct {
Value string `json:"value"`
} `json:"selected_option"`
} `json:"actions"`
Container struct {
MessageTS string `json:"message_ts"`
@@ -49,6 +52,11 @@ type (
}
)
const (
StatusAccept = "accept"
StatusReject = "reject"
)
func slackHandler(trustSvc *trust.Service, slackSigningSecret string, logger *log.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
@@ -102,8 +110,9 @@ func slackHandler(trustSvc *trust.Service, slackSigningSecret string, logger *lo
httpserver.RenderJSON(w, http.StatusOK, SlackInteractiveResponse{Success: true, Message: "no action required"})
return
}
action := slackPayload.Actions[0]
if action.Value == "" {
if action.Value == "" && action.SelectedOption.Value == "" {
httpserver.RenderJSON(w, http.StatusOK, SlackInteractiveResponse{Success: true, Message: "no action required"})
return
}
@@ -149,9 +158,10 @@ func slackHandler(trustSvc *trust.Service, slackSigningSecret string, logger *lo
var documentIDs []gid.GID
var reportIDs []gid.GID
var fileIDs []gid.GID
var statusAction string
switch action.ActionID {
case "accept_all", "reject_all":
// accept_all, reject_all
if strings.HasSuffix(action.ActionID, "_all") {
currentMessageId, err := gid.ParseGID(action.Value)
if err != nil {
httpserver.RenderJSON(w, http.StatusBadRequest, SlackInteractiveResponse{Success: false, Message: "invalid message ID"})
@@ -164,36 +174,57 @@ func slackHandler(trustSvc *trust.Service, slackSigningSecret string, logger *lo
httpserver.RenderJSON(w, http.StatusInternalServerError, SlackInteractiveResponse{Success: false, Message: "internal server error"})
return
}
case "accept_document", "reject_document":
docID, err := gid.ParseGID(action.Value)
if err != nil {
httpserver.RenderJSON(w, http.StatusBadRequest, SlackInteractiveResponse{Success: false, Message: "invalid document ID"})
if strings.HasPrefix(action.ActionID, "accept_") {
statusAction = "accept"
} else {
statusAction = "reject"
}
} else {
var gID gid.GID
// handle_<document|report|file> is used in an overflow menu (a select) to choose between grant and reject on requested accesses
if strings.HasPrefix(action.ActionID, "handle_") {
// action value is the select option value. <accept|reject>-<ID>
params := strings.Split(action.SelectedOption.Value, "/")
statusAction = params[0]
gID, err = gid.ParseGID(params[1])
if err != nil {
httpserver.RenderJSON(w, http.StatusBadRequest, SlackInteractiveResponse{Success: false, Message: "invalid ID"})
return
}
} else {
// accept_<document|report|file>, reject_<document|report|file>, revoke_<document|report|file>
gID, err = gid.ParseGID(action.Value)
if err != nil {
httpserver.RenderJSON(w, http.StatusBadRequest, SlackInteractiveResponse{Success: false, Message: "invalid ID"})
return
}
if strings.HasPrefix(action.ActionID, "accept_") {
statusAction = StatusAccept
} else {
statusAction = StatusReject
}
}
switch gID.EntityType() {
case coredata.DocumentEntityType:
documentIDs = []gid.GID{gID}
case coredata.ReportEntityType:
reportIDs = []gid.GID{gID}
case coredata.TrustCenterFileEntityType:
fileIDs = []gid.GID{gID}
default:
logger.ErrorCtx(ctx, "unknown entity type", log.Error(err))
httpserver.RenderJSON(w, http.StatusInternalServerError, SlackInteractiveResponse{Success: false, Message: "internal server error"})
return
}
documentIDs = []gid.GID{docID}
case "accept_report", "reject_report":
repID, err := gid.ParseGID(action.Value)
if err != nil {
httpserver.RenderJSON(w, http.StatusBadRequest, SlackInteractiveResponse{Success: false, Message: "invalid report ID"})
return
}
reportIDs = []gid.GID{repID}
case "accept_file", "reject_file":
fileID, err := gid.ParseGID(action.Value)
if err != nil {
httpserver.RenderJSON(w, http.StatusBadRequest, SlackInteractiveResponse{Success: false, Message: "invalid file ID"})
return
}
fileIDs = []gid.GID{fileID}
default:
httpserver.RenderJSON(w, http.StatusBadRequest, SlackInteractiveResponse{Success: false, Message: fmt.Sprintf("unknown action: %s", action.ActionID)})
return
}
if strings.HasPrefix(action.ActionID, "accept_") {
switch statusAction {
case StatusAccept:
if err := tenantSvc.TrustCenterAccesses.GrantByIDs(
ctx,
initialSlackMessage.OrganizationID,
@@ -206,10 +237,8 @@ func slackHandler(trustSvc *trust.Service, slackSigningSecret string, logger *lo
httpserver.RenderJSON(w, http.StatusInternalServerError, SlackInteractiveResponse{Success: false, Message: "internal server error"})
return
}
}
if strings.HasPrefix(action.ActionID, "reject_") {
if err := tenantSvc.TrustCenterAccesses.RejectByIDs(
case StatusReject:
if err := tenantSvc.TrustCenterAccesses.RejectOrRevokeByIDs(
ctx,
initialSlackMessage.OrganizationID,
requesterEmail,
@@ -221,6 +250,10 @@ func slackHandler(trustSvc *trust.Service, slackSigningSecret string, logger *lo
httpserver.RenderJSON(w, http.StatusInternalServerError, SlackInteractiveResponse{Success: false, Message: "internal server error"})
return
}
default:
logger.ErrorCtx(ctx, "unknown status action access", log.Error(err))
httpserver.RenderJSON(w, http.StatusInternalServerError, SlackInteractiveResponse{Success: false, Message: "internal server error"})
return
}
if err := tenantSvc.SlackMessages.UpdateSlackAccessMessage(

View File

@@ -62,36 +62,57 @@
}{{range .Documents}},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "<https://{{$.Domain}}/organizations/{{$.OrganizationID}}/documents/{{.ID}}|{{jsonEscape .Title}}>"
},
"fields": [
{
"type": "mrkdwn",
"text": "<https://{{$.Domain}}/organizations/{{$.OrganizationID}}/documents/{{.ID}}|{{jsonEscape .Title}}>"
},
{
"type": "mrkdwn",
{{if eq .Status "GRANTED"}}
"text": "<https://{{$.Domain}}/organizations/{{$.OrganizationID}}/trust-center/access|✓ Granted>"
{{else if eq .Status "REJECTED"}}
"text": "✗ Rejected"
{{else if eq .Status "REVOKED"}}
"text": "✗ Revoked"
{{else}}
"text": "Requested"
{{end}}
}
],
"accessory": {{if eq .Status "GRANTED"}}{
"type": "button",
"text": {
"type": "plain_text",
"text": "✓ Granted"
"text": "Revoke"
},
"url": "https://{{$.Domain}}/organizations/{{$.OrganizationID}}/trust-center/access"
}{{else if eq .Status "REJECTED"}}{
"type": "button",
"text": {
"type": "plain_text",
"text": "✗ Rejected"
},
"url": "https://{{$.Domain}}/organizations/{{$.OrganizationID}}/trust-center/access"
}{{else if eq .Status "REVOKED"}}{
"type": "button",
"text": {
"type": "plain_text",
"text": "✗ Revoked"
},
"url": "https://{{$.Domain}}/organizations/{{$.OrganizationID}}/trust-center/access"
"action_id": "reject_document",
"value": "{{.ID}}",
"style": "danger"
}{{else if eq .Status "REQUESTED"}}{
"type": "overflow",
"options": [
{
"text": {
"type": "plain_text",
"text": "Accept"
},
"value": "accept/{{.ID}}"
},
{
"text": {
"type": "plain_text",
"text": "Reject"
},
"value": "reject/{{.ID}}"
}
],
"action_id": "handle_document"
}{{else}}{
"type": "button",
"text": {
"type": "plain_text",
"text": "Accept"
"text": "Grant"
},
"action_id": "accept_document",
"value": "{{.ID}}",
@@ -110,22 +131,57 @@
}{{range .Reports}},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "<https://{{$.Domain}}/organizations/{{$.OrganizationID}}/audits/{{.AuditID}}|{{jsonEscape .Title}}>"
},
"accessory": {{if .Granted}}{
"fields": [
{
"type": "mrkdwn",
"text": "<https://{{$.Domain}}/organizations/{{$.OrganizationID}}/audits/{{.AuditID}}|{{jsonEscape .Title}}>"
},
{
"type": "mrkdwn",
{{if eq .Status "GRANTED"}}
"text": "<https://{{$.Domain}}/organizations/{{$.OrganizationID}}/trust-center/access|✓ Granted>"
{{else if eq .Status "REJECTED"}}
"text": "✗ Rejected"
{{else if eq .Status "REVOKED"}}
"text": "✗ Revoked"
{{else}}
"text": "Requested"
{{end}}
}
],
"accessory": {{if eq .Status "GRANTED"}}{
"type": "button",
"text": {
"type": "plain_text",
"text": "✓ Granted"
"text": "Revoke"
},
"url": "https://{{$.Domain}}/organizations/{{$.OrganizationID}}/trust-center/access"
"action_id": "reject_report",
"value": "{{.ID}}",
"style": "danger"
}{{else if eq .Status "REQUESTED"}}{
"type": "overflow",
"options": [
{
"text": {
"type": "plain_text",
"text": "Accept"
},
"value": "accept/{{.ID}}"
},
{
"text": {
"type": "plain_text",
"text": "Reject"
},
"value": "reject/{{.ID}}"
}
],
"action_id": "handle_report"
}{{else}}{
"type": "button",
"text": {
"type": "plain_text",
"text": "Accept"
"text": "Revoke"
},
"action_id": "accept_report",
"value": "{{.ID}}",
@@ -144,22 +200,57 @@
}{{range .Files}},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "<https://{{$.Domain}}/organizations/{{$.OrganizationID}}/trust-center/files|{{jsonEscape .Name}}>{{if .Category}} ({{jsonEscape .Category}}){{end}}"
},
"accessory": {{if .Granted}}{
"fields": [
{
"type": "mrkdwn",
"text": "<https://{{$.Domain}}/organizations/{{$.OrganizationID}}/trust-center/files|{{jsonEscape .Name}}>{{if .Category}} ({{jsonEscape .Category}}){{end}}"
},
{
"type": "mrkdwn",
{{if eq .Status "GRANTED"}}
"text": "<https://{{$.Domain}}/organizations/{{$.OrganizationID}}/trust-center/access|✓ Granted>"
{{else if eq .Status "REJECTED"}}
"text": "✗ Rejected"
{{else if eq .Status "REVOKED"}}
"text": "✗ Revoked"
{{else}}
"text": "Requested"
{{end}}
}
],
"accessory": {{if eq .Status "GRANTED"}}{
"type": "button",
"text": {
"type": "plain_text",
"text": "✓ Granted"
"text": "Revoke"
},
"url": "https://{{$.Domain}}/organizations/{{$.OrganizationID}}/trust-center/access"
"action_id": "reject_file",
"value": "{{.ID}}",
"style": "danger"
}{{else if eq .Status "REQUESTED"}}{
"type": "overflow",
"options": [
{
"text": {
"type": "plain_text",
"text": "Accept"
},
"value": "accept/{{.ID}}"
},
{
"text": {
"type": "plain_text",
"text": "Reject"
},
"value": "reject/{{.ID}}"
}
],
"action_id": "handle_file"
}{{else}}{
"type": "button",
"text": {
"type": "plain_text",
"text": "Accept"
"text": "Grant"
},
"action_id": "accept_file",
"value": "{{.ID}}",

View File

@@ -576,7 +576,7 @@ func (s *TrustCenterAccessService) sendTrustCenterAccessEmail(
return nil
}
func (s *TrustCenterAccessService) RejectByIDs(
func (s *TrustCenterAccessService) RejectOrRevokeByIDs(
ctx context.Context,
organizationID gid.GID,
email string,
@@ -599,20 +599,20 @@ func (s *TrustCenterAccessService) RejectByIDs(
if len(documentIDs) > 0 {
shouldSendEmail = true
if err := coredata.RejectByDocumentIDs(ctx, tx, s.svc.scope, access.ID, documentIDs); err != nil {
return fmt.Errorf("cannot reject document accesses: %w", err)
if err := coredata.RejectOrRevokeByDocumentIDs(ctx, tx, s.svc.scope, access.ID, documentIDs); err != nil {
return fmt.Errorf("cannot reject/revoke document accesses: %w", err)
}
}
if len(reportIDs) > 0 {
shouldSendEmail = true
if err := coredata.RejectByReportIDs(ctx, tx, s.svc.scope, access.ID, reportIDs); err != nil {
return fmt.Errorf("cannot reject report accesses: %w", err)
if err := coredata.RejectOrRevokeByReportIDs(ctx, tx, s.svc.scope, access.ID, reportIDs); err != nil {
return fmt.Errorf("cannot reject/revoke report accesses: %w", err)
}
}
if len(fileIDs) > 0 {
shouldSendEmail = true
if err := coredata.RejectByTrustCenterFileIDs(ctx, tx, s.svc.scope, access.ID, fileIDs); err != nil {
return fmt.Errorf("cannot reject trust center file accesses: %w", err)
if err := coredata.RejectOrRevokeByTrustCenterFileIDs(ctx, tx, s.svc.scope, access.ID, fileIDs); err != nil {
return fmt.Errorf("cannot reject/revoke trust center file accesses: %w", err)
}
}