diff --git a/pkg/coredata/trust_center_document_access.go b/pkg/coredata/trust_center_document_access.go index 116acb319..1666fc67a 100644 --- a/pkg/coredata/trust_center_document_access.go +++ b/pkg/coredata/trust_center_document_access.go @@ -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 diff --git a/pkg/server/api/trust/v1/slack_handler.go b/pkg/server/api/trust/v1/slack_handler.go index a273aa654..bd651a92d 100644 --- a/pkg/server/api/trust/v1/slack_handler.go +++ b/pkg/server/api/trust/v1/slack_handler.go @@ -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_ 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. - + 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_, reject_, revoke_ + 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( diff --git a/pkg/trust/templates/access-request.json.tmpl b/pkg/trust/templates/access-request.json.tmpl index 01f10dc5d..a464531ca 100644 --- a/pkg/trust/templates/access-request.json.tmpl +++ b/pkg/trust/templates/access-request.json.tmpl @@ -62,36 +62,57 @@ }{{range .Documents}}, { "type": "section", - "text": { - "type": "mrkdwn", - "text": "" - }, + "fields": [ + { + "type": "mrkdwn", + "text": "" + }, + { + "type": "mrkdwn", + {{if eq .Status "GRANTED"}} + "text": "" + {{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": "" - }, - "accessory": {{if .Granted}}{ + "fields": [ + { + "type": "mrkdwn", + "text": "" + }, + { + "type": "mrkdwn", + {{if eq .Status "GRANTED"}} + "text": "" + {{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": "{{if .Category}} ({{jsonEscape .Category}}){{end}}" - }, - "accessory": {{if .Granted}}{ + "fields": [ + { + "type": "mrkdwn", + "text": "{{if .Category}} ({{jsonEscape .Category}}){{end}}" + }, + { + "type": "mrkdwn", + {{if eq .Status "GRANTED"}} + "text": "" + {{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}}", diff --git a/pkg/trust/trust_center_access_service.go b/pkg/trust/trust_center_access_service.go index 2bdf15b40..5d2fa1b36 100644 --- a/pkg/trust/trust_center_access_service.go +++ b/pkg/trust/trust_center_access_service.go @@ -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) } }