diff --git a/sherlock/internal/api/sherlock/users_v3_upsert.go b/sherlock/internal/api/sherlock/users_v3_upsert.go index 720025d63..1945663a0 100644 --- a/sherlock/internal/api/sherlock/users_v3_upsert.go +++ b/sherlock/internal/api/sherlock/users_v3_upsert.go @@ -66,7 +66,7 @@ func usersV3Upsert(ctx *gin.Context) { return } - copiedUser, hasUpdates := processUserEdits(copiedUser, body.userDirectlyEditableFields, body.GithubAccessToken) + copiedUser, hasUpdates, shouldNotify := processUserEdits(copiedUser, body.userDirectlyEditableFields, body.GithubAccessToken) var statusCode int if hasUpdates { if err = db.Omit(clause.Associations).Save(copiedUser).Error; err != nil { @@ -83,10 +83,28 @@ func usersV3Upsert(ctx *gin.Context) { errors.AbortRequest(ctx, err) return } + + if shouldNotify { + lines := make([]string, 0, 3) + if result.Name != nil && result.NameFrom != nil { + lines = append(lines, fmt.Sprintf("Name: %s (from %s)", *result.Name, *result.NameFrom)) + } + if result.GithubUsername != nil && result.GithubID != nil { + lines = append(lines, fmt.Sprintf("GitHub: %s (%s)", *result.GithubUsername, *result.GithubID)) + } + if result.SlackUsername != nil && result.SlackID != nil { + lines = append(lines, fmt.Sprintf("Slack: %s (%s)", *result.SlackUsername, *result.SlackID)) + } + slack.SendPermissionChangeNotification(ctx, callingUser.SlackReference(true), slack.PermissionChangeNotificationInputs{ + Summary: fmt.Sprintf("updated account linking for User %s", result.SlackReference(true)), + Results: lines, + }) + } + ctx.JSON(statusCode, userFromModel(result)) } -func processUserEdits(callingUser *models.User, directEdits userDirectlyEditableFields, userGithubToken *string) (resultingUser *models.User, hasUpdates bool) { +func processUserEdits(callingUser *models.User, directEdits userDirectlyEditableFields, userGithubToken *string) (resultingUser *models.User, hasUpdates bool, shouldNotify bool) { githubString := "github" sherlockString := "sherlock" slackString := "slack" @@ -151,6 +169,7 @@ func processUserEdits(callingUser *models.User, directEdits userDirectlyEditable if slackID != "" && (callingUser.SlackID == nil || slackID != *callingUser.SlackID) { callingUser.SlackID = &slackID hasUpdates = true + shouldNotify = true } // Set Slack username if we got it and it is different from what we already have @@ -170,6 +189,7 @@ func processUserEdits(callingUser *models.User, directEdits userDirectlyEditable if githubID != "" && (callingUser.GithubID == nil || githubID != *callingUser.GithubID) { callingUser.GithubID = &githubID hasUpdates = true + shouldNotify = true } // Set Github username if we got it and it is different from what we already have @@ -184,5 +204,5 @@ func processUserEdits(callingUser *models.User, directEdits userDirectlyEditable callingUser.NameFrom = &githubString hasUpdates = true } - return callingUser, hasUpdates + return callingUser, hasUpdates, shouldNotify } diff --git a/sherlock/internal/api/sherlock/users_v3_upsert_test.go b/sherlock/internal/api/sherlock/users_v3_upsert_test.go index 0b9be741b..02e0a14c8 100644 --- a/sherlock/internal/api/sherlock/users_v3_upsert_test.go +++ b/sherlock/internal/api/sherlock/users_v3_upsert_test.go @@ -140,6 +140,10 @@ func (s *handlerSuite) TestUserV3Upsert_maximal_sherlockName() { Name: "slack username", RealName: "name from slack", }, nil) + + // Security alert + c.EXPECT().SendMessageContext(mock.Anything, "#notification-channel", mock.Anything).Return("", "", "", nil).Once() + c.EXPECT().SendMessageContext(mock.Anything, "#permission-change-channel", mock.Anything).Return("", "", "", nil).Once() }, func() { github.UseMockedClient(s.T(), func(c *github.MockClient) { c.Users.EXPECT().Get(mock.Anything, "").Return(&github2.User{ @@ -188,6 +192,10 @@ func (s *handlerSuite) TestUserV3Upsert_maximal_slackName() { Name: "slack username", RealName: "name from slack", }, nil) + + // Security alert + c.EXPECT().SendMessageContext(mock.Anything, "#notification-channel", mock.Anything).Return("", "", "", nil).Once() + c.EXPECT().SendMessageContext(mock.Anything, "#permission-change-channel", mock.Anything).Return("", "", "", nil).Once() }, func() { github.UseMockedClient(s.T(), func(c *github.MockClient) { c.Users.EXPECT().Get(mock.Anything, "").Return(&github2.User{ @@ -233,6 +241,10 @@ func (s *handlerSuite) TestUserV3Upsert_maximal_githubName() { Name: "slack username", RealName: "name from slack", }, nil) + + // Security alert + c.EXPECT().SendMessageContext(mock.Anything, "#notification-channel", mock.Anything).Return("", "", "", nil).Once() + c.EXPECT().SendMessageContext(mock.Anything, "#permission-change-channel", mock.Anything).Return("", "", "", nil).Once() }, func() { github.UseMockedClient(s.T(), func(c *github.MockClient) { c.Users.EXPECT().Get(mock.Anything, "").Return(&github2.User{ @@ -353,6 +365,7 @@ func (s *handlerSuite) Test_processUserEdits() { githubMockConfig func(c *github.MockClient) wantResultingUser *models.User wantHasUpdates bool + wantShouldNotify bool }{ { name: "can do nothing", @@ -365,15 +378,17 @@ func (s *handlerSuite) Test_processUserEdits() { githubMockConfig: func(c *github.MockClient) {}, wantResultingUser: &models.User{Email: s.TestData.User_Suitable().Email}, wantHasUpdates: false, + wantShouldNotify: false, }, } for _, tt := range tests { s.Run(tt.name, func() { slack.UseMockedClient(s.T(), tt.slackMockConfig, func() { github.UseMockedClient(s.T(), tt.githubMockConfig, func() { - gotResultingUser, gotHasUpdates := processUserEdits(tt.args.callingUser, tt.args.directEdits, tt.args.userGithubToken) + gotResultingUser, gotHasUpdates, gotShouldNotify := processUserEdits(tt.args.callingUser, tt.args.directEdits, tt.args.userGithubToken) assert.Equalf(s.T(), tt.wantResultingUser, gotResultingUser, "processUserEdits(%v, %v, %v)", tt.args.callingUser, tt.args.directEdits, tt.args.userGithubToken) assert.Equalf(s.T(), tt.wantHasUpdates, gotHasUpdates, "processUserEdits(%v, %v, %v)", tt.args.callingUser, tt.args.directEdits, tt.args.userGithubToken) + assert.Equalf(s.T(), tt.wantShouldNotify, gotShouldNotify, "processUserEdits(%v, %v, %v)", tt.args.callingUser, tt.args.directEdits, tt.args.userGithubToken) }) }) })