From e5b346b0a1b748d7df11e21626a1c7c943bcf205 Mon Sep 17 00:00:00 2001 From: Michael Myers Date: Tue, 31 Dec 2024 16:32:05 -0600 Subject: [PATCH] Display permission banner on Enroll Resource page This adds an info banner to the Enroll Resource page if the user has no permissions to add any resource kind. --- .../SelectResource/SelectResource.test.tsx | 66 ++++++++++++++++++- .../SelectResource/SelectResource.tsx | 31 +++++---- web/packages/teleport/src/features.tsx | 5 +- 3 files changed, 88 insertions(+), 14 deletions(-) diff --git a/web/packages/teleport/src/Discover/SelectResource/SelectResource.test.tsx b/web/packages/teleport/src/Discover/SelectResource/SelectResource.test.tsx index 23c8ff9ab5967..9a963a4f7e2b2 100644 --- a/web/packages/teleport/src/Discover/SelectResource/SelectResource.test.tsx +++ b/web/packages/teleport/src/Discover/SelectResource/SelectResource.test.tsx @@ -16,18 +16,32 @@ * along with this program. If not, see . */ +import { MemoryRouter } from 'react-router'; + import { Platform, UserAgent } from 'design/platform'; +import { render, screen, waitFor } from 'design/utils/testing'; import { OnboardUserPreferences, Resource, } from 'gen-proto-ts/teleport/userpreferences/v1/onboard_pb'; +import { ContextProvider } from 'teleport/index'; +import { + allAccessAcl, + createTeleportContext, + noAccess, +} from 'teleport/mocks/contexts'; import { OnboardDiscover } from 'teleport/services/user'; import { makeDefaultUserPreferences } from 'teleport/services/userPreferences/userPreferences'; +import * as userUserContext from 'teleport/User/UserContext'; import { ResourceKind } from '../Shared'; import { resourceKindToPreferredResource } from '../Shared/ResourceKind'; -import { filterResources, sortResources } from './SelectResource'; +import { + filterResources, + SelectResource, + sortResources, +} from './SelectResource'; import { ResourceSpec } from './types'; const setUp = () => { @@ -1112,6 +1126,56 @@ describe('sorting Connect My Computer', () => { }); }); +test('displays an info banner if lacking permissions to add resources', async () => { + jest.spyOn(userUserContext, 'useUser').mockReturnValue({ + preferences: makeDefaultUserPreferences(), + updatePreferences: () => null, + updateClusterPinnedResources: () => null, + getClusterPinnedResources: () => null, + }); + + const ctx = createTeleportContext(); + ctx.storeUser.setState({ acl: { ...allAccessAcl, tokens: noAccess } }); + + render( + + + {}} /> + + + ); + + await waitFor(() => { + expect( + screen.getByText(/You cannot add new resources./i) + ).toBeInTheDocument(); + }); +}); + +test('does not display erorr banner if user has permissions to add', async () => { + jest.spyOn(userUserContext, 'useUser').mockReturnValue({ + preferences: makeDefaultUserPreferences(), + updatePreferences: () => null, + updateClusterPinnedResources: () => null, + getClusterPinnedResources: () => null, + }); + + const ctx = createTeleportContext(); + ctx.storeUser.setState({ acl: { ...allAccessAcl } }); + + render( + + + {}} /> + + + ); + + expect( + screen.queryByText(/You cannot add new resources./i) + ).not.toBeInTheDocument(); +}); + describe('filterResources', () => { it('filters out resources based on supportedPlatforms', () => { const winAndLinux = makeResourceSpec({ diff --git a/web/packages/teleport/src/Discover/SelectResource/SelectResource.tsx b/web/packages/teleport/src/Discover/SelectResource/SelectResource.tsx index 37a764b01cd31..d8fa09e0645e3 100644 --- a/web/packages/teleport/src/Discover/SelectResource/SelectResource.tsx +++ b/web/packages/teleport/src/Discover/SelectResource/SelectResource.tsx @@ -20,7 +20,7 @@ import { useEffect, useState, type ComponentPropsWithoutRef } from 'react'; import { useHistory, useLocation } from 'react-router'; import styled from 'styled-components'; -import { Box, Flex, Link, P3, Text } from 'design'; +import { Alert, Box, Flex, Link, P3, Text } from 'design'; import * as Icons from 'design/Icon'; import { NewTab } from 'design/Icon'; import { getPlatform, Platform } from 'design/platform'; @@ -71,12 +71,21 @@ export function SelectResource({ onSelect }: SelectResourceProps) { const { preferences } = useUser(); const [search, setSearch] = useState(''); - const [resources, setResources] = useState([]); - const [defaultResources, setDefaultResources] = useState([]); - const [showApp, setShowApp] = useState(false); const RESOURCES = !cfg.isEnterprise ? BASE_RESOURCES : [...BASE_RESOURCES, ...SAML_APPLICATIONS]; + const { acl, authType } = ctx.storeUser.state; + const platform = getPlatform(); + const [resources, setResources] = useState( + addHasAccessField(acl, filterResources(platform, authType, RESOURCES)) + ); + + // a user must be able to create tokens AND have access to create at least one + // type of resource in order to be considered eligible to "add resources" + const canAddResources = acl.tokens.create && resources.some(r => r.hasAccess); + + const [defaultResources, setDefaultResources] = useState([]); + const [showApp, setShowApp] = useState(false); function onSearch(s: string, customList?: ResourceSpec[]) { const list = customList || defaultResources; @@ -96,14 +105,6 @@ export function SelectResource({ onSelect }: SelectResourceProps) { useEffect(() => { // Apply access check to each resource. - const userContext = ctx.storeUser.state; - const { acl, authType } = userContext; - const platform = getPlatform(); - - const resources = addHasAccessField( - acl, - filterResources(platform, authType, RESOURCES) - ); const onboardDiscover = storageService.getOnboardDiscover(); const sortedResources = sortResources( resources, @@ -147,6 +148,12 @@ export function SelectResource({ onSelect }: SelectResourceProps) { return ( + {!canAddResources && ( + + You cannot add new resources. Reach out to your Teleport administrator + for additional permissions. + + )} Select Resource To Add diff --git a/web/packages/teleport/src/features.tsx b/web/packages/teleport/src/features.tsx index 94b92b0150d97..e1af09b9b3dde 100644 --- a/web/packages/teleport/src/features.tsx +++ b/web/packages/teleport/src/features.tsx @@ -476,7 +476,10 @@ export class FeatureIntegrationEnroll implements TeleportFeature { }; hasAccess(flags: FeatureFlags) { - return flags.enrollIntegrations; + if (cfg.hideInaccessibleFeatures) { + return flags.enrollIntegrations; + } + return true; } navigationItem = {