diff --git a/lib/services/useracl.go b/lib/services/useracl.go index 241bde207b702..5ec6fe73f1ce8 100644 --- a/lib/services/useracl.go +++ b/lib/services/useracl.go @@ -114,6 +114,8 @@ type UserACL struct { CrownJewel ResourceAccess `json:"crownJewel"` // AccessGraphSettings defines access to manage access graph settings. AccessGraphSettings ResourceAccess `json:"accessGraphSettings"` + // ReviewRequests defines the ability to review requests + ReviewRequests bool `json:"reviewRequests"` } func hasAccess(roleSet RoleSet, ctx *Context, kind string, verbs ...string) bool { @@ -205,6 +207,7 @@ func NewUserACL(user types.User, userRoles RoleSet, features proto.Features, des botInstances := newAccess(userRoles, ctx, types.KindBotInstance) crownJewelAccess := newAccess(userRoles, ctx, types.KindCrownJewel) userTasksAccess := newAccess(userRoles, ctx, types.KindUserTask) + reviewRequests := userRoles.MaybeCanReviewRequests() var auditQuery ResourceAccess var securityReports ResourceAccess @@ -218,6 +221,7 @@ func NewUserACL(user types.User, userRoles RoleSet, features proto.Features, des AppServers: appServerAccess, DBServers: dbServerAccess, DB: dbAccess, + ReviewRequests: reviewRequests, KubeServers: kubeServerAccess, Desktops: desktopAccess, AuthConnectors: authConnectors, diff --git a/lib/services/useracl_test.go b/lib/services/useracl_test.go index 23251b04d1f3f..4bcb6a75763da 100644 --- a/lib/services/useracl_test.go +++ b/lib/services/useracl_test.go @@ -114,6 +114,8 @@ func TestNewUserACL(t *testing.T) { // test that desktopRecordingEnabled being false overrides the roleSet.RecordDesktopSession() returning true userContext = NewUserACL(user, roleSet, proto.Features{}, false, false) require.False(t, userContext.DesktopSessionRecording) + + require.False(t, userContext.ReviewRequests) } func TestNewUserACLCloud(t *testing.T) { diff --git a/web/packages/teleport/src/TopBar/TopBar.tsx b/web/packages/teleport/src/TopBar/TopBar.tsx index 707af5b4b7cac..20c828f7c5510 100644 --- a/web/packages/teleport/src/TopBar/TopBar.tsx +++ b/web/packages/teleport/src/TopBar/TopBar.tsx @@ -168,7 +168,11 @@ export function TopBar({ CustomLogo }: TopBarProps) { /> )} - {topBarLinks.map(({ topMenuItem, navigationItem }) => { + {topBarLinks.map(({ topMenuItem, navigationItem, hasAccess }) => { + const canAccess = hasAccess(ctx.getFeatureFlags()); + if (!canAccess) { + return; + } const link = navigationItem.getLink(clusterId); const currentPath = history.location.pathname; const selected = diff --git a/web/packages/teleport/src/mocks/contexts.ts b/web/packages/teleport/src/mocks/contexts.ts index bf2581de1763e..8b477eb6513d0 100644 --- a/web/packages/teleport/src/mocks/contexts.ts +++ b/web/packages/teleport/src/mocks/contexts.ts @@ -59,6 +59,7 @@ export const allAccessAcl: Acl = { clipboardSharingEnabled: true, desktopSessionRecordingEnabled: true, directorySharingEnabled: true, + reviewRequests: true, license: fullAccess, download: fullAccess, plugins: fullAccess, diff --git a/web/packages/teleport/src/services/user/makeAcl.ts b/web/packages/teleport/src/services/user/makeAcl.ts index 57c5fd99615c7..785b27af00aa3 100644 --- a/web/packages/teleport/src/services/user/makeAcl.ts +++ b/web/packages/teleport/src/services/user/makeAcl.ts @@ -39,6 +39,7 @@ export function makeAcl(json): Acl { const dbServers = json.dbServers || defaultAccess; const db = json.db || defaultAccess; const desktops = json.desktops || defaultAccess; + const reviewRequests = json.reviewRequests ?? false; const connectionDiagnostic = json.connectionDiagnostic || defaultAccess; // Defaults to true, see RFD 0049 // https://github.com/gravitational/teleport/blob/master/rfd/0049-desktop-clipboard.md#security @@ -87,6 +88,7 @@ export function makeAcl(json): Acl { kubeServers, tokens, accessRequests, + reviewRequests, billing, plugins, integrations, diff --git a/web/packages/teleport/src/services/user/types.ts b/web/packages/teleport/src/services/user/types.ts index e2a5484648eb5..0558e0b1f4079 100644 --- a/web/packages/teleport/src/services/user/types.ts +++ b/web/packages/teleport/src/services/user/types.ts @@ -71,6 +71,7 @@ export interface AccessWithUse extends Access { export interface Acl { directorySharingEnabled: boolean; + reviewRequests: boolean; desktopSessionRecordingEnabled: boolean; clipboardSharingEnabled: boolean; authConnectors: Access; diff --git a/web/packages/teleport/src/services/user/user.test.ts b/web/packages/teleport/src/services/user/user.test.ts index 53c6d26d0a04e..7aca65ef89884 100644 --- a/web/packages/teleport/src/services/user/user.test.ts +++ b/web/packages/teleport/src/services/user/user.test.ts @@ -193,6 +193,7 @@ test('undefined values in context response gives proper default values', async ( create: false, remove: false, }, + reviewRequests: false, accessRequests: { list: false, read: false, diff --git a/web/packages/teleport/src/stores/storeUserContext.ts b/web/packages/teleport/src/stores/storeUserContext.ts index 23c075a681607..0290fda593ceb 100644 --- a/web/packages/teleport/src/stores/storeUserContext.ts +++ b/web/packages/teleport/src/stores/storeUserContext.ts @@ -125,6 +125,10 @@ export default class StoreUserContext extends Store { return this.state.acl.clipboardSharingEnabled; } + getReviewRequests() { + return this.state.acl.reviewRequests; + } + getNodeAccess() { return this.state.acl.nodes; } diff --git a/web/packages/teleport/src/teleportContext.tsx b/web/packages/teleport/src/teleportContext.tsx index 535e559d4d0d8..1ba4c3fde4380 100644 --- a/web/packages/teleport/src/teleportContext.tsx +++ b/web/packages/teleport/src/teleportContext.tsx @@ -164,6 +164,7 @@ class TeleportContext implements types.Context { // having list access, requestable roles, or allowed search_as_roles. if (cfg.hideInaccessibleFeatures) { return !!( + userContext.getReviewRequests() || userContext.getAccessRequestAccess().list || userContext.getRequestableRoles().length || userContext.getAllowedSearchAsRoles().length