Skip to content

Commit

Permalink
add support for access request promoted notification
Browse files Browse the repository at this point in the history
  • Loading branch information
rudream committed Jun 6, 2024
1 parent 93f8f92 commit fa24b82
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 10 deletions.
2 changes: 2 additions & 0 deletions api/types/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -1029,6 +1029,8 @@ const (
NotificationAccessRequestApprovedSubKind = "access-request-approved"
// NotificationAccessRequestDeniedSubKind is the subkind for a notification for a user's access request being denied.
NotificationAccessRequestDeniedSubKind = "access-request-denied"
// NotificationAccessRequestPromotedSubKind is the subkind for a notification for a user's access request being promoted to an access list.
NotificationAccessRequestPromotedSubKind = "access-request-promoted"
)

const (
Expand Down
26 changes: 16 additions & 10 deletions lib/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -5202,23 +5202,29 @@ func generateAccessRequestReviewedNotification(req types.AccessRequest, params t
if req.GetState().IsApproved() {
subKind = types.NotificationAccessRequestApprovedSubKind
reviewVerb = "approved"
} else if req.GetState().IsPromoted() {
subKind = types.NotificationAccessRequestPromotedSubKind
} else {
subKind = types.NotificationAccessRequestDeniedSubKind
reviewVerb = "denied"
}

var notificationText string
// If this was a resource request.
if len(req.GetRequestedResourceIDs()) > 0 {
notificationText = fmt.Sprintf("%s %s your access request for %d resources.", params.Review.Author, reviewVerb, len(req.GetRequestedResourceIDs()))
if len(req.GetRequestedResourceIDs()) == 1 {
notificationText = fmt.Sprintf("%s %s your access request for a resource.", params.Review.Author, reviewVerb)
}
// If this was a role request.
if req.GetState().IsPromoted() {
notificationText = fmt.Sprintf("%s promoted your access request to long-term access.", params.Review.Author)
} else {
notificationText = fmt.Sprintf("%s %s your access request for the '%s' role.", params.Review.Author, reviewVerb, req.GetRoles()[0])
if len(req.GetRoles()) > 1 {
notificationText = fmt.Sprintf("%s %s your access request for %d roles.", params.Review.Author, reviewVerb, len(req.GetRoles()))
// If this was a resource request.
if len(req.GetRequestedResourceIDs()) > 0 {
notificationText = fmt.Sprintf("%s %s your access request for %d resources.", params.Review.Author, reviewVerb, len(req.GetRequestedResourceIDs()))
if len(req.GetRequestedResourceIDs()) == 1 {
notificationText = fmt.Sprintf("%s %s your access request for a resource.", params.Review.Author, reviewVerb)
}
// If this was a role request.
} else {
notificationText = fmt.Sprintf("%s %s your access request for the '%s' role.", params.Review.Author, reviewVerb, req.GetRoles()[0])
if len(req.GetRoles()) > 1 {
notificationText = fmt.Sprintf("%s %s your access request for %d roles.", params.Review.Author, reviewVerb, len(req.GetRoles()))
}
}
}

Expand Down
116 changes: 116 additions & 0 deletions lib/auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3943,3 +3943,119 @@ func TestAccessRequestAuditLog(t *testing.T) {
require.Equal(t, expectedAnnotations, arc.Annotations)
require.Equal(t, "APPROVED", arc.RequestState)
}

func TestAccessRequestNotifications(t *testing.T) {
t.Parallel()
ctx := context.Background()

fakeClock := clockwork.NewFakeClock()

testAuthServer, err := NewTestAuthServer(TestAuthServerConfig{
Dir: t.TempDir(),
Clock: fakeClock,
})
require.NoError(t, err)
testTLSServer, err := testAuthServer.NewTestTLSServer()
require.NoError(t, err)

reviewerUsername := "reviewer"
requesterUsername := "requester"
requestRoleName := "requestRole"

reviewerRole, err := types.NewRole(reviewerUsername, types.RoleSpecV6{
Allow: types.RoleConditions{
Logins: []string{"user"},
ReviewRequests: &types.AccessReviewConditions{
Roles: []string{"requestRole"},
},
},
})
require.NoError(t, err)

requesterRole, err := types.NewRole(requesterUsername, types.RoleSpecV6{
Allow: types.RoleConditions{
Request: &types.AccessRequestConditions{
Roles: []string{requestRoleName},
},
},
})
require.NoError(t, err)

requestedRole, err := types.NewRole(requestRoleName, types.RoleSpecV6{
Allow: types.RoleConditions{
Request: &types.AccessRequestConditions{
Roles: []string{requestRoleName},
},
},
})
require.NoError(t, err)
_, err = testTLSServer.AuthServer.AuthServer.UpsertRole(ctx, requestedRole)
require.NoError(t, err)

_, err = testTLSServer.AuthServer.AuthServer.UpsertRole(ctx, reviewerRole)
require.NoError(t, err)
reviewer, err := types.NewUser(reviewerUsername)
require.NoError(t, err)
reviewer.SetRoles([]string{reviewerUsername})
_, err = testTLSServer.AuthServer.AuthServer.UpsertUser(ctx, reviewer)
require.NoError(t, err)

_, err = testTLSServer.AuthServer.AuthServer.UpsertRole(ctx, requesterRole)
require.NoError(t, err)
requester, err := types.NewUser(requesterUsername)
require.NoError(t, err)
requester.SetRoles([]string{requesterUsername})
_, err = testTLSServer.AuthServer.AuthServer.UpsertUser(ctx, requester)
require.NoError(t, err)

accessRequest, err := types.NewAccessRequest(uuid.NewString(), requesterUsername, requestRoleName)
require.NoError(t, err)
req, err := testTLSServer.AuthServer.AuthServer.CreateAccessRequestV2(ctx, accessRequest, TestUser(requesterUsername).I.GetIdentity())
require.NoError(t, err)

// Verify that a global notification was created which matches for users who can review the requestRole.
globalNotifsResp, _, err := testTLSServer.AuthServer.AuthServer.Notifications.ListGlobalNotifications(ctx, 100, "")
require.NoError(t, err)
require.Len(t, globalNotifsResp, 1)
require.Equal(t, &types.AccessReviewConditions{
Roles: []string{requestRoleName},
}, globalNotifsResp[0].GetSpec().GetByPermissions().GetRoleConditions()[0].ReviewRequests)

reviewerIdentity := TestUser(reviewerUsername)
reviewerClient, err := testTLSServer.NewClient(reviewerIdentity)
require.NoError(t, err)

// Approve the request
_, err = reviewerClient.SubmitAccessReview(ctx, types.AccessReviewSubmission{
RequestID: req.GetName(),
Review: types.AccessReview{
ProposedState: types.RequestState_APPROVED,
},
})
require.NoError(t, err)
// Verify that a user notification was created notifying the requester that their access request was approved.
userNotifsResp, _, err := testTLSServer.AuthServer.AuthServer.Notifications.ListUserNotifications(ctx, 100, "")
require.NoError(t, err)
require.Len(t, userNotifsResp, 1)
require.Contains(t, userNotifsResp[0].GetMetadata().GetLabels()[types.NotificationTitleLabel], "reviewer approved your access request")

// Create another access request.
accessRequest, err = types.NewAccessRequest(uuid.NewString(), requesterUsername, requestRoleName)
require.NoError(t, err)
req, err = testTLSServer.AuthServer.AuthServer.CreateAccessRequestV2(ctx, accessRequest, TestUser(requesterUsername).I.GetIdentity())
require.NoError(t, err)

// Deny the request.
_, err = reviewerClient.SubmitAccessReview(ctx, types.AccessReviewSubmission{
RequestID: req.GetName(),
Review: types.AccessReview{
ProposedState: types.RequestState_DENIED,
},
})
require.NoError(t, err)
// Verify that a user notification was created notifying the requester that their access request was denied.
userNotifsResp, _, err = testTLSServer.AuthServer.AuthServer.Notifications.ListUserNotifications(ctx, 100, "")
require.NoError(t, err)
require.Len(t, userNotifsResp, 2)
require.Contains(t, userNotifsResp[1].GetMetadata().GetLabels()[types.NotificationTitleLabel], "reviewer denied your access request")
}
13 changes: 13 additions & 0 deletions web/packages/teleport/src/Notifications/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,17 @@ export const notifications: Notification[] = [
},
],
},
{
id: '7',
title: `joe promoted your access request to long-term access.`,
subKind: NotificationSubKind.AccessRequestPromoted,
createdDate: subMinutes(Date.now(), 4), // 4 minutes ago
clicked: false,
labels: [
{
name: 'request-id',
value: '3bd7d71f-64ad-588a-988c-22f3853910fa',
},
],
},
];
1 change: 1 addition & 0 deletions web/packages/teleport/src/services/notifications/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export enum NotificationSubKind {
AccessRequestPending = 'access-request-pending',
AccessRequestApproved = 'access-request-approved',
AccessRequestDenied = 'access-request-denied',
AccessRequestPromoted = 'access-request-promoted',
}

/** LocalNotificationKind is the kind of local notifications which are generated on the frontend and not stored in the backend. These do not need to be kept in sync with the backend. */
Expand Down

0 comments on commit fa24b82

Please sign in to comment.