Skip to content

Commit

Permalink
@ requester by email in slack access requests (#40041)
Browse files Browse the repository at this point in the history
* @ requester in access requests

* Add NotifyUser to direct message user on request update

* resolve comments

* Update test to make sure message is sent
  • Loading branch information
Alex McGrath authored Apr 17, 2024
1 parent 8978ab3 commit 03a91bc
Show file tree
Hide file tree
Showing 8 changed files with 64 additions and 3 deletions.
4 changes: 4 additions & 0 deletions integrations/access/accessrequest/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,10 @@ func (a *App) updateMessages(ctx context.Context, reqID string, tag pd.Resolutio
return trace.Wrap(err)
}

if err := a.bot.NotifyUser(ctx, reqID, reqData); err != nil && !trace.IsNotImplemented(err) {
return trace.Wrap(err)
}

log.Infof("Successfully marked request as %s in all messages", tag)

return nil
Expand Down
3 changes: 3 additions & 0 deletions integrations/access/accessrequest/bot.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/integrations/access/common"
"github.com/gravitational/teleport/integrations/lib/plugindata"
pd "github.com/gravitational/teleport/integrations/lib/plugindata"
)

Expand All @@ -37,4 +38,6 @@ type MessagingBot interface {
// UpdateMessages updates access request messages that were previously sent via Broadcast
// This is used to change the access-request status and number of required approval remaining
UpdateMessages(ctx context.Context, reqID string, data pd.AccessRequestData, messageData SentMessages, reviews []types.AccessReview) error
// NotifyUser notifies the user if their access request status has changed
NotifyUser(ctx context.Context, reqID string, ard plugindata.AccessRequestData) error
}
5 changes: 5 additions & 0 deletions integrations/access/discord/bot.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,11 @@ func (b DiscordBot) PostReviewReply(ctx context.Context, channelID, timestamp st
return nil
}

// NotifyUser will send users a direct message with the access request status
func (b DiscordBot) NotifyUser(ctx context.Context, reqID string, reqData pd.AccessRequestData) error {
return trace.NotImplemented("notify user not implemented for plugin")
}

// UpdateMessages updates already posted Discord messages
func (b DiscordBot) UpdateMessages(ctx context.Context, reqID string, reqData pd.AccessRequestData, messagingData accessrequest.SentMessages, reviews []types.AccessReview) error {
var errors []error
Expand Down
5 changes: 5 additions & 0 deletions integrations/access/mattermost/bot.go
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,11 @@ func (b Bot) LookupDirectChannel(ctx context.Context, email string) (string, err
return channel.ID, nil
}

// NotifyUser will send users a direct message with the access request status
func (b Bot) NotifyUser(ctx context.Context, reqID string, reqData pd.AccessRequestData) error {
return trace.NotImplemented("notify user not implemented for plugin")
}

func (b Bot) UpdateMessages(ctx context.Context, reqID string, reqData pd.AccessRequestData, mmData accessrequest.SentMessages, reviews []types.AccessReview) error {
text, err := b.buildPostText(reqID, reqData)
if err != nil {
Expand Down
5 changes: 5 additions & 0 deletions integrations/access/opsgenie/bot.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ func (b *Bot) PostReviewReply(ctx context.Context, _ string, alertID string, rev
return trace.Wrap(b.client.PostReviewNote(ctx, alertID, review))
}

// NotifyUser will send users a direct message with the access request status
func (b Bot) NotifyUser(ctx context.Context, reqID string, reqData pd.AccessRequestData) error {
return trace.NotImplemented("notify user not implemented for plugin")
}

// UpdateMessages add notes to the alert containing updates to status.
// This will also resolve alerts based on the resolution tag.
func (b *Bot) UpdateMessages(ctx context.Context, reqID string, data pd.AccessRequestData, alertData accessrequest.SentMessages, reviews []types.AccessReview) error {
Expand Down
5 changes: 5 additions & 0 deletions integrations/access/servicenow/bot.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ func (b *Bot) PostReviewReply(ctx context.Context, _ string, incidentID string,
return trace.Wrap(b.client.PostReviewNote(ctx, incidentID, review))
}

// NotifyUser will send users a direct message with the access request status
func (b Bot) NotifyUser(ctx context.Context, reqID string, reqData pd.AccessRequestData) error {
return trace.NotImplemented("notify user not implemented for plugin")
}

// UpdateMessages add notes to the incident containing updates to status.
// This will also resolve incidents based on the resolution tag.
func (b *Bot) UpdateMessages(ctx context.Context, reqID string, data pd.AccessRequestData, incidentData accessrequest.SentMessages, reviews []types.AccessReview) error {
Expand Down
31 changes: 29 additions & 2 deletions integrations/access/slack/bot.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,28 @@ func (b Bot) lookupDirectChannelByEmail(ctx context.Context, email string) (stri
return result.User.ID, nil
}

// NotifyUser directly messages a user when their request is updated
func (b Bot) NotifyUser(ctx context.Context, reqID string, reqData pd.AccessRequestData) error {
recipient, err := b.FetchRecipient(ctx, reqData.User)
if err != nil {
return trace.Wrap(err)
}

if recipient.Kind != RecipientKindEmail {
return trace.BadParameter("user was not found, cant directly notify")
}

_, err = b.client.NewRequest().
SetContext(ctx).
SetBody(Message{BaseMessage: BaseMessage{Channel: recipient.ID}, BlockItems: []BlockItem{
NewBlockItem(SectionBlock{
Text: NewTextObjectItem(MarkdownObject{Text: fmt.Sprintf("Request with ID %q has been updated: *%s*", reqID, reqData.ResolutionTag)}),
}),
}}).
Post("chat.postMessage")
return trace.Wrap(err)
}

// Expire updates request's Slack post with EXPIRED status and removes action buttons.
func (b Bot) UpdateMessages(ctx context.Context, reqID string, reqData pd.AccessRequestData, slackData accessrequest.SentMessages, reviews []types.AccessReview) error {
var errors []error
Expand Down Expand Up @@ -217,6 +239,11 @@ func (b Bot) UpdateMessages(ctx context.Context, reqID string, reqData pd.Access
return nil
}

const (
RecipientKindEmail = "Email"
RecipientKindChannel = "Channel"
)

func (b Bot) FetchRecipient(ctx context.Context, name string) (*common.Recipient, error) {
if lib.IsEmail(name) {
channel, err := b.lookupDirectChannelByEmail(ctx, name)
Expand All @@ -229,15 +256,15 @@ func (b Bot) FetchRecipient(ctx context.Context, name string) (*common.Recipient
return &common.Recipient{
Name: name,
ID: channel,
Kind: "Email",
Kind: RecipientKindEmail,
Data: nil,
}, nil
}
// TODO: check if channel exists ?
return &common.Recipient{
Name: name,
ID: name,
Kind: "Channel",
Kind: RecipientKindChannel,
Data: nil,
}, nil
}
Expand Down
9 changes: 8 additions & 1 deletion integrations/access/slack/testlib/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package testlib

import (
"context"
"fmt"
"runtime"
"sort"
"strings"
Expand Down Expand Up @@ -249,7 +250,7 @@ func (s *SlackSuiteOSS) TestApproval() {

s.startApp()

// Test setup: we create an access request and wait for its Discord message
// Test setup: we create an access request and wait for its Slack message
userName := integration.RequesterOSSUserName
req := s.CreateAccessRequest(ctx, userName, []string{s.reviewer1SlackUser.Profile.Email})
msgs := s.checkNewMessages(t, ctx, channelsToMessages(s.requesterOSSSlackUser.ID, s.reviewer1SlackUser.ID), matchOnlyOnChannel)
Expand All @@ -264,6 +265,12 @@ func (s *SlackSuiteOSS) TestApproval() {
require.NoError(t, err)
assert.Equal(t, "*Status*: ✅ APPROVED\n*Resolution reason*: ```\nokay```", statusLine)
})

s.checkNewMessages(t, ctx, channelsToMessages(s.requesterOSSSlackUser.ID), matchOnlyOnChannel, func(t *testing.T, m slack.Message) {
line := fmt.Sprintf("Request with ID %q has been updated: *%s*", req.GetName(), types.RequestState_APPROVED.String())
assert.Equal(t, line, m.BlockItems[0].Block.(slack.SectionBlock).Text.GetText())
})

}

// TestDenial tests that when a request is denied, its corresponding message
Expand Down

0 comments on commit 03a91bc

Please sign in to comment.